diff options
483 files changed, 19094 insertions, 0 deletions
diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..73667ad --- /dev/null +++ b/.hgignore @@ -0,0 +1,7 @@ +(^|/)\.svn($|/) +(^|/)\.hg($|/) +(^|/)\.hgtags($|/) +^log$ +\.pyc$ +\.pyo$ +^build$ @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..1baa459 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,453 @@ +ChangeLog for PyLint +==================== + +-- 0.15.0 + * included Stefan Rank's patch to deal with 2.4 relative import + * included Robert Kirkpatrick's tutorial and typos fixes + * fix bug in reenabling message + * fix #2473: invoking pylint on __init__.py (hopefuly) + * typecheck: acquired-members option has been dropped in favor of the more + generic generated-members option. If the zope option is set, the behaviour + is now to add some default values to generated-members. + * flymake integration: added bin/epylint and elisp/pylint-flymake.el + +2008-01-14 -- 0.14.0 + * fix #3733: Messages (dis)appear depending on order of file names + * fix #4026: pylint.el should require compile + * fix a bug in colorized reporter, spotted by Dave Borowitz + * applied patch from Stefan Rank to avoid W0410 false positive when + multiple "from __future__" import statements + * implement #4012: flag back tick as deprecated (new W0333 message) + * new ignored-class option on typecheck checker allowing to skip members + checking based on class name (patch provided by Thomas W Barr) + + +2007-06-07 -- 0.13.2 + * fix disable-checker option so that it won't accidentally enable the + rpython checker which is disabled by default + * added note about the gedit plugin into documentation + +2007-03-02 -- 0.13.1 + * fix some unexplained 0.13.0 packaging issue which led to a bunch of + files missing from the distribution + +2007-02-28 -- 0.13.0 + * new RPython (Restricted Python) checker for PyPy felow or people + wanting to get a compiled version of their python program using the + translator of the PyPy project. For more information about PyPy or + RPython, visit http://codespeak.net/pypy/ + * new E0104 and E0105 messages introduced to respectivly warn about + "return" and "yield" outside function or method + * new E0106 message when "yield" and "return something" are mixed in a + function or method + * new W0107 message for unnecessary pass statement + * new W0614 message to differentiate between unused `import X` and + unused `from X import *` (#3209, patch submitted by Daniel Drake) + * included Daniel Drake's patch to have a different message E1003 instead of + E1001 when a missing member is found but an inference failure has been + detected + * msvs reporter for Visual Studio line number reporting (#3285) + * allow disable-all option inline (#3218, patch submitted by Daniel Drake) + * --init-hook option to call arbitray code necessary to set + environment (eg sys.path) (#3156) + * One more Daniel's patch fixing a command line option parsing + problem, this'll definitly be the DDrake release :) + * fix #3184: crashes on "return" outside function + * fix #3205: W0704 false positive + * fix #3123: W0212 false positive on static method + * fix #2485: W0222 false positive + * fix #3259: when a message is explicitly enabled, check the checker + emitting it is enabled + + +2006-11-23 -- 0.12.2 + * fix #3143: W0233 bug w/ YES objects + * fix #3119: Off-by-one error counting lines in a file + * fix #3117: ease sys.stdout overriding for reporters + * fix #2508: E0601 false positive with lambda + * fix #3125: E1101 false positive and a message duplication. Only the last part + is actually fixed since the initial false positive is due to dynaming setting of + attributes on the decimal.Context class. + * fix #3149: E0101 false positives and introduced E0100 for generator __init__ + methods + * fixed some format checker false positives + +2006-09-25 -- 0.12.1 + * fixed python >= 2.4 format false positive with multiple lines statement + * fixed some 2.5 issues + * fixed generator expression scope bug (depends on astng 0.16.1) + * stop requiring __revision__ + +2006-08-10 -- 0.12.0 + * usability changes: + + - parseable, html and color options are now handled by a single + output-format option + - enable-<checkerid> and disable-all options are now handled by + two (exclusive) enable-checker and disable-checker options + taking a comma separated list of checker names as value + - renamed debug-mode option to errors-only + + * started a reference user manual + * new W0212 message for access to protected member from client code + (close #14081) + * new W0105 and W0106 messages extracted from W0104 (statement seems + to have no effect) respectivly when the statement is actually string + (that's sometimes used instead of comments for documentation) or an + empty statement generated by a useless semicolumn + * reclassified W0302 to C0302 + * fix so that global messages are not anymore connected to the last + analyzed module (close #10106) + * fix some bugs related to local disabling of messages + * fix cr/lf pb when generating the rc file on windows platforms + + +2006-04-19 -- 0.11.0 + * fix crash caused by the exceptions checker in some case + * fix some E1101 false positive with abstract method or classes defining + __getattr__ + * dirty fix to avoid "_socketobject" has not "connect" member. The actual + problem is that astng isn't able to understand the code used to create + socket.socket object with exec + * added an option in the similarity checker to ignore docstrings, enabled + by default + * included patch from Benjamin Niemann to allow block level + enabling/disabling of messages + + +2006-03-06 -- 0.10.0 + * WARNING, this release include some configuration changes (see below), + so you may have to check and update your own configuration file(s) if + you use one + * this release require the 0.15 version of astng or superior (it will save + you a lot of pylint crashes...) + * W0705 has been reclassified to E0701, and is now detecting more + inheriting problem, and a false positive when empty except clause is + following an Exception catch has been fixed (close #10422) + * E0212 and E0214 (metaclass/class method should have mcs/cls as first + argument have been reclassified to C0202 and C0203 since this not as + well established as "self" for instance method (E0213) + * W0224 has been reclassified into F0220 (failed to resolve interfaces + implemented by a class) + * a new typecheck checker, introducing the following checks: + + - E1101, access to unexistant member (implements #10430), remove + the need of E0201 and so some options has been moved from the + classes checker to this one + - E1102, calling a non callable object + - E1111 and W1111 when an assigment is done on a function call but the + infered function returns None (implements #10431) + + * change in the base checker: + + - checks module level and instance attribute names (new const-rgx + and attr-rgx configuration option) (implements #10209 and + #10440) + - list comprehension and generator expression variables have their + own regular expression (the inlinevar-rgx option) (implements + #9146) + - the C0101 check with its min-name-lentgh option has + been removed (this can be specified in the regxp after all...) + - W0103 and W0121 are now handled by the variables checker + (W0103 is now W0603 and W0604 has been splitted into different messages) + - W0131 and W0132 messages have been reclassified to C0111 and + C0112 respectivly + - new W0104 message on statement without effect + + * regexp support for dummy-variables (dummy-variables-rgx option + replace dummy-variables) (implements #10027) + * better global statement handling, see W0602, W0603, W0604 messages + (implements #10344 and #10236) + * --debug-mode option, disabling all checkers without error message + and filtering others to only display error + * fixed some R0201 (method could be a function) false positive + + +2006-01-10 -- 0.9.0 + * a lot of updates to follow astng 0.14 API changes, so install + logilab-astng 0.14 or greater before using this version of pylint + * checker number 10 ! newstyle will search for problems regarding old + style / new style classes usage problems (rely on astng 0.14 new + style detection feature) + * new 'load-plugins' options to load additional pylint plugins (usable + from the command line or from a configuration file) (implements + #10031) + * check if a "pylintrc" file exists in the current working directory + before using the one specified in the PYLINTRC environment variable + or the default ~/.pylintrc or /etc/pylintrc + * fixed W0706 (Identifier used to raise an exception is assigned...) + false positive and reraising a catched exception instance + * fixed E0611 (No name get in module blabla) false positive when accessing + to a class'__dict__ + * fixed some E0203 ("access to member before its definition") false + positive + * fixed E0214 ("metaclass method frist argument should be mcs) false + positive with staticmethod used on a metaclass + * fixed packaging which was missing the test/regrtest_data directory + * W0212 (method could be a function) has been reclassified in the + REFACTOR category as R0201, and is no more considerer when a method + overrides an abstract method from an ancestor class + * include module name in W0401 (wildcard import), as suggested by + Amaury + * when using the '--parseable', path are written relative to the + current working directory if in a sub-directory of it (#9789) + * 'pylint --version' shows logilab-astng and logilab-common versions + * fixed pylint.el to handle space in file names + * misc lint style fixes + + +2005-11-07 -- 0.8.1 + * fix "deprecated module" false positive when the code imports a + module whose name starts with a deprecated module's name (close + #10061) + * fix "module has no name __dict__" false positive (close #10039) + * fix "access to undefined variable __path__" false positive (close + #10065) + * fix "explicit return in __init__" false positive when return is + actually in an inner function (close #10075) + +2005-10-21 -- 0.8.0 + * check names imported from a module exists in the module (E0611), + patch contributed by Amaury Forgeot d'Arc + * print a warning (W0212) for methods that could be a function + (implements #9100) + * new --defining-attr-methods option on classes checker + * new --acquired-members option on the classes checker, used when + --zope=yes to avoid false positive on acquired attributes (listed + using this new option) (close #8616) + * generate one E0602 for each use of an undefined variable + (previously, only one for the first use but not for the following) + (implements #1000) + * make profile option saveable + * fix Windows .bat file, patch contributed by Amaury Forgeot d'Arc + * fix one more false positive for E0601 (access before definition) + with for loop such as "for i in range(10): print i" (test + func_noerror_defined_and_used_on_same_line) + * fix false positive for E0201 (undefined member) when accessing to + __name__ on a class object + * fix astng checkers traversal order + * fix bug in format checker when parsing a file from a platform + using different new line characters (close #9239) + * fix encoding detection regexp + * fix --rcfile handling (support for --rcfile=file, close #9590) + + +2005-05-27 -- 0.7.0 + * WARNING: pylint is no longer a logilab subpackage. Users may have to + manually remove the old logilab/pylint directory. + * introduce a new --additional-builtins option to handle user defined + builtins + * --reports option has now -r as short alias, and -i for --include-ids + * fix a bug in the variables checker which may causing some false + positives when variables are defined and used within the same + statement (test func_noerror_defined_and_used_on_same_line) + * this time, real fix of the "disable-msg in the config file" problem, + test added to unittest_lint + * fix bug with --list-messages and python -OO + * fix possible false positive for W0201 + + +2005-04-14 -- 0.6.4 + * allow to parse files without extension when a path is given on the + command line (test noext) + * don't fail if we are unable to read an inline option (e.g. inside a + module), just produce an information message (test func_i0010) + * new message E0103 for break or continue outside loop (close #8883, + test func_continue_not_in_loop) + * fix bug in the variables checker, causing non detection of some + actual name error (close #8884, test + func_nameerror_on_string_substitution) + * fix bug in the classes checker which was making pylint crash if + "object" is assigned in a class inheriting from it (test + func_noerror_object_as_class_attribute) + * fix problem with the similar checker when related options are + defined in a configuration file + * new --generate-man option to generate pylint's man page (require the + latest logilab.common (>= 0.9.3) + * packaged (generated...) man page + +2005-02-24 -- 0.6.3 + * fix scope problem which may cause false positive and true negative + on E0602 + * fix problem with some options such as disable-msg causing error when + they are coming from the configuration file + +2005-02-16 -- 0.6.2 + * fix false positive on E0201 ("access to undefined member") with + metaclasses + * fix false positive on E0203 ("access to member before its + definition") when attributes are defined in a parent class + * fix false positive on W0706 ("identifier used to raise an exception + assigned to...") + * fix interpretation of "\t" as value for the indent-string + configuration variable + * fix --rcfile so that --rcfile=pylintrc (only --rcfile pylintrc was + working in earlier release) + * new raw checker example in the examples/ directory + +2005-02-04 -- 0.6.1 + * new --rcfile option to specify the configuration file without the + PYLINTRC environment variable + * added an example module for a custom pylint checker (see the + example/ directory) + * some fixes to handle fixes in common 0.9.1 (should however still working + with common 0.9.0, even if upgrade is recommended) + +2005-01-20 -- 0.6.0 + * refix pylint emacs mode + * no more traceback when just typing "pylint" + * fix a bug which may cause crashes on resolving parent classes + * fix problems with the format checker: don't chock on files + containing multiple CR, avoid C0322, C0323, C0324 false positives + with triple quoted string with quote inside + * correctly detect access to member defined latter in __init__ method + * now depends on common 0.8.1 to fix problem with interface resolution + (close #8606) + * new --list-msgs option describing available checkers and their + messages + * added windows specific documentation to the README file, contributed + by Brian van den Broek + * updated doc/features.txt (actually this file is now generated using + the --list-msgs option), more entries into the FAQ + * improved tests coverage + + +2004-10-19 -- 0.5.0 + * avoid to import analyzed modules ! + * new Refactor and Convention message categories. Some Warnings have been + remaped into those new categories + * added "similar", a tool to find copied and pasted lines of code, + both using a specific command line tool and integrated as a + pylint's checker + * imports checker may report import dependancies as a dot graph + * new checker regrouping most Refactor detection (with some new metrics) + * more command line options storable in the configuration file + * fix bug with total / undocumented number of methods + + +2004-07-08 -- 0.4.2 + * fix pylint emacs mode + * fix classes checkers to handler twisted interfaces + +2004-05-14 -- 0.4.1 + * fix the setup.py script to allow bdist_winst (well, the generated + installer has not been tested...) with the necessary + logilab/__init__.py file + * fix file naming convention as suggested by Andreas Amoroso + * fix stupid crash bug with bad method names + +2004-05-10 -- 0.4.0 + * fix file path with --parsable + * --parsable option has been renamed to --parseable + * added patch from Andreas Amoroso to output message to files instead + of standard output + * added Run to the list of correct variable names + * fix variable names regexp and checking of local classes names + * some basic handling of metaclasses + * no-docstring-rgx apply now on classes too + * new option to specify a different regexp for methods than for + functions + * do not display the evaluation report when no statements has been + analysed + * fixed crash with a class nested in a method + * fixed format checker to deals with triple quoted string and + lines with code and comment mixed + * use logilab.common.ureports to layout reports + + +2004-02-17 -- 0.3.3 + * added a parsable text output, used when the --parsable option is + provided + * added an emacs mode using this output, availabe in the distrib's + elisp directory + * fixed some typos in messages + * change include-ids options to yn, and allow it to be in the + configuration file + * do not chock on corrupted stats files + * fixed bug in the format checker which may stop pylint execution + * provide scripts for unix and windows to wrap the minimal pylint tk + gui + +2003-12-23 -- 0.3.2 + * html-escape messages in the HTML reporter (bug reported by Juergen + Hermann) + * added "TODO" to the list of default note tags + * added "rexec" to the list of default deprecated modules + * fixed typos in some messages + +2003-12-05 -- 0.3.1 + * bug fix in format and classes checkers + * remove print statement from imports checkers + * provide a simple tk gui, essentially usefull for windows users + +2003-11-20 -- 0.3.0 + * new exceptions checker, checking for string exception and empty + except clauses. + * imports checker checks for reimport of modules + * classes checker checks for calls to ancestor's __init__ and abstract + method not overriden. It doesn't complain anymore for unused import in + __init__ files, and provides a new option ignore-interface-methods, + usefull when you're using zope Interface implementation in your project + * base checker checks for black listed builtins call (controled by the + bad-functions option) and for use of * and ** + * format checker checks for use of <> and "l" as long int marker + * major internal API changes + * use the rewrite of astng, based on compiler.ast + * added unique id for messages, as suggested by Wolfgang Grafen + * added unique id for reports + * can take multiple modules or files as argument + * new options command line options : --disable-msg, --enable-msg, + --help-msg, --include-ids, --reports, --disable-report, --cache-size + * --version shows the version of the python interpreter + * removed some options which are now replaced by [en|dis]able-msg, or + disable-report + * read disable-msg and enable-msg options in source files (should be + in comments on the top of the file, in the form + "# pylint: disable-msg=W0402" + * new message for modules importing themselves instead of the "cyclic + import" message + * fix bug with relative and cyclic imports + * fix bug in imports checker (cycle was not always detected) + * still fixes in format checker : don't check comment and docstring, + check first line after an indent + * black and white list now apply to all identifiers, not only + variables, so changed the configuration option from + (good|bad)-variable-names to (good|bad)-names + * added string, rexec and Bastion to the default list of deprecated + modules + * do not print redefinition warning for function/class/method defined + in mutually exclusive branchs + + +2003-10-10 -- 0.2.1 + * added some documentation, fixed some typos + * set environment variable PYLINT_IMPORT to 1 during pylint execution. + * check that variables "imported" using the global statement exist + * indentation problems are now warning instead of errors + * fix checkers.initialize to try to load all files with a known python + extension (patch from wrobell) + * fix a bunch of messages + * fix sample configuration file + * fix the bad-construction option + * fix encoding checker + * fix format checker + +2003-09-12 -- 0.2.0 + * new source encoding / FIXME checker (pep 263) + * new --zope option which trigger Zope import. Usefull to check Zope + products code. + * new --comment option which enable the evaluation note comment + (disabled by default). + * a ton of bug fixes + * easy functionnal test infrastructure + + +2003-06-18 -- 0.1.2 + * bug fix release + * remove dependency to pyreverse + +2003-06-01 -- 0.1.1 + * much more functionnalities ! + +2003-05-19 -- 0.1 + * initial release @@ -0,0 +1,3 @@ +python-logilab-common (>= 0.19.0) +python-logilab-astng (>= 0.16.1) +python-tk diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..dde44eb --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,21 @@ +include COPYING +include DEPENDS +include ChangeLog +include TODO +include bin/symilar +include bin/pylint +include bin/*.bat +include bin/pylint-gui +include bin/epylint +include examples/pylintrc* +include examples/*.py +include elisp/*.el +include elisp/startup +include man/pylint.1 +recursive-include doc *.txt *.html +recursive-include test/input *.py similar* noext +recursive-include test/messages *.txt *.txt2 +recursive-include test/rpythoninput *.py +recursive-include test/rpythonmessages *.txt +recursive-include test/regrtest_data *.py +include test/fulltest.sh @@ -0,0 +1,59 @@ +README for PyLint +================= + +Dependencies +------------ +Pylint requires the logilab-astng (version >= 0.14), logilab-common +(version >= 0.13) and the optik (only for python < 2.3) packages. +Pylint should be compatible with any python >= 2.2. + +* http://www.logilab.org/projects/astng +* http://www.logilab.org/projects/common +* http://optik.sourceforge.net/ + + +Install +------- +From the source distribution, extract the tarball and run :: + + python setup.py install + +You'll have to install dependancies 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. + + +Comments, support, bug reports +------------------------------ +Use the python-projects@logilab.org mailing list. Since we do not have +publicly available bug tracker yet, bug reports should be emailed +there too. + +You can subscribe to this mailing list at +http://www.logilab.org/mailinglists/python_projects/mailinglist_register_form + +Archives are available at +http://lists.logilab.org/pipermail/python-projects/ + + +Contributors +------------ +* Sylvain Thénault: main author / maintainer +* Alexandre Fayolle: TkInter gui, documentation, debian support +* Brian van den Broek: windows installation documentation +* Amaury Forgeot d'Arc: patch to check names imported from a module + exists in the module +* Benjamin Niemann: patch to allow block level enabling/disabling of messages +* Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, + Maarten ter Huurne, Mirko Friedenhagen (among others): + bug reports, feedback, feature requests... +* All the Logilab's team: daily use, bug reports, feature requests +* Other people have contributed by their feedback, if I've forgotten + you, send me a note ! @@ -0,0 +1,46 @@ +PyLint's TODO list +------------------ + + +- un rapport avec les métriques vues dans TDD +- métrique manquantes +- tests + +* faire tourner sur wxpython... + +* test external dependancies + + +* avoir les options liés à un message id dans son aide + +* avoir les messages id géré par un checker dans --help + +* avoir la valeur courante des options dans --help + +* doc développeur + +* supporter des wildcards dans disable-msg ? + +* voir notes gvr sur main + +* récupérer les phrases d'évaluation dans un fichier texte + +* i18n avec gettext + +* commenter les regexp de format.py + +* gestion nested_scopes (modes py2.1, 2.2... ?) + +* checkers : + - vérifier arguments __new__ + - compléter format checker + voir http://www.python.org/peps/pep-0008.html + - vérifier classes sans __init__ mais avec plusieurs ancêtres ayant + un __init__ + - opérateur % avec des formats ne correspondant pas aux arguments + - mauvais nombre d'arguments passés à une méthode ou fonction + - utilisation constante dans condition + - gestion del statements + - vérification utilisation __getattribute__, __slots__ dans new + style class seulement + - vérification assignements quand __slots__ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0c4bd13 --- /dev/null +++ b/__init__.py @@ -0,0 +1,16 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2002-2008 LOGILAB S.A. (Paris, FRANCE). +http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + diff --git a/__pkginfo__.py b/__pkginfo__.py new file mode 100644 index 0000000..4330f3d --- /dev/null +++ b/__pkginfo__.py @@ -0,0 +1,73 @@ +# pylint: disable-msg=W0622,C0103 +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""pylint packaging information""" + +modname = 'pylint' + +numversion = (0, 14, 0) +version = '.'.join([str(num) for num in numversion]) + +license = 'GPL' +copyright = '''Copyright (c) 2003-2008 Sylvain Thenault (thenault@gmail.com). +Copyright (c) 2003-2008 LOGILAB S.A. (Paris, FRANCE). +http://www.logilab.fr/ -- mailto:contact@logilab.fr''' + +short_desc = "python code static checker" +long_desc = """\ + Pylint is a Python source code analyzer which looks for programming + errors, helps enforcing a coding standard and sniffs for some code + smells (as defined in Martin Fowler's Refactoring book) + . + Pylint can be seen as another PyChecker since nearly all tests you + can do with PyChecker can also be done with Pylint. However, Pylint + offers some more features, like checking length of lines of code, + checking if variable names are well-formed according to your coding + standard, or checking if declared interfaces are truly implemented, + and much more. + . + Additionally, it is possible to write plugins to add your own checks.""" + +author = "Sylvain Thenault" +author_email = "sylvain.thenault@logilab.fr" + +web = "http://www.logilab.org/project/name/%s" % modname +ftp = "ftp://ftp.logilab.org/pub/%s" % modname +mailinglist = "mailto://python-projects@logilab.org" + +from os.path import join +scripts = [join('bin', filename) + for filename in ('pylint', 'pylint-gui', "symilar", "epylint")] + +include_dirs = [join('test', 'input'), + join('test', 'messages'), + join('test', 'rpythonmessages'), + join('test', 'regrtest_data')] + +pyversions = ["2.3", "2.4", "2.5"] + +debian_uploader = 'Alexandre Fayolle <afayolle@debian.org>' + +classifiers = ['Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Debuggers', + 'Topic :: Software Development :: Quality Assurance', + 'Topic :: Software Development :: Testing', + ] diff --git a/announce.txt b/announce.txt new file mode 100644 index 0000000..769f8a3 --- /dev/null +++ b/announce.txt @@ -0,0 +1,42 @@ +What's new ? +------------ +%(CHANGELOG)s + + +What is pylint ? +---------------- + +Pylint is a python tool that checks if a module satisfy a coding +standard. Pylint can be seen as another pychecker since nearly all +tests you can do with pychecker can also be done with Pylint. But +Pylint offers some more features, like checking line-code's length, +checking if variable names are well-formed according to your coding +standard, or checking if declared interfaces are truly implemented, +and much more (see http://www.logilab.org/projects/pylint/ for the +complete check list). The big advantage with Pylint is that it is +highly configurable, customizable, and you can easily write a small +plugin to add a personal feature. + +The usage it quite simple : + +$ pylint mypackage.mymodule + + +This command will output all the errors and warnings related to the +tested code (here : mypackage.mymodule), will dump a little summary at +the end, and will give a mark to the tested code. + +Pylint is free software distributed under the GNU Public Licence. + + +Home page +--------- +%(WEB)s + +Download +-------- +%(FTP)s + +Mailing list +------------ +%(MAILINGLIST)s diff --git a/bin/epylint b/bin/epylint new file mode 100755 index 0000000..b437e8a --- /dev/null +++ b/bin/epylint @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +import re +import sys + +from subprocess import * + +p = Popen("pylint -f parseable -r n --disable-msg-cat=C,R %s" % + sys.argv[1], shell = True, stdout = PIPE).stdout + +for line in p: + match = re.search("\\[([WE])(, (.+?))?\\]", line) + if match: + kind = match.group(1) + func = match.group(3) + + if kind == "W": + msg = "Warning" + else: + msg = "Error" + + if func: + line = re.sub("\\[([WE])(, (.+?))?\\]", + "%s (%s):" % (msg, func), line) + else: + line = re.sub("\\[([WE])?\\]", "%s:" % msg, line) + print line, + +p.close() diff --git a/bin/pylint b/bin/pylint new file mode 100755 index 0000000..e20e031 --- /dev/null +++ b/bin/pylint @@ -0,0 +1,4 @@ +#!/usr/bin/env python +import sys +from pylint import lint +lint.Run(sys.argv[1:]) diff --git a/bin/pylint-gui b/bin/pylint-gui new file mode 100755 index 0000000..025378f --- /dev/null +++ b/bin/pylint-gui @@ -0,0 +1,7 @@ +#!/usr/bin/env python +import sys +try: + from pylint import gui + gui.Run(sys.argv[1:]) +except ImportError: + sys.exit('tkinter is not available') diff --git a/bin/pylint-gui.bat b/bin/pylint-gui.bat new file mode 100644 index 0000000..68d552e --- /dev/null +++ b/bin/pylint-gui.bat @@ -0,0 +1,20 @@ +@echo off
+rem = """-*-Python-*- script
+@echo off
+rem -------------------- DOS section --------------------
+rem You could set PYTHONPATH or TK environment variables here
+python -x %~f0 %*
+goto exit
+
+"""
+# -------------------- Python section --------------------
+import sys
+from pylint import gui
+gui.Run(sys.argv[1:])
+
+
+DosExitLabel = """
+:exit
+rem """
+
+
diff --git a/bin/pylint.bat b/bin/pylint.bat new file mode 100644 index 0000000..772735a --- /dev/null +++ b/bin/pylint.bat @@ -0,0 +1,19 @@ +@echo off
+rem = """-*-Python-*- script
+rem -------------------- DOS section --------------------
+rem You could set PYTHONPATH or TK environment variables here
+python -x %~f0 %*
+goto exit
+
+"""
+# -------------------- Python section --------------------
+import sys
+from pylint import lint
+lint.Run(sys.argv[1:])
+
+
+DosExitLabel = """
+:exit
+rem """
+
+
diff --git a/bin/symilar b/bin/symilar new file mode 100755 index 0000000..7ca139f --- /dev/null +++ b/bin/symilar @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from pylint.checkers import similar +similar.run() diff --git a/bin/symilar.bat b/bin/symilar.bat new file mode 100644 index 0000000..5c9bd0e --- /dev/null +++ b/bin/symilar.bat @@ -0,0 +1,20 @@ +@echo off
+rem = """-*-Python-*- script
+@echo off
+rem -------------------- DOS section --------------------
+rem You could set PYTHONPATH or TK environment variables here
+python -x %~f0 %*
+goto exit
+
+"""
+# -------------------- Python section --------------------
+import sys
+from pylint.checkers import similar
+similar.run()
+
+
+DosExitLabel = """
+:exit
+rem """
+
+
diff --git a/checkers/__init__.py b/checkers/__init__.py new file mode 100644 index 0000000..57c1ba1 --- /dev/null +++ b/checkers/__init__.py @@ -0,0 +1,164 @@ +# Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""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: rpython + +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 tokenize +from os import listdir +from os.path import dirname, join, isdir, splitext + +from logilab.astng.utils import ASTWalker +from logilab.common.configuration import OptionsProviderMixIn + +from pylint.reporters import diff_string, EmptyReport + +def table_lines_from_stats(stats, old_stats, columns): + """get values listed in <columns> from <stats> and <old_stats>, + and return a formated list of values, designed to be given to a + ureport.Table object + """ + lines = [] + for m_type in columns: + new = stats[m_type] + format = str + if isinstance(new, float): + format = lambda num: '%.3f' % num + old = old_stats.get(m_type) + if old is not None: + diff_str = diff_string(old, new) + old = format(old) + else: + old, diff_str = 'NC', 'NC' + lines += (m_type.replace('_', ' '), format(new), old, diff_str) + return lines + + +class BaseChecker(OptionsProviderMixIn, ASTWalker): + """base class for checkers""" + + options = () + priority = -9 + enabled = True + may_be_disabled = True + name = None + + def __init__(self, linter=None): + """checker instances should have the linter as argument + + linter is an object implementing ILinter + """ + ASTWalker.__init__(self, self) + self.name = self.name.lower() + OptionsProviderMixIn.__init__(self) + self.linter = linter + + def add_message(self, msg_id, line=None, node=None, args=None): + """add a message of a given type""" + self.linter.add_message(msg_id, line, node, args) + + def is_enabled(self): + """return true if the checker is enabled""" + return self.enabled + + def enable(self, enabled): + """enable / disable this checker if true / false is given + + it false values has no effect if the checker can't be disabled + """ + if not enabled and not self.may_be_disabled: + raise Exception("can't disable %s checker" % self.name) + if enabled or self.may_be_disabled: + self.enabled = enabled + + def package_dir(self): + """return the base directory for the analysed package""" + return dirname(self.linter.base_file) + + + # dummy methods implementing the IChecker interface + + def open(self): + """called before visiting project (i.e set of modules)""" + + def close(self): + """called after visiting project (i.e set of modules)""" + +class BaseRawChecker(BaseChecker): + """base class for raw checkers""" + + def process_module(self, stream): + """process a module + + the module's content is accessible via the stream object + + stream must implements the readline method + """ + self.process_tokens(tokenize.generate_tokens(stream.readline)) + + def process_tokens(self, tokens): + """should be overiden by subclasses""" + raise NotImplementedError() + + +PY_EXTS = ('.py', '.pyc', '.pyo', '.pyw', '.so', '.dll') + +def initialize(linter): + """initialize linter with checkers in this package """ + package_load(linter, __path__[0]) + +def package_load(linter, directory): + """load all module and package in the given directory, looking for a + 'register' function in each one, used to register pylint checkers + """ + globs = globals() + imported = {} + for filename in listdir(directory): + basename, extension = splitext(filename) + if not imported.has_key(basename) and ( + (extension in PY_EXTS and basename != '__init__') or ( + not extension and not basename == 'CVS' and + isdir(join(directory, basename)))): + try: + module = __import__(basename, globs, globs, None) + except ValueError: + # empty module name (usually emacs auto-save files) + continue + except ImportError: + import sys + print >> sys.stderr, "Problem importing module: %s" % filename + else: + if hasattr(module, 'register'): + module.register(linter) + imported[basename] = 1 + +__all__ = ('CheckerHandler', 'BaseChecker', 'initialize', 'package_load') diff --git a/checkers/base.py b/checkers/base.py new file mode 100644 index 0000000..d2fc9f6 --- /dev/null +++ b/checkers/base.py @@ -0,0 +1,556 @@ +# Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""basic checker for Python code +""" + +from logilab import astng +from logilab.common.ureports import Table + +from pylint.interfaces import IASTNGChecker +from pylint.reporters import diff_string +from pylint.checkers import BaseChecker +from pylint.checkers.utils import are_exclusive + +import re + +# regex for class/function/variable/constant nane +CLASS_NAME_RGX = re.compile('[A-Z_][a-zA-Z0-9]+$') +MOD_NAME_RGX = re.compile('(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$') +CONST_NAME_RGX = re.compile('(([A-Z_][A-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}$') +# do not require a doc string on system methods +NO_REQUIRED_DOC_RGX = re.compile('__.*__') + +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, (astng.For, astng.ListComp, astng.GenExpr)): + return True + parent = parent.parent + return False + +def in_nested_list(nested_list, obj): + """return true if the object is an element of <nested_list> or of a nested + list + """ + for elmt in nested_list: + if isinstance(elmt, (list, tuple)): + if in_nested_list(elmt, obj): + return True + elif elmt == obj: + return True + return False + +def report_by_type_stats(sect, stats, old_stats): + """make a report of + + * percentage of different types documented + * percentage of different types with a bad name + """ + # percentage of different types documented and/or with a bad name + nice_stats = {} + for node_type in ('module', 'class', 'method', 'function'): + nice_stats[node_type] = {} + total = stats[node_type] + if total == 0: + doc_percent = 0 + badname_percent = 0 + else: + documented = total - stats['undocumented_'+node_type] + doc_percent = float((documented)*100) / total + badname_percent = (float((stats['badname_'+node_type])*100) + / total) + nice_stats[node_type]['percent_documented'] = doc_percent + nice_stats[node_type]['percent_badname'] = badname_percent + lines = ('type', 'number', 'old number', 'difference', + '%documented', '%badname') + for node_type in ('module', 'class', 'method', 'function'): + new = stats[node_type] + old = old_stats.get(node_type, None) + if old is not None: + diff_str = diff_string(old, new) + else: + old, diff_str = 'NC', 'NC' + lines += (node_type, str(new), str(old), diff_str, + '%.2f' % nice_stats[node_type]['percent_documented'], + '%.2f' % nice_stats[node_type]['percent_badname']) + sect.append(Table(children=lines, cols=6, rheaders=1)) + + +MSGS = { + 'E0100': ('__init__ method is a generator', + 'Used when the special class method __init__ is turned into a ' + 'generator by a yield in its body.'), + 'E0101': ('Explicit return in __init__', + 'Used when the special class method __init__ has an explicit \ + return value.'), + 'E0102': ('%s already defined line %s', + 'Used when a function / class / method is redefined.'), + 'E0103': ('%r not properly in loop', + 'Used when break or continue keywords are used outside a loop.'), + + 'E0104': ('Return outside function', + 'Used when a "return" statement is found outside a function or ' + 'method.'), + 'E0105': ('Yield outside function', + 'Used when a "yield" statement is found outside a function or ' + 'method.'), + 'E0106': ('Return with argument inside generator', + 'Used when a "return" statement with an argument is found ' + 'outside in a generator function or method (e.g. with some ' + '"yield" statements).'), + + 'W0101': ('Unreachable code', + 'Used when there is some code behind a "return" or "raise" \ + statement, which will never be accessed.'), + 'W0102': ('Dangerous default value %s as argument', + 'Used when a mutable value as list or dictionary is detected in \ + a default value for an argument.'), + 'W0104': ('Statement seems to have no effect', + 'Used when a statement doesn\'t have (or at least seems to) \ + any effect.'), + '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': ('Unnecessary semicolon', + 'Used when a statement is endend by a semi-colon (";"), which \ + isn\'t necessary (that\'s python, not C ;).'), + 'W0107': ('Unnecessary pass statement', + 'Used when a "pass" statement that can be avoided is ' + 'encountered.)'), + + 'W0122': ('Use of the exec statement', + 'Used when you use the "exec" statement, to discourage its \ + usage. That doesn\'t mean you can not use it !'), + + 'W0141': ('Used builtin function %r', + 'Used when a black listed builtin function is used (see the ' + 'bad-function option). Usual black listed functions are the ones ' + 'like map, or filter , where Python offers now some cleaner ' + 'alternative like list comprehension.'), + 'W0142': ('Used * or ** magic', + 'Used when a function or method is called using `*args` or ' + '`**kwargs` to dispatch arguments. This doesn\'t improve ' + 'readability and should be used with care.'), + + 'C0102': ('Black listed name "%s"', + 'Used when the name is listed in the black list (unauthorized \ + names).'), + 'C0103': ('Invalid name "%s" (should match %s)', + 'Used when the name doesn\'t match the regular expression \ + associated to its type (constant, variable, class...).'), + + 'C0111': ('Missing docstring', # W0131 + 'Used when a module, function, class or method has no docstring.\ + Some special methods like __init__ doesn\'t necessary require a \ + docstring.'), + 'C0112': ('Empty docstring', # W0132 + 'Used when a module, function, class or method has an empty \ + docstring (it would be too easy ;).'), + + 'C0121': ('Missing required attribute "%s"', # W0103 + 'Used when an attribute required for modules is missing.'), + + } + +class BasicChecker(BaseChecker): + """checks for : + * doc strings + * modules / classes / functions / methods / arguments / variables name + * number of arguments, local variables, branchs, returns and statements in +functions, methods + * required module attributes + * dangerous default values as arguments + * redefinition of function / method / class + * uses of the global statement + """ + + __implements__ = IASTNGChecker + + name = 'basic' + msgs = MSGS + priority = -1 + options = (('required-attributes', + {'default' : (), 'type' : 'csv', + 'metavar' : '<attributes>', + 'help' : 'Required attributes for module, separated by a ' + 'comma'} + ), + ('no-docstring-rgx', + {'default' : NO_REQUIRED_DOC_RGX, + 'type' : 'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match ' + 'functions or classes name which do not require a ' + 'docstring'} + ), +## ('min-name-length', +## {'default' : 3, 'type' : 'int', 'metavar' : '<int>', +## 'help': 'Minimal length for module / class / function / ' +## 'method / argument / variable names'} +## ), + ('module-rgx', + {'default' : MOD_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'module names'} + ), + ('const-rgx', + {'default' : CONST_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'module level names'} + ), + ('class-rgx', + {'default' : CLASS_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'class names'} + ), + ('function-rgx', + {'default' : DEFAULT_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'function names'} + ), + ('method-rgx', + {'default' : DEFAULT_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'method names'} + ), + ('attr-rgx', + {'default' : DEFAULT_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'instance attribute names'} + ), + ('argument-rgx', + {'default' : DEFAULT_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'argument names'}), + ('variable-rgx', + {'default' : DEFAULT_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'variable names'} + ), + ('inlinevar-rgx', + {'default' : COMP_VAR_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'list comprehension / generator expression variable \ + names'} + ), + ('good-names', + {'default' : ('i', 'j', 'k', 'ex', 'Run', '_'), + 'type' :'csv', 'metavar' : '<names>', + 'help' : 'Good variable names which should always be accepted,' + ' separated by a comma'} + ), + ('bad-names', + {'default' : ('foo', 'bar', 'baz', 'toto', 'tutu', 'tata'), + 'type' :'csv', 'metavar' : '<names>', + 'help' : 'Bad variable names which should always be refused, ' + 'separated by a comma'} + ), + + ('bad-functions', + {'default' : ('map', 'filter', 'apply', 'input'), + 'type' :'csv', 'metavar' : '<builtin function names>', + 'help' : 'List of builtins function names that should not be ' + 'used, separated by a comma'} + ), + ) + reports = ( ('R0101', 'Statistics by type', report_by_type_stats), ) + + def __init__(self, linter): + BaseChecker.__init__(self, linter) + self.stats = None + self._returns = None + + def open(self): + """initialize visit variables and statistics + """ + self._returns = [] + self.stats = self.linter.add_stats(module=0, function=0, + method=0, class_=0, + badname_module=0, + badname_class=0, badname_function=0, + badname_method=0, badname_attr=0, + badname_const=0, + badname_variable=0, + badname_inlinevar=0, + badname_argument=0, + undocumented_module=0, + undocumented_function=0, + undocumented_method=0, + undocumented_class=0) + + def visit_module(self, node): + """check module name, docstring and required arguments + """ + self.stats['module'] += 1 + self._check_name('module', node.name.split('.')[-1], node) + self._check_docstring('module', node) + self._check_required_attributes(node, self.config.required_attributes) + + def visit_class(self, node): + """check module name, docstring and redefinition + increment branch counter + """ + self.stats['class'] += 1 + self._check_name('class', node.name, node) + if self.config.no_docstring_rgx.match(node.name) is None: + self._check_docstring('class', node) + self._check_redefinition('class', node) + for attr, anodes in node.instance_attrs.items(): + self._check_name('attr', attr, anodes[0]) + + def visit_discard(self, node): + """check for various kind of statements without effect""" + expr = node.expr + if isinstance(node.expr, astng.Const): + # XXX lineno maybe dynamically set incidently + if expr.value is None and expr.lineno is None: + # const None node with lineno to None are inserted + # on unnecessary semi-column + # XXX navigate to get a correct lineno + brothers = list(node.parent.getChildNodes()) + previoussibling = brothers[brothers.index(node)-1] + self.add_message('W0106', node=previoussibling) + return + if isinstance(expr.value, basestring): + # tread string statement in a separated message + self.add_message('W0105', node=node) + return + # ignore if this is a function call (can't predicate side effects) + # or a yield (which are wrapped by a discard node in py >= 2.5) + if not isinstance(node.expr, (astng.CallFunc, astng.Yield)): + self.add_message('W0104', node=node) + + def visit_pass(self, node): + """check is the pass statement is really necessary + """ + # if self._returns is empty, we're outside a function ! + if len(node.parent.getChildNodes()) > 1: + self.add_message('W0107', node=node) + + def visit_function(self, node): + """check function name, docstring, arguments, redefinition, + variable names, max locals + """ + is_method = node.is_method() + self._returns.append([]) + f_type = is_method and 'method' or 'function' + self.stats[f_type] += 1 + # function name + self._check_name(f_type, node.name, node) + # docstring + if self.config.no_docstring_rgx.match(node.name) is None: + self._check_docstring(f_type, node) + # check default arguments'value + self._check_defaults(node) + # check arguments name + args = node.argnames + if args is not None: + self._recursive_check_names(args, node) + # check for redefinition + self._check_redefinition(is_method and 'method' or 'function', node) + + def leave_function(self, node): + """most of the work is done here on close: + checks for max returns, branch, return in __init__ + """ + returns = self._returns.pop() + if node.is_method() and node.name == '__init__': + if node.is_generator(): + self.add_message('E0100', node=node) + else: + values = [r.value for r in returns] + if [v for v in values if not ( + (isinstance(v, astng.Const) and v.value is None) + or (isinstance(v, astng.Name) and v.name == 'None'))]: + self.add_message('E0101', node=node) + elif node.is_generator(): + # make sure we don't mix non-None returns and yields + for retnode in returns: + if isinstance(retnode, astng.Return) and \ + isinstance(retnode.value, astng.Const) and \ + retnode.value.value is not None: + self.add_message('E0106', node=node, + line=retnode.fromlineno) + + def visit_assname(self, node): + """check module level assigned names""" + frame = node.frame() + ass_type = node.ass_type() + if isinstance(ass_type, (astng.ListCompFor, astng.GenExprFor)): + self._check_name('inlinevar', node.name, node) + elif isinstance(frame, astng.Module): + if isinstance(ass_type, astng.Assign) and not in_loop(ass_type): + self._check_name('const', node.name, node) + elif isinstance(frame, astng.Function): + # global introduced variable aren't in the function locals + if node.name in frame: + self._check_name('variable', node.name, node) + + def visit_return(self, node): + """check is the node has a right sibling (if so, that's some unreachable + code) + """ + # if self._returns is empty, we're outside a function ! + if not self._returns: + self.add_message('E0104', node=node) + return + self._returns[-1].append(node) + self._check_unreachable(node) + + def visit_yield(self, node): + """check is the node has a right sibling (if so, that's some unreachable + code) + """ + # if self._returns is empty, we're outside a function ! + if not self._returns: + self.add_message('E0105', node=node) + return + self._returns[-1].append(node) + + def visit_continue(self, node): + """check is the node has a right sibling (if so, that's some unreachable + code) + """ + self._check_unreachable(node) + self._check_in_loop(node, 'continue') + + def visit_break(self, node): + """check is the node has a right sibling (if so, that's some unreachable + code) + """ + self._check_unreachable(node) + self._check_in_loop(node, 'break') + + def visit_raise(self, node): + """check is the node has a right sibling (if so, that's some unreachable + code) + """ + self._check_unreachable(node) + + def visit_exec(self, node): + """just pring a warning on exec statements""" + self.add_message('W0122', node=node) + + def visit_callfunc(self, node): + """visit a CallFunc node -> check if this is not a blacklisted builtin + call and check for * or ** use + """ + if isinstance(node.node, astng.Name): + name = node.node.name + # ignore the name if it's not a builtin (ie not defined in the + # locals nor globals scope) + if not (node.frame().has_key(name) or + node.root().has_key(name)): + if name in self.config.bad_functions: + self.add_message('W0141', node=node, args=name) + if node.star_args or node.dstar_args: + self.add_message('W0142', node=node.node) + + + def _check_unreachable(self, node): + """check unreachable code""" + unreach_stmt = node.next_sibling() + if unreach_stmt is not None: + self.add_message('W0101', node=unreach_stmt) + + def _check_in_loop(self, node, node_name): + """check that a node is inside a for or while loop""" + _node = node.parent + while _node: + if isinstance(_node, (astng.For, astng.While)): + break + _node = _node.parent + else: + self.add_message('E0103', node=node, args=node_name) + + def _check_redefinition(self, redef_type, node): + """check for redefinition of a function / method / class name""" + defined_self = node.parent.frame()[node.name] + if defined_self is not node and not are_exclusive(node, defined_self): + self.add_message('E0102', node=node, + args=(redef_type, defined_self.lineno)) + + def _check_docstring(self, node_type, node): + """check the node has a non empty docstring""" + docstring = node.doc + if docstring is None: + self.stats['undocumented_'+node_type] += 1 + self.add_message('C0111', node=node) + elif not docstring.strip(): + self.stats['undocumented_'+node_type] += 1 + self.add_message('C0112', node=node) + + def _recursive_check_names(self, args, node): + """check names in a possibly recursive list <arg>""" + for arg in args: + if type(arg) is type(''): + self._check_name('argument', arg, node) + else: + self._recursive_check_names(arg, node) + + def _check_name(self, node_type, name, node): + """check for a name using the type's regexp""" + if name in self.config.good_names: + return + if name in self.config.bad_names: + self.stats['badname_' + node_type] += 1 + self.add_message('C0102', node=node, args=name) + return + regexp = getattr(self.config, node_type + '_rgx') + if regexp.match(name) is None: + self.add_message('C0103', node=node, args=(name, regexp.pattern)) + self.stats['badname_' + node_type] += 1 + + + def _check_defaults(self, node): + """check for dangerous default values as arguments""" + for default in node.defaults: + try: + value = default.infer().next() + except astng.InferenceError: + continue + if isinstance(value, (astng.Dict, astng.List)): + if value is default: + msg = default.as_string() + else: + msg = '%s (%s)' % (default.as_string(), value.as_string()) + self.add_message('W0102', node=node, args=(msg,)) + + def _check_required_attributes(self, node, attributes): + """check for required attributes""" + for attr in attributes: + if not node.has_key(attr): + self.add_message('C0121', node=node, args=attr) + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(BasicChecker(linter)) + diff --git a/checkers/classes.py b/checkers/classes.py new file mode 100644 index 0000000..a32c89c --- /dev/null +++ b/checkers/classes.py @@ -0,0 +1,512 @@ +# Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""classes checker for Python code +""" +from __future__ import generators + +from logilab.common.compat import set +from logilab import astng + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker +from pylint.checkers.utils import overrides_a_method + +MSGS = { + 'F0202': ('Unable to check methods signature (%s / %s)', + 'Used when PyLint has been unable to check methods signature \ + compatibility for an unexpected raison. Please report this kind \ + if you don\'t make sense of it.'), + + 'E0202': ('An attribute inherited from %s hide this method', + 'Used when a class defines a method which is hiden by an \ + instance attribute from an ancestor class.'), + 'E0203': ('Access to member %r before its definition line %s', + 'Used when an instance member is accessed before it\'s actually\ + assigned.'), + 'W0201': ('Attribute %r defined outside __init__', + 'Used when an instance attribute is defined outside the __init__\ + method.'), + + 'W0212': ('Access to a protected member %s of a client class', # E0214 + 'Used when a protected member (i.e. class member with a name \ + beginning with an underscore) is access outside the class or a \ + descendant of the class where it\'s defined.'), + + 'E0211': ('Method has no argument', + 'Used when a method which should have the bound instance as \ + first argument has no argument defined.'), + 'E0213': ('Method should have "self" as first argument', + 'Used when a method has an attribute different the "self" as\ + first argument. This is considered as an error since this is\ + a soooo common convention that you should\'nt break it!'), + 'C0202': ('Class method should have "cls" as first argument', # E0212 + 'Used when a class method has an attribute different than "cls"\ + as first argument, to easily differentiate them from regular \ + instance methods.'), + 'C0203': ('Metaclass method should have "mcs" as first argument', # E0214 + 'Used when a metaclass method has an attribute different the \ + "mcs" as first argument.'), + + 'W0211': ('Static method with %r as first argument', + 'Used when a static method has "self" or "cls" as first argument.' + ), + 'R0201': ('Method could be a function', + 'Used when a method doesn\'t use its bound instance, and so could\ + be written as a function.' + ), + + 'E0221': ('Interface resolved to %s is not a class', + 'Used when a class claims to implement an interface which is not \ + a class.'), + 'E0222': ('Missing method %r from %s interface' , + 'Used when a method declared in an interface is missing from a \ + class implementing this interface'), + 'W0221': ('Arguments number differs from %s method', + 'Used when a method has a different number of arguments than in \ + the implemented interface or in an overridden method.'), + 'W0222': ('Signature differs from %s method', + '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', + 'Used when an abstract method (ie raise NotImplementedError) is \ + not overridden in concrete class.' + ), + 'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224 + 'Used when a PyLint as failed to find interfaces implemented by \ + a class'), + + + 'W0231': ('__init__ method from base class %r is not called', + 'Used when an ancestor class method has an __init__ method \ + which is not called by a derived class.'), + 'W0232': ('Class has no __init__ method', + 'Used when a class has no __init__ method, neither its parent \ + classes.'), + 'W0233': ('__init__ method from a non direct base class %r is called', + 'Used when an __init__ method is called on a class which is not \ + in the direct ancestors for the analysed class.'), + + } + + +class ClassChecker(BaseChecker): + """checks for : + * methods without self as first argument + * overridden methods signature + * access only to existant members via self + * attributes not defined in the __init__ method + * supported interfaces implementation + * unreachable code + """ + + __implements__ = (IASTNGChecker,) + + # configuration section name + name = 'classes' + # messages + msgs = MSGS + priority = -2 + # configuration options + options = (('ignore-iface-methods', + {'default' : (#zope interface + 'isImplementedBy', 'deferred', 'extends', 'names', + 'namesAndDescriptions', 'queryDescriptionFor', 'getBases', + 'getDescriptionFor', 'getDoc', 'getName', 'getTaggedValue', + 'getTaggedValueTags', 'isEqualOrExtendedBy', 'setTaggedValue', + 'isImplementedByInstancesOf', + # twisted + 'adaptWith', + # logilab.common interface + 'is_implemented_by'), + 'type' : 'csv', + 'metavar' : '<method names>', + 'help' : 'List of interface methods to ignore, \ +separated by a comma. This is used for instance to not check methods defines \ +in Zope\'s Interface base class.'} + ), + + ('defining-attr-methods', + {'default' : ('__init__', '__new__', 'setUp'), + 'type' : 'csv', + 'metavar' : '<method names>', + 'help' : 'List of method names used to declare (i.e. assign) \ +instance attributes.'} + ), + + ) + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self._accessed = [] + self._first_attrs = [] + self._meth_could_be_func = None + + def visit_class(self, node): + """init visit variable _accessed and check interfaces + """ + self._accessed.append({}) + self._check_bases_classes(node) + self._check_interfaces(node) + # if not an interface, exception, metaclass + if node.type == 'class': + try: + node.local_attr('__init__') + except astng.NotFoundError: + self.add_message('W0232', args=node, node=node) + + def leave_class(self, cnode): + """close a class node: + check that instance attributes are defined in __init__ and check + access to existant members + """ + # checks attributes are defined in an allowed method such as __init__ + defining_methods = self.config.defining_attr_methods + for attr, nodes in cnode.instance_attrs.items(): + node = nodes[0] # XXX + frame = node.frame() + if frame.name not in defining_methods: + # check attribute is defined in a parent's __init__ + for parent in cnode.instance_attr_ancestors(attr): + frame = parent.instance_attrs[attr][0].frame() # XXX + if frame.name in defining_methods: + # we're done :) + break + else: + # check attribute is defined as a class attribute + try: + cnode.local_attr(attr) + except astng.NotFoundError: + self.add_message('W0201', args=attr, node=node) + # check access to existant members on non metaclass classes + accessed = self._accessed.pop() + if cnode.type != 'metaclass': + self._check_accessed_members(cnode, accessed) + + def visit_function(self, node): + """check method arguments, overriding""" + # ignore actual functions + if not node.is_method(): + return + self._meth_could_be_func = True + # check first argument is self if this is actually a method + klass = node.parent.frame() + self._check_first_arg_for_type(node, klass.type == 'metaclass') + if node.name == '__init__': + self._check_init(node) + return + if not isinstance(klass, astng.Class): + return + # check signature if the method overrload an herited method + for overridden in klass.local_attr_ancestors(node.name): + # get astng for the searched method + try: + meth_node = overridden[node.name] + except KeyError: + # we have found the method but it's not in the local + # dictionnary. + # This may happen with astng build from living objects + continue + if not isinstance(meth_node, astng.Function): + continue + self._check_signature(node, meth_node, 'overridden') + break + # check if the method overload an attribute + try: + overridden = klass.instance_attr(node.name)[0] # XXX + # we may be unable to get owner class if this is a monkey + # patched method + while overridden.parent and not isinstance(overridden, astng.Class): + overridden = overridden.parent.frame() + self.add_message('E0202', args=overridden.name, node=node) + except astng.NotFoundError: + pass + + pymethods = set(('__new__', '__init__', + '__getattr__', '__setattr__', + '__hash__', '__cmp__', + '__mul__', '__div__', '__add__', '__sub__', + '__rmul__', '__rdiv__', '__radd__', '__rsub__', + # To be continued + )) + 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.argnames is not None: + self._first_attrs.pop() + class_node = node.parent.frame() + if (self._meth_could_be_func and node.type == 'method' + and not node.name in self.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 + if self._first_attrs and isinstance(node.expr, astng.Name) and \ + node.expr.name == self._first_attrs[-1]: + self._accessed[-1].setdefault(attrname, []).append(node) + elif attrname[0] == '_' and not attrname == '_' and not ( + attrname.startswith('__') and attrname.endswith('__')): + # XXX move this in a reusable function + klass = node.frame() + while klass is not None and not isinstance(klass, astng.Class): + if klass.parent is None: + klass = None + else: + klass = klass.parent.frame() + # XXX infer to be more safe and less dirty ?? + # in classes, check we are not getting a parent method + # through the class object or through super + callee = node.expr.as_string() + if klass is None or not (callee == klass.name or + callee in klass.basenames + or (isinstance(node.expr, astng.CallFunc) + and isinstance(node.expr.node, astng.Name) + and node.expr.node.name == 'super')): + self.add_message('W0212', node=node, args=attrname) + + + def visit_name(self, node): + """check if the name handle an access to a class member + if so, register it + """ + if self._first_attrs and (node.name == self._first_attrs[-1] or + not self._first_attrs[-1]): + self._meth_could_be_func = False + + def _check_accessed_members(self, node, accessed): + """check that accessed members are defined""" + # XXX refactor, probably much simpler now that E0201 is in type checker + for attr, nodes in accessed.items(): + # deactivate "except doesn't do anything", that's expected + # pylint: disable-msg=W0704 +## # is it a builtin attribute ? +## if attr in ('__dict__', '__class__', '__doc__'): +## # FIXME: old class object doesn't have __class__ +## continue + # is it a class attribute ? + try: + node.local_attr(attr) + # yes, stop here + continue + except astng.NotFoundError: + pass + # is it an instance attribute of a parent class ? + try: + node.instance_attr_ancestors(attr).next() + # yes, stop here + continue + except StopIteration: + pass + # is it an instance attribute ? + try: + def_nodes = node.instance_attr(attr) # XXX + #instance_attribute = True + except astng.NotFoundError: + pass + #instance_attribute = False + else: + if len(def_nodes) == 1: + def_node = def_nodes[0] + # check that if the node is accessed in the same method as + # it's defined, it's accessed after the initial assigment + frame = def_node.frame() + lno = def_node.source_line() + for _node in nodes: + if _node.frame() is frame and _node.lineno < lno: + self.add_message('E0203', node=_node, + args=(attr, lno)) + + def _check_first_arg_for_type(self, node, metaclass=0): + """check the name of first argument, expect: + + * 'self' for a regular method + * 'cls' for a class method + * 'mcs' for a metaclass + * not one of the above for a static method + """ + # don't care about functions with unknown argument (builtins) + if node.argnames is None: + return + self._first_attrs.append(node.argnames and node.argnames[0]) + # static method + if node.type == 'staticmethod': + if node.argnames and node.argnames[0] in ('self', 'cls', 'mcs'): + self.add_message('W0211', args=node.argnames[0], node=node) + self._first_attrs[-1] = None + # class / regular method with no args + elif not node.argnames: + self.add_message('E0211', node=node) + # metaclass method + elif metaclass: + if self._first_attrs[-1] != 'mcs': + self.add_message('C0203', node=node) + # class method + elif node.type == 'classmethod': + if self._first_attrs[-1] != 'cls': + self.add_message('C0202', node=node) + # regular method without self as argument + elif self._first_attrs[-1] != 'self': + self.add_message('E0213', node=node) + + def _check_bases_classes(self, node): + """check that the given class node implements abstract methods from + base classes + """ + for method in node.methods(): + owner = method.parent.frame() + if owner is node: + continue + # owner is not this class, it must be a parent class + # check that the ancestor's method is not abstract + if method.is_abstract(pass_is_abstract=False): + self.add_message('W0223', node=node, + args=(method.name, owner.name)) + + def _check_interfaces(self, node): + """check that the given class node really implements declared + interfaces + """ + e0221_hack = [False] + def iface_handler(obj): + """filter interface objects, it should be classes""" + if not isinstance(obj, astng.Class): + e0221_hack[0] = True + self.add_message('E0221', node=node, + args=(obj.as_string(),)) + return False + return True + ignore_iface_methods = self.config.ignore_iface_methods + try: + for iface in node.interfaces(handler_func=iface_handler): + for imethod in iface.methods(): + name = imethod.name + if name.startswith('_') or name in ignore_iface_methods: + # don't check method begining with an underscore, + # usually belonging to the interface implementation + continue + # get class method astng + try: + method = node_method(node, name) + except astng.NotFoundError: + self.add_message('E0222', args=(name, iface.name), + node=node) + continue + # ignore inherited methods + if method.parent.frame() is not node: + continue + # check signature + self._check_signature(method, imethod, + '%s interface' % iface.name) + except astng.InferenceError: + if e0221_hack[0]: + return + implements = astng.Instance(node).getattr('__implements__')[0] + assignment = implements.parent + assert isinstance(assignment, astng.Assign) + # assignment.expr can be a Name or a 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.expr.as_string())) + + def _check_init(self, node): + """check that the __init__ method call super or ancestors'__init__ + method + """ + klass_node = node.parent.frame() + to_call = _ancestors_to_call(klass_node) + for stmt in node.nodes_of_class(astng.CallFunc): + expr = stmt.node + if not isinstance(expr, astng.Getattr) \ + or expr.attrname != '__init__': + continue + # skip the test if using super + if isinstance(expr.expr, astng.CallFunc) and \ + isinstance(expr.expr.node, astng.Name) and \ + expr.expr.node.name == 'super': + return + try: + klass = expr.expr.infer().next() + if klass is astng.YES: + continue + try: + del to_call[klass] + except KeyError: + self.add_message('W0233', node=expr, args=klass.name) + except astng.InferenceError: + continue + for klass in to_call.keys(): + if klass.name == 'object': + continue + self.add_message('W0231', args=klass.name, node=node) + + def _check_signature(self, method1, refmethod, class_type): + """check that the signature of the two given methods match + + class_type is in 'class', 'interface' + """ + if not (isinstance(method1, astng.Function) + and isinstance(refmethod, astng.Function)): + self.add_message('F0202', args=(method1, refmethod), node=method1) + return + # don't care about functions with unknown argument (builtins) + if method1.argnames is None or refmethod.argnames is None: + return + if len(method1.argnames) != len(refmethod.argnames): + self.add_message('W0221', args=class_type, node=method1) + elif len(method1.defaults) < len(refmethod.defaults): + self.add_message('W0222', args=class_type, node=method1) + + +def _ancestors_to_call(klass_node, method='__init__'): + """return a dictionary where keys are the list of base classes providing + the queried method, and so that should/may be called from the method node + """ + to_call = {} + for base_node in klass_node.ancestors(recurs=False): + try: + base_node.local_attr(method) + to_call[base_node] = 1 + except astng.NotFoundError: + continue + return to_call + + +def node_method(node, method_name): + """get astng for <method_name> on the given class node, ensuring it + is a Function node + """ + stmt = node.local_attr(method_name) + if not isinstance(stmt, astng.Function): + raise astng.NotFoundError(method_name) + return stmt + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(ClassChecker(linter)) diff --git a/checkers/design_analysis.py b/checkers/design_analysis.py new file mode 100644 index 0000000..21b4b4a --- /dev/null +++ b/checkers/design_analysis.py @@ -0,0 +1,330 @@ +# Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""check for signs of poor design + + + see http://intranet.logilab.fr/jpl/view?rql=Any%20X%20where%20X%20eid%201243 + FIXME: missing 13, 15, 16 +""" + +from logilab.astng import Function, InferenceError + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker + +def class_is_abstract(klass): + """return true if the given class node should be considered as an abstract + class + """ + for attr in klass.values(): + if isinstance(attr, Function): + if attr.is_abstract(pass_is_abstract=False): + return True + return False + + +MSGS = { + 'R0901': ('Too many ancestors (%s/%s)', + 'Used when class has too many parent classes, try to reduce \ + this to get a more simple (and so easier to use) class.'), + 'R0902': ('Too many instance attributes (%s/%s)', + 'Used when class has too many instance attributes, try to reduce \ + this to get a more simple (and so easier to use) class.'), + 'R0903': ('Too few public methods (%s/%s)', + 'Used when class has too few public methods, so be sure it\'s \ + really worth it.'), + 'R0904': ('Too many public methods (%s/%s)', + 'Used when class has too many public methods, try to reduce \ + this to get a more simple (and so easier to use) class.'), + + 'R0911': ('Too many return statements (%s/%s)', + 'Used when a function or method has too many return statement, \ + making it hard to follow.'), + 'R0912': ('Too many branches (%s/%s)', + 'Used when a function or method has too many branches, \ + making it hard to follow.'), + 'R0913': ('Too many arguments (%s/%s)', + 'Used when a function or method takes too many arguments.'), + 'R0914': ('Too many local variables (%s/%s)', + 'Used when a function or method has too many local variables.'), + 'R0915': ('Too many statements (%s/%s)', + 'Used when a function or method has too many statements. You \ + should then split it in smaller functions / methods.'), + + 'R0921': ('Abstract class not referenced', + 'Used when an abstract class is not used as ancestor anywhere.'), + 'R0922': ('Abstract class is only referenced %s times', + 'Used when an abstract class is used less than X times as \ + ancestor.'), + 'R0923': ('Interface not implemented', + 'Used when an interface class is not implemented anywhere.'), + } + + +class MisdesignChecker(BaseChecker): + """checks for sign of poor/misdesign: + * number of methods, attributes, local variables... + * size, complexity of functions, methods + """ + + __implements__ = (IASTNGChecker,) + + # configuration section name + name = 'design' + # messages + msgs = MSGS + priority = -2 + # configuration options + options = (('max-args', + {'default' : 5, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of arguments for function / method'} + ), + ('max-locals', + {'default' : 15, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of locals for function / method body'} + ), + ('max-returns', + {'default' : 6, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of return / yield for function / ' + 'method body'} + ), + ('max-branchs', + {'default' : 12, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of branch for function / method body'} + ), + ('max-statements', + {'default' : 50, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of statements in function / method ' + 'body'} + ), + ('max-parents', + {'default' : 7, + 'type' : 'int', + 'metavar' : '<num>', + 'help' : 'Maximum number of parents for a class (see R0901).'} + ), + ('max-attributes', + {'default' : 7, + 'type' : 'int', + 'metavar' : '<num>', + 'help' : 'Maximum number of attributes for a class \ +(see R0902).'} + ), + ('min-public-methods', + {'default' : 2, + 'type' : 'int', + 'metavar' : '<num>', + 'help' : 'Minimum number of public methods for a class \ +(see R0903).'} + ), + ('max-public-methods', + {'default' : 20, + 'type' : 'int', + 'metavar' : '<num>', + 'help' : 'Maximum number of public methods for a class \ +(see R0904).'} + ), + ) + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self.stats = None + self._returns = None + self._branchs = None + self._used_abstracts = None + self._used_ifaces = None + self._abstracts = None + self._ifaces = None + self._stmts = 0 + + def open(self): + """initialize visit variables""" + self.stats = self.linter.add_stats() + self._returns = [] + self._branchs = [] + self._used_abstracts = {} + self._used_ifaces = {} + self._abstracts = [] + self._ifaces = [] + + def close(self): + """check that abstract/interface classes are used""" + for abstract in self._abstracts: + if not abstract in self._used_abstracts: + self.add_message('R0921', node=abstract) + elif self._used_abstracts[abstract] < 2: + self.add_message('R0922', node=abstract, + args=self._used_abstracts[abstract]) + for iface in self._ifaces: + if not iface in self._used_ifaces: + self.add_message('R0923', node=iface) + + def visit_class(self, node): + """check size of inheritance hierarchy and number of instance attributes + """ + self._inc_branch() + # Is the total inheritance hierarchy is 7 or less? + nb_parents = len(list(node.ancestors())) + if nb_parents > self.config.max_parents: + self.add_message('R0901', node=node, + args=(nb_parents, self.config.max_parents)) + # Does the class contain less than 20 attributes for + # non-GUI classes (40 for GUI)? + # FIXME detect gui classes + if len(node.instance_attrs) > self.config.max_attributes: + self.add_message('R0902', node=node, + args=(len(node.instance_attrs), + self.config.max_attributes)) + # update abstract / interface classes structures + if class_is_abstract(node): + self._abstracts.append(node) + elif node.type == 'interface' and node.name != 'Interface': + self._ifaces.append(node) + for parent in node.ancestors(False): + if parent.name == 'Interface': + continue + self._used_ifaces[parent] = 1 + try: + for iface in node.interfaces(): + self._used_ifaces[iface] = 1 + except InferenceError: + # XXX log ? + pass + for parent in node.ancestors(): + try: + self._used_abstracts[parent] += 1 + except KeyError: + self._used_abstracts[parent] = 1 + + def leave_class(self, node): + """check number of public methods""" + nb_public_methods = 0 + for method in node.methods(): + if not method.name.startswith('_'): + nb_public_methods += 1 + # Does the class contain less than 20 public methods ? + if nb_public_methods > self.config.max_public_methods: + self.add_message('R0904', node=node, + args=(nb_public_methods, + self.config.max_public_methods)) + # stop here for exception, metaclass and interface classes + if node.type != 'class': + return + # Does the class contain more than 5 public methods ? + if nb_public_methods < self.config.min_public_methods: + self.add_message('R0903', node=node, + args=(nb_public_methods, + self.config.min_public_methods)) + + + def visit_function(self, node): + """check function name, docstring, arguments, redefinition, + variable names, max locals + """ + self._inc_branch() + # init branch and returns counters + self._returns.append(0) + self._branchs.append(0) + # check number of arguments + args = node.argnames + if args is not None and len(args) > self.config.max_args: + self.add_message('R0913', node=node, + args=(len(args), self.config.max_args)) + # check number of local variables + locnum = len(node.locals) + if locnum > self.config.max_locals: + self.add_message('R0914', node=node, + args=(locnum, self.config.max_locals)) + # init statements counter + self._stmts = 1 + + def leave_function(self, node): + """most of the work is done here on close: + checks for max returns, branch, return in __init__ + """ + returns = self._returns.pop() + if returns > self.config.max_returns: + self.add_message('R0911', node=node, + args=(returns, self.config.max_returns)) + branchs = self._branchs.pop() + if branchs > self.config.max_branchs: + self.add_message('R0912', node=node, + args=(branchs, self.config.max_branchs)) + # check number of statements + if self._stmts > self.config.max_statements: + self.add_message('R0915', node=node, + args=(self._stmts, self.config.max_statements)) + + def visit_return(self, _): + """count number of returns/yields""" + if not self._returns: + return # return outside function, reported by the base checker + self._returns[-1] += 1 + + def visit_yield(self, _): + """count number of returns/yields""" + if not self._returns: + return # yield 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 branchs counter""" + branchs = len(node.handlers) + if node.else_: + branchs += 1 + self._inc_branch(branchs) + self._stmts += branchs + + def visit_tryfinally(self, _): + """increments the branchs counter""" + self._inc_branch(2) + self._stmts += 2 + + def visit_if(self, node): + """increments the branchs counter""" + branchs = len(node.tests) + if node.else_: + branchs += 1 + self._inc_branch(branchs) + self._stmts += branchs + + def visit_while(self, node): + """increments the branchs counter""" + branchs = 1 + if node.else_: + branchs += 1 + self._inc_branch(branchs) + + visit_for = visit_while + + def _inc_branch(self, branchsnum=1): + """increments the branchs counter""" + branchs = self._branchs + for i in xrange(len(branchs)): + branchs[i] += branchsnum + + # FIXME: make a nice report... + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(MisdesignChecker(linter)) diff --git a/checkers/exceptions.py b/checkers/exceptions.py new file mode 100644 index 0000000..1663915 --- /dev/null +++ b/checkers/exceptions.py @@ -0,0 +1,154 @@ +# Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""exceptions handling (raising, catching, exceptions classes) checker +""" + +from logilab.common.compat import enumerate +from logilab import astng +from logilab.astng.inference import unpack_infer + +from pylint.checkers import BaseChecker +from pylint.checkers.utils import is_empty, is_raising +from pylint.interfaces import IASTNGChecker + +MSGS = { + 'E0701': ( + 'Bad except clauses order (%s)', + 'Used when except clauses are not in the correct order (from the \ + more specific to the more generic). If you don\'t fix the order, \ + some exceptions may not be catched by the most specific handler.'), + 'E0702': ('Raising %s while only classes, instances or string are allowed', + 'Used when something which is neither a class, an instance or a \ + string is raised (i.e. a `TypeError` will be raised).'), + + 'W0701': ('Raising a string exception', + 'Used when a string exception is raised.'), + 'W0702': ('No exception type(s) specified', + 'Used when an except clause doesn\'t specify exceptions type to \ + catch.'), + 'W0703': ('Catch "Exception"', + 'Used when an except catches Exception instances.'), + 'W0704': ('Except doesn\'t do anything', + 'Used when an except clause does nothing but "pass" and there is\ + no "else" clause.'), + 'W0706': ( + 'Identifier %s used to raise an exception is assigned to %s', + 'Used when a variable used to raise an exception is initially \ + assigned to a value which can\'t be used as an exception.'), + } + +class ExceptionsChecker(BaseChecker): + """checks for + * excepts without exception filter + * string exceptions + """ + + __implements__ = IASTNGChecker + + name = 'exceptions' + msgs = MSGS + priority = -4 + options = () + + def visit_raise(self, node): + """check for string exception + """ + # ignore empty raise + if node.expr1 is None: + return + expr = node.expr1 + if isinstance(expr, astng.Const): + value = expr.value + if isinstance(value, str): + self.add_message('W0701', node=node) + else: + self.add_message('E0702', node=node, + args=value.__class__.__name__) + elif isinstance(expr, astng.Name) and \ + expr.name in ('None', 'True', 'False'): + self.add_message('E0702', node=node, args=expr.name) + elif isinstance(expr, astng.Mod): + self.add_message('W0701', node=node) + else: + try: + value = unpack_infer(expr).next() + except astng.InferenceError: + return + # have to be careful since Const, Dict, .. inherit from + # Instance now and get the original astng class as _proxied + if (value is astng.YES or + isinstance(value, (astng.Class, astng.Module)) or + (isinstance(value, astng.Instance) and + isinstance(value._proxied, astng.Class) and + value._proxied.root().name != '__builtin__')): + return + if isinstance(value, astng.Const) and \ + (value.value is None or + value.value is True or value.value is False): + # this Const has been generated by resolve + # since None, True and False are represented by Name + # nodes in the ast, and so this const node doesn't + # have the necessary parent, lineno and so on attributes + assinfo = value.as_string() + else: + assinfo = '%s line %s' % (value.as_string(), + value.source_line()) + self.add_message('W0706', node=node, + args=(expr.as_string(), assinfo)) + + def visit_tryexcept(self, node): + """check for empty except + """ + exceptions_classes = [] + nb_handlers = len(node.handlers) + for index, handler in enumerate(node.handlers): + exc_type = handler[0] + stmt = handler[2] + # single except doing nothing but "pass" without else clause + if nb_handlers == 1 and is_empty(stmt) and not node.else_: + self.add_message('W0704', node=exc_type or stmt) + if exc_type is None: + if nb_handlers == 1 and not is_raising(stmt): + self.add_message('W0702', node=stmt.nodes[0]) + # check if a "except:" is followed by some other + # except + elif index < (nb_handlers - 1): + msg = 'empty except clause should always appears last' + self.add_message('E0701', node=node, args=msg) + else: + try: + excs = list(unpack_infer(exc_type)) + except astng.InferenceError: + continue + for exc in excs: + # XXX skip other non class nodes + if exc is astng.YES or not isinstance(exc, astng.Class): + continue + exc_ancestors = [anc for anc in exc.ancestors() + if isinstance(anc, astng.Class)] + for previous_exc in exceptions_classes: + if previous_exc in exc_ancestors: + msg = '%s is an ancestor class of %s' % ( + previous_exc.name, exc.name) + self.add_message('E0701', node=exc_type, args=msg) + if (exc.name == 'Exception' + and exc.root().name == 'exceptions' + and nb_handlers == 1 and not is_raising(stmt)): + self.add_message('W0703', node=exc_type) + exceptions_classes += excs + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(ExceptionsChecker(linter)) diff --git a/checkers/format.py b/checkers/format.py new file mode 100644 index 0000000..41a0fd8 --- /dev/null +++ b/checkers/format.py @@ -0,0 +1,343 @@ +# Copyright (c) 2003-2008 Sylvain Thenault (thenault@gmail.com). +# Copyright (c) 2003-2008 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 re +import tokenize +if not hasattr(tokenize, 'NL'): + raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") + +from logilab.common.textutils import pretty_match +from logilab.astng import nodes + +from pylint.interfaces import IRawChecker, IASTNGChecker +from pylint.checkers import BaseRawChecker + +MSGS = { + 'C0301': ('Line too long (%s/%s)', + 'Used when a line is longer than a given number of characters.'), + 'C0302': ('Too many lines in module (%s)', # was W0302 + 'Used when a module has too much lines, reducing its readibility.' + ), + + 'W0311': ('Bad indentation. Found %s %s, expected %s', + 'Used when an unexpected number of indentation\'s tabulations or ' + 'spaces has been found.'), + 'W0312': ('Found indentation with %ss instead of %ss', + 'Used when there are some mixed tabs and spaces in a module.'), + + 'F0321': ('Format detection error in %r', + 'Used when an unexpected error occured in bad format detection.' + 'Please report the error if it occurs.'), + 'C0321': ('More than one statement on a single line', + 'Used when more than on statement are found on the same line.'), + 'C0322': ('Operator not preceded by a space\n%s', + 'Used when one of the following operator (!= | <= | == | >= | < ' + '| > | = | \+= | -= | \*= | /= | %) is not preceded by a space.'), + 'C0323': ('Operator not followed by a space\n%s', + 'Used when one of the following operator (!= | <= | == | >= | < ' + '| > | = | \+= | -= | \*= | /= | %) is not followed by a space.'), + 'C0324': ('Comma not followed by a space\n%s', + 'Used when a comma (",") is not followed by a space.'), + + 'W0331': ('Use of the <> operator', + 'Used when the deprecated "<>" operator is used instead \ + of "!=".'), + 'W0332': ('Use l as long integer identifier', + 'Used when a lower case "l" is used to mark a long integer. You ' + 'should use a upper case "L" since the letter "l" looks too much ' + 'like the digit "1"'), + 'W0333': ('Use of the `` operator', + 'Used when the deprecated "``" (backtick) operator is used ' + 'instead of the str() function.'), + } + +# simple quoted string rgx +SQSTRING_RGX = r'"([^"\\]|\\.)*("|\\$)' +# triple quoted string rgx +TQSTRING_RGX = r'"""([^"]|"(?!""))*("""|$)' +# simple apostrophed rgx +SASTRING_RGX = r"'([^'\\]|\\.)*('|\\$)" +# triple apostrophed string rgx # FIXME english please +TASTRING_RGX = r"'''([^']|'(?!''))*('''|$)" + +# finally, the string regular expression +STRING_RGX = re.compile('%s|%s|%s|%s' % (TQSTRING_RGX, TASTRING_RGX, + SQSTRING_RGX, SASTRING_RGX), re.M) + +COMMENT_RGX = re.compile("#.*$", re.M) + +OPERATORS = r'!=|<=|==|>=|<|>|=|\+=|-=|\*=|/=|%' + +OP_RGX_MATCH_1 = r'[^(]*(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s).*' % OPERATORS +OP_RGX_SEARCH_1 = r'(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s)' % OPERATORS + +OP_RGX_MATCH_2 = r'[^(]*(%s)(?!\s|=|>|<).*' % OPERATORS +OP_RGX_SEARCH_2 = r'(%s)(?!\s|=|>)' % OPERATORS + +BAD_CONSTRUCT_RGXS = ( +## (re.compile( +## r'\s*(class|def|if|for|while)\s+([^:\[\]]|\[.*:?.*\])*?:\s*\w+(\s|\w)*'), +## re.compile(r':\s*[^\s]+.*'), +## 'C0321'), + + (re.compile(OP_RGX_MATCH_1, re.M), + re.compile(OP_RGX_SEARCH_1, re.M), + 'C0322'), + + (re.compile(OP_RGX_MATCH_2, re.M), + re.compile(OP_RGX_SEARCH_2, re.M), + 'C0323'), + + (re.compile(r'.*,[^\s)].*', re.M), + re.compile(r',[^\s)]', re.M), + 'C0324'), + ) + + +def get_string_coords(line): + """return a list of string positions (tuple (start, end)) in the line + """ + result = [] + for match in re.finditer(STRING_RGX, line): + result.append( (match.start(), match.end()) ) + return result + +def in_coords(match, string_coords): + """return true if the match in in the string coord + """ + mstart = match.start() + for start, end in string_coords: + if mstart >= start and mstart < end: + return 1 + return 0 + +def check_line(line, writer): + """check a line for a bad construction + if it founds one, return a message describing the problem + else return None + """ + clean_str = STRING_RGX.sub('', line) + clean_str = COMMENT_RGX.sub('', clean_str) + for rgx_match, rgx_search, msg_id in BAD_CONSTRUCT_RGXS: + if rgx_match.match(clean_str): + string_positions = get_string_coords(line) + for match in re.finditer(rgx_search, line): + if not in_coords(match, string_positions): + return msg_id, pretty_match(match, line.rstrip()) + writer.add_message('F0321', line=line, args=line) + + + +class FormatChecker(BaseRawChecker): + """checks for : + * unauthorized constructions + * strict indentation + * line length + * use of <> instead of != + """ + + __implements__ = (IRawChecker, IASTNGChecker) + + # configuration section name + name = 'format' + # messages + msgs = MSGS + # configuration options + # for available dict keys/values see the optik parser 'add_option' method + options = (('max-line-length', + {'default' : 80, 'type' : "int", 'metavar' : '<int>', + 'help' : 'Maximum number of characters on a single line.'}), + ('max-module-lines', + {'default' : 1000, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of lines in a module'} + ), + ('indent-string', + {'default' : ' ', 'type' : "string", 'metavar' : '<string>', + 'help' : 'String used as indentation unit. This is usually \ +" " (4 spaces) or "\\t" (1 tab).'}), + ) + def __init__(self, linter=None): + BaseRawChecker.__init__(self, linter) + self._lines = None + self._visited_lines = None + + def new_line(self, tok_type, line, line_num, junk): + """a new line has been encountered, process it if necessary""" + if not tok_type in junk: + self._lines[line_num] = line + self.check_lines(line, line_num) + + def process_tokens(self, tokens): + """process tokens and search for : + + _ non strict indentation (i.e. not always using the <indent> parameter as + indent unit) + _ too long lines (i.e. longer than <max_chars>) + _ optionally bad construct (if given, bad_construct must be a compiled + regular expression). + """ + indent = tokenize.INDENT + dedent = tokenize.DEDENT + newline = tokenize.NEWLINE + junk = (tokenize.COMMENT, tokenize.NL) + indents = [0] + check_equal = 0 + line_num = 0 + self._lines = {} + self._visited_lines = {} + for (tok_type, token, start, _, line) in tokens: + if start[0] != line_num: + line_num = start[0] + self.new_line(tok_type, line, line_num, junk) + if tok_type == tokenize.OP: + if token == '<>': + self.add_message('W0331', line=line_num) + elif tok_type == tokenize.NUMBER: + if token.endswith('l'): + self.add_message('W0332', line=line_num) + + elif tok_type == newline: + # a program statement, or ENDMARKER, will eventually follow, + # after some (possibly empty) run of tokens of the form + # (NL | COMMENT)* (INDENT | DEDENT+)? + # If an INDENT appears, setting check_equal is wrong, and will + # be undone when we see the INDENT. + check_equal = 1 + + elif tok_type == indent: + check_equal = 0 + self.check_indent_level(token, indents[-1]+1, line_num) + indents.append(indents[-1]+1) + + elif tok_type == dedent: + # there's nothing we need to check here! what's important is + # that when the run of DEDENTs ends, the indentation of the + # program statement (or ENDMARKER) that triggered the run is + # equal to what's left at the top of the indents stack + check_equal = 1 + if len(indents) > 1: + del indents[-1] + + elif check_equal and tok_type not in junk: + # this is the first "real token" following a NEWLINE, so it + # must be the first token of the next program statement, or an + # ENDMARKER; the "line" argument exposes the leading whitespace + # for this statement; in the case of ENDMARKER, line is an empty + # string, so will properly match the empty string with which the + # "indents" stack was seeded + check_equal = 0 + self.check_indent_level(line, indents[-1], line_num) + 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) + + def visit_default(self, node): + """check the node line number and check it if not yet done + """ + if not node.is_statement(): + return + prev_sibl = node.previous_sibling() + if prev_sibl is not None: + # don't use .source_line since it causes C0321 false positive ! + prev_line = prev_sibl.fromlineno + # discard discard nodes introducted by ending ";" + if isinstance(node, nodes.Discard) and \ + isinstance(node.expr, nodes.Const) and \ + node.expr.lineno is None: + prev_line = None + else: + # itou ? + try: + prev_line = node.parent.statement().fromlineno + except AttributeError: + prev_line = node.parent.statement().lineno + line = node.fromlineno #source_line() + if prev_line == line and self._visited_lines.get(line) != 2: + self.add_message('C0321', node=node) + self._visited_lines[line] = 2 + return + if self._visited_lines.has_key(line): + return + lines = [] + for line in xrange(node.fromlineno, node.tolineno + 1): + self._visited_lines[line] = 1 + try: + lines.append(self._lines[line].rstrip()) + except KeyError: + lines.append('') + #print 'check', '\n'.join(lines) + #print node + try: + msg_def = check_line('\n'.join(lines), self) + if msg_def: + self.add_message(msg_def[0], node=node, args=msg_def[1]) + except KeyError: + # FIXME: internal error ! + pass + + 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 + for line in lines.splitlines(): + if len(line) > max_chars: + self.add_message('C0301', line=i, args=(len(line), max_chars)) + i += 1 + + def check_indent_level(self, string, expected, line_num): + """return the indent level of the string + """ + indent = self.config.indent_string + if indent == '\\t': # \t is not interpreted in the configuration file + indent = '\t' + level = 0 + unit_size = len(indent) + while string[:unit_size] == indent: + string = string[unit_size:] + level += 1 + suppl = '' + while string and string[0] in ' \t': + if string[0] != indent[0]: + if string[0] == '\t': + args = ('tab', 'space') + else: + args = ('space', 'tab') + self.add_message('W0312', args=args, line=line_num) + return level + suppl += string[0] + string = string [1:] + if level != expected or suppl: + i_type = 'spaces' + if indent[0] == '\t': + i_type = 'tabs' + self.add_message('W0311', line=line_num, + args=(level * unit_size + len(suppl), i_type, + expected * unit_size)) + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(FormatChecker(linter)) diff --git a/checkers/imports.py b/checkers/imports.py new file mode 100644 index 0000000..b6825ba --- /dev/null +++ b/checkers/imports.py @@ -0,0 +1,393 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""imports checkers for Python code +""" + +from logilab.common.graph import get_cycles +from logilab.common.modutils import is_standard_module, is_relative, \ + get_module_part +from logilab.common.ureports import VerbatimText, Paragraph + +from logilab import astng + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker, EmptyReport +from pylint.checkers.utils import are_exclusive + +def get_first_import(context, name, base, level=0): + """return the node where [base.]<name> is imported or None if not found + """ + for node in context.values(): + if isinstance(node, astng.Import): + if name in [iname[0] for iname in node.names]: + return node + if isinstance(node, astng.From): + if base == node.modname and level == node.level and \ + name in [iname[0] for iname in node.names]: + return node + + +# utilities to represents import dependencies as tree and dot graph ########### + +def filter_dependencies_info(dep_info, package_dir, mode='external'): + """filter external or internal dependencies from dep_info (return a + new dictionary containing the filtered modules only) + """ + if mode == 'external': + filter_func = lambda x: not is_standard_module(x, (package_dir,)) + else: + assert mode == 'internal' + filter_func = lambda x: is_standard_module(x, (package_dir,)) + result = {} + for importee, importers in dep_info.items(): + if filter_func(importee): + result[importee] = importers + return result + +def make_tree_defs(mod_files_list): + """get a list of 2-uple (module, list_of_files_which_import_this_module), + it will return a dictionnary to represent this as a tree + """ + tree_defs = {} + for mod, files in mod_files_list: + node = (tree_defs, ()) + for prefix in mod.split('.'): + node = node[0].setdefault(prefix, [{}, []]) + node[1] += files + return tree_defs + +def repr_tree_defs(data, indent_str=None): + """return a string which represents imports as a tree""" + lines = [] + nodes = data.items() + for i in range(len(nodes)): + mod, (sub, files) = nodes[i] + if not files: + files = '' + else: + files = '(%s)' % ','.join(files) + if indent_str is None: + lines.append('%s %s' % (mod, files)) + sub_indent_str = ' ' + else: + lines.append('%s\-%s %s' % (indent_str, mod, files)) + if i == len(nodes)-1: + sub_indent_str = '%s ' % indent_str + else: + sub_indent_str = '%s| ' % indent_str + if sub: + lines.append(repr_tree_defs(sub, sub_indent_str)) + return '\n'.join(lines) + +def dot_node(modname): + """return the string representation for a dot node""" + return '"%s" [ label="%s" ];' % (modname, modname) + +def dot_edge(from_, to_): + """return the string representation for a dot edge between two nodes""" + return'"%s" -> "%s" [ ] ;' % (from_, to_) + +DOT_HEADERS = '''rankdir="LR" URL="." concentrate=false +edge[fontsize="10" ] +node[width="0" height="0" fontsize="12" fontcolor="black"]''' + +def dependencies_graph(filename, dep_info): + """write dependencies as defined in the dep_info dictionary as a dot + (graphviz) file + """ + done = {} + stream = open(filename, 'w') + print >> stream, "digraph g {" + print >> stream, DOT_HEADERS + for modname, dependencies in dep_info.items(): + done[modname] = 1 + print >> stream, dot_node(modname) + for modname in dependencies: + if not done.has_key(modname): + done[modname] = 1 + print >> stream, dot_node(modname) + for depmodname, dependencies in dep_info.items(): + for modname in dependencies: + print >> stream, dot_edge(modname, depmodname) + print >> stream,'}' + stream.close() + +def make_graph(filename, dep_info, sect, gtype): + """generate a dependencies graph and add some information about it in the + report's section + """ + dependencies_graph(filename, dep_info) + sect.append(Paragraph('%simports graph has been written to %s' + % (gtype, filename))) + + +# the import checker itself ################################################### + +MSGS = { + 'F0401': ('Unable to import %r (%s)' , + 'Used when pylint has been unable to import a module.'), + 'R0401': ('Cyclic import (%s)', + 'Used when a cyclic import between two or more modules is \ + detected.'), + + 'W0401': ('Wildcard import %s', + 'Used when `from module import *` is detected.'), + 'W0402': ('Uses of a deprecated module %r', + 'Used a module marked as deprecated is imported.'), + 'W0403': ('Relative import %r', + 'Used when an import relative to the package directory is \ + detected.'), + 'W0404': ('Reimport %r (imported line %s)', + 'Used when a module is reimported multiple times.'), + 'W0406': ('Module import itself', + 'Used when a module is importing itself.'), + + 'W0410': ('__future__ import is not the first non docstring statement', + 'Python 2.5 and greater require __future__ import to be the \ + first non docstring statement in the module.'), + } + +class ImportsChecker(BaseChecker): + """checks for + * external modules dependencies + * relative / wildcard imports + * cyclic imports + * uses of deprecated modules + """ + + __implements__ = IASTNGChecker + + name = 'imports' + msgs = MSGS + priority = -2 + + options = (('deprecated-modules', + {'default' : ('regsub','string', 'TERMIOS', + 'Bastion', 'rexec'), + 'type' : 'csv', + 'metavar' : '<modules>', + 'help' : 'Deprecated modules which should not be used, \ +separated by a comma'} + ), + ('import-graph', + {'default' : '', + 'type' : 'string', + 'metavar' : '<file.dot>', + 'help' : 'Create a graph of every (i.e. internal and \ +external) dependencies in the given file (report R0402 must not be disabled)'} + ), + ('ext-import-graph', + {'default' : '', + 'type' : 'string', + 'metavar' : '<file.dot>', + 'help' : 'Create a graph of external dependencies in the \ +given file (report R0402 must not be disabled)'} + ), + ('int-import-graph', + {'default' : '', + 'type' : 'string', + 'metavar' : '<file.dot>', + 'help' : 'Create a graph of internal dependencies in the \ +given file (report R0402 must not be disabled)'} + ), + + ) + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self.stats = None + self.import_graph = None + self.__int_dep_info = self.__ext_dep_info = None + self.reports = (('R0401', 'External dependencies', + self.report_external_dependencies), + ('R0402', 'Modules dependencies graph', + self.report_dependencies_graph), + ) + + def open(self): + """called before visiting project (i.e set of modules)""" + self.linter.add_stats(dependencies={}) + self.linter.add_stats(cycles=[]) + self.stats = self.linter.stats + self.import_graph = {} + + def close(self): + """called before visiting project (i.e set of modules)""" + # 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""" + for name, _ in node.names: + self._check_deprecated(node, name) + relative = self._check_relative(node, name) + self._imported_module(node, name, relative) + # handle reimport + self._check_reimport(node, name) + + + def visit_from(self, node): + """triggered when a from statement is seen""" + basename = node.modname + if basename == '__future__': + # check 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, astng.From) + and prev.modname == '__future__'): + self.add_message('W0410', node=node) + self._check_deprecated(node, basename) + level = node.level + if level > 0: # explicit relative import (leading dots) + relative = True + else: + relative = self._check_relative(node, basename) + for name, _ in node.names: + if name == '*': + self.add_message('W0401', args=basename, node=node) + continue + # handle reimport + self._check_reimport(node, name, basename, level) + # analyze dependencies + fullname = '.' * level + '%s.%s' % (basename, name) + try: + # XXXFIXME: don't use get_module_part which doesn't take + # care of package precedence + fullname = get_module_part(fullname, + context_file=node.root().file) + except ImportError, ex: + self.add_message('F0401', args=(fullname, ex), node=node) + continue + self._imported_module(node, fullname, relative) + + def _imported_module(self, node, mod_path, relative): + """notify an imported module, used to analyze dependencies + """ + context_name = node.root().name + if relative: + context_parts = context_name.split('.') + if mod_path.startswith('.'): + while mod_path.startswith('.'): + mod_path = mod_path[1:] + del context_parts[-1] # one level upwards + context_parts.append(mod_path) + else: + context_parts[-1] = mod_path + mod_path = '.'.join(context_parts) + if context_name == mod_path: + # module importing itself ! + self.add_message('W0406', node=node) + elif not is_standard_module(mod_path): + # handle dependencies + mod_paths = self.stats['dependencies'].setdefault(mod_path, []) + if not context_name in mod_paths: + mod_paths.append(context_name) + if is_standard_module( mod_path, (self.package_dir(),) ): + # update import graph + mgraph = self.import_graph.setdefault(context_name, []) + if not mod_path in mgraph: + mgraph.append(mod_path) + + def _check_relative(self, node, mod_path): + """check relative import module""" + # check for relative import + context_file = node.root().file + relative = is_relative(mod_path, context_file) + if relative: + self.add_message('W0403', args=mod_path, node=node) + return relative + + def _check_deprecated(self, node, mod_path): + """check if the module is deprecated""" + for mod_name in self.config.deprecated_modules: + if mod_path.startswith(mod_name) and \ + (len(mod_path) == len(mod_name) + or mod_path[len(mod_name)] == '.'): + self.add_message('W0402', node=node, args=mod_path) + + def _check_reimport(self, node, name, basename=None, level=0): + """check if the import is necessary (i.e. not already done) + """ + frame = node.frame() + first = get_first_import(frame, name, basename, level) + if isinstance(first, (astng.Import, astng.From)) and first is not node \ + and not are_exclusive(first, node): + self.add_message('W0404', node=node, args=(name, first.lineno)) + else: + root = node.root() + if root is frame: + return + first = get_first_import(root, name, basename) + if not isinstance(first, (astng.Import, astng.From)): + return + if first is not node and not are_exclusive(first, node): + self.add_message('W0404', node=node, + args=(name, first.lineno)) + + + def report_external_dependencies(self, sect, _, dummy): + """return a verbatim layout for displaying dependencies + """ + dep_info = make_tree_defs(self._external_dependencies_info().items()) + if not dep_info: + raise EmptyReport() + tree_str = repr_tree_defs(dep_info) + sect.append(VerbatimText(tree_str)) + + def report_dependencies_graph(self, sect, _, dummy): + """write dependencies as a dot (graphviz) file""" + dep_info = self.stats['dependencies'] + if not dep_info or not (self.config.import_graph + or self.config.ext_import_graph + or self.config.int_import_graph): + raise EmptyReport() + filename = self.config.import_graph + if filename: + make_graph(filename, dep_info, sect, '') + filename = self.config.ext_import_graph + if filename: + make_graph(filename, self._external_dependencies_info(), + sect, 'external ') + filename = self.config.int_import_graph + if filename: + make_graph(filename, self._internal_dependencies_info(), + sect, 'internal ') + + def _external_dependencies_info(self): + """return cached external dependencies information or build and + cache them + """ + if self.__ext_dep_info is None: + self.__ext_dep_info = filter_dependencies_info( + self.stats['dependencies'], self.package_dir(), 'external') + return self.__ext_dep_info + + def _internal_dependencies_info(self): + """return cached internal dependencies information or build and + cache them + """ + if self.__int_dep_info is None: + self.__int_dep_info = filter_dependencies_info( + self.stats['dependencies'], self.package_dir(), 'internal') + return self.__int_dep_info + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(ImportsChecker(linter)) diff --git a/checkers/misc.py b/checkers/misc.py new file mode 100644 index 0000000..9160645 --- /dev/null +++ b/checkers/misc.py @@ -0,0 +1,127 @@ +# pylint: disable-msg=W0511 +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Check source code is ascii only or has an encoding declaration (PEP 263) +""" + +__revision__ = '$Id: misc.py,v 1.19 2005-11-02 09:21:47 syt Exp $' + +import re + +from pylint.interfaces import IRawChecker +from pylint.checkers import BaseChecker + +def is_ascii(string): + """return true if non ascii characters are detected in the given string + """ + if string: + return max([ord(char) for char in string]) < 128 + return True + +# regexp matching both emacs and vim declaration +ENCODING_RGX = re.compile("[^#]*#*.*coding[:=]\s*([^\s]+)") + +def guess_encoding(string): + """try to guess encoding from a python file as string + return None if not found + """ + assert type(string) is type(''), type(string) + # check for UTF-8 byte-order mark + if string.startswith('\xef\xbb\xbf'): + return 'UTF-8' + first_lines = string.split('\n', 2)[:2] + for line in first_lines: + # check for emacs / vim encoding declaration + match = ENCODING_RGX.match(line) + if match is not None: + return match.group(1) + + +MSGS = { + 'E0501': ('Non ascii characters found but no encoding specified (PEP 263)', + 'Used when some non ascii characters are detected but now \ + encoding is specified, as explicited in the PEP 263.'), + 'E0502': ('Wrong encoding specified (%s)', + 'Used when a known encoding is specified but the file doesn\'t \ + seem to be actually in this encoding.'), + 'E0503': ('Unknown encoding specified (%s)', + 'Used when an encoding is specified, but it\'s unknown to Python.' + ), + + 'W0511': ('%s', + 'Used when a warning note as FIXME or XXX is detected.'), + } + +class EncodingChecker(BaseChecker): + """checks for: + * warning notes in the code like FIXME, XXX + * PEP 263: source code with non ascii character but no encoding declaration + """ + __implements__ = IRawChecker + + # configuration section name + name = 'miscellaneous' + msgs = MSGS + + options = (('notes', + {'type' : 'csv', 'metavar' : '<comma separated values>', + 'default' : ('FIXME', 'XXX', 'TODO'), + 'help' : 'List of note tags to take in consideration, \ +separated by a comma.' + }), + ) + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + + def process_module(self, stream): + """inspect the source file to found encoding problem or fixmes like + notes + """ + # source encoding + data = stream.read() + if not is_ascii(data): + encoding = guess_encoding(data) + if encoding is None: + self.add_message('E0501', line=1) + else: + try: + unicode(data, encoding) + except UnicodeError: + self.add_message('E0502', args=encoding, line=1) + except LookupError: + self.add_message('E0503', args=encoding, line=1) + del data + # warning notes in the code + stream.seek(0) + notes = [] + for note in self.config.notes: + notes.append(re.compile(note)) + linenum = 1 + for line in stream.readlines(): + for note in notes: + match = note.search(line) + if match: + self.add_message('W0511', args=line[match.start():-1], + line=linenum) + break + linenum += 1 + + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(EncodingChecker(linter)) diff --git a/checkers/newstyle.py b/checkers/newstyle.py new file mode 100644 index 0000000..9832a72 --- /dev/null +++ b/checkers/newstyle.py @@ -0,0 +1,148 @@ +# Copyright (c) 2005-2006 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""check for new / old style related problems +""" + +import sys + +from logilab import astng + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker + +MSGS = { + 'E1001': ('Use __slots__ on an old style class', + 'Used when an old style class use the __slots__ attribute.'), + 'E1002': ('Use super on an old style class', + 'Used when an old style class use the super builtin.'), + 'E1003': ('Bad first argument %r given to super class', + 'Used when another argument than the current class is given as \ + first argument of the super builtin.'), + 'E1010': ('Raising a new style class', + 'Used when a new style class is raised since it\'s not \ + possible with python < 2.5.'), + + 'W1001': ('Use of "property" on an old style class', + 'Used when PyLint detect the use of the builtin "property" \ + on an old style class while this is relying on new style \ + classes features'), + 'W1010': ('Exception doesn\'t inherit from standard "Exception" class', + 'Used when a custom exception class is raised but doesn\'t \ + inherit from the builtin "Exception" class.'), + } + + +class NewStyleConflictChecker(BaseChecker): + """checks for usage of new style capabilities on old style classes and + other new/old styles conflicts problems + * use of property, __slots__, super + * "super" usage + * raising a new style class as exception + """ + + __implements__ = (IASTNGChecker,) + + # configuration section name + name = 'newstyle' + # messages + msgs = MSGS + priority = -2 + # configuration options + options = () + +# def __init__(self, linter=None): +# BaseChecker.__init__(self, linter) + + def visit_class(self, node): + """check __slots__ usage + """ + if '__slots__' in node and not node.newstyle: + self.add_message('E1001', node=node) + + def visit_callfunc(self, node): + """check property usage""" + parent = node.parent.frame() + if (isinstance(parent, astng.Class) and + not parent.newstyle and + isinstance(node.node, astng.Name)): + name = node.node.name + if name == 'property': + self.add_message('W1001', node=node) + + def visit_raise(self, node): + """check for raising new style class + """ + # ignore empty raise + if node.expr1 is None: + return + if not isinstance(node.expr1, (astng.Const, astng.Mod)): + try: + name = node.expr1.nodes_of_class(astng.Name).next() + value = name.infer().next() + except (StopIteration, astng.ResolveError): + pass + else: + if isinstance(value, astng.Class): + if value.newstyle and sys.version_info < (2, 5): + self.add_message('E1010', node=node) + elif not inherit_from_std_ex(value): + self.add_message('W1010', node=node) + + def visit_function(self, node): + """check use of super""" + # ignore actual functions or method within a new style class + if not node.is_method(): + return + klass = node.parent.frame() + for stmt in node.nodes_of_class(astng.CallFunc): + expr = stmt.node + if not isinstance(expr, astng.Getattr): + continue + call = expr.expr + # skip the test if using super + if isinstance(call, astng.CallFunc) and \ + isinstance(call.node, astng.Name) and \ + call.node.name == 'super': + if not klass.newstyle: + # super should not be used on an old style class + self.add_message('E1002', node=node) + else: + # super first arg should be the class + try: + supcls = (call.args and call.args[0].infer().next() + or None) + except astng.InferenceError: + continue + if klass is not supcls: + supcls = getattr(supcls, 'name', supcls) + self.add_message('E1003', node=node, args=supcls) + + + +def inherit_from_std_ex(node): + """return true if the given class node is subclass of + exceptions.Exception + """ + if node.name == 'Exception' and node.root().name == 'exceptions': + return True + for parent in node.ancestors(recurs=False): + if inherit_from_std_ex(parent): + return True + return False + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(NewStyleConflictChecker(linter)) diff --git a/checkers/raw_metrics.py b/checkers/raw_metrics.py new file mode 100644 index 0000000..4771207 --- /dev/null +++ b/checkers/raw_metrics.py @@ -0,0 +1,128 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Raw metrics checker +""" + +import tokenize + +# pylint now requires pylint >= 2.2, so this is no longer necessary +#if not hasattr(tokenize, 'NL'): +# raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") + +from logilab.common.ureports import Table + +from pylint.interfaces import IRawChecker +from pylint.checkers import BaseRawChecker, EmptyReport +from pylint.reporters import diff_string + + +def report_raw_stats(sect, stats, old_stats): + """calculate percentage of code / doc / comment / empty + """ + total_lines = stats['total_lines'] + if not total_lines: + raise EmptyReport() + sect.description = '%s lines have been analyzed' % total_lines + lines = ('type', 'number', '%', 'previous', 'difference') + for node_type in ('code', 'docstring', 'comment', 'empty'): + key = node_type + '_lines' + total = stats[key] + percent = float(total * 100) / total_lines + old = old_stats.get(key, None) + if old is not None: + diff_str = diff_string(old, total) + else: + old, diff_str = 'NC', 'NC' + lines += (node_type, str(total), '%.2f' % percent, + str(old), diff_str) + sect.append(Table(children=lines, cols=5, rheaders=1)) + + +class RawMetricsChecker(BaseRawChecker): + """does not check anything but gives some raw metrics : + * total number of lines + * total number of code lines + * total number of docstring lines + * total number of comments lines + * total number of empty lines + """ + + __implements__ = (IRawChecker,) + + # configuration section name + name = 'metrics' + # configuration options + options = ( ) + # messages + msgs = {} + # reports + reports = ( ('R0701', 'Raw metrics', report_raw_stats), ) + + def __init__(self, linter): + BaseRawChecker.__init__(self, linter) + self.stats = None + + def open(self): + """init statistics""" + self.stats = self.linter.add_stats(total_lines=0, code_lines=0, + empty_lines=0, docstring_lines=0, + comment_lines=0) + + def process_tokens(self, tokens): + """update stats""" + i = 0 + tokens = list(tokens) + while i < len(tokens): + i, lines_number, line_type = get_type(tokens, i) + self.stats['total_lines'] += lines_number + self.stats[line_type] += lines_number + + +JUNK = (tokenize.NL, tokenize.INDENT, tokenize.NEWLINE, tokenize.ENDMARKER) + +def get_type(tokens, start_index): + """return the line type : docstring, comment, code, empty + """ + i = start_index + tok_type = tokens[i][0] + start = tokens[i][2] + pos = start + line_type = None + while i < len(tokens) and tokens[i][2][0] == start[0]: + tok_type = tokens[i][0] + pos = tokens[i][3] + if line_type is None: + if tok_type == tokenize.STRING: + line_type = 'docstring_lines' + elif tok_type == tokenize.COMMENT: + line_type = 'comment_lines' + elif tok_type in JUNK: + pass + else: + line_type = 'code_lines' + i += 1 + + if line_type is None: + line_type = 'empty_lines' + elif i < len(tokens) and tok_type == tokenize.NEWLINE: + i += 1 + return i, pos[0] - start[0] + 1, line_type + + +def register(linter): + """ required method to auto register this checker """ + linter.register_checker(RawMetricsChecker(linter)) + diff --git a/checkers/rpython.py b/checkers/rpython.py new file mode 100644 index 0000000..e2239d5 --- /dev/null +++ b/checkers/rpython.py @@ -0,0 +1,448 @@ +# Copyright (c) 2007 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""check a python program is `Restricted Python`_ compliant. It is intended to +find potential pypy translation bugs at once without waiting a long time to get +translation failures one by one. +""" + +__docformat__ = "restructuredtext en" + +import re + +from logilab.common.compat import set +from logilab import astng + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker + + +MSGS = { + 'E1201': ('Using unavailable keyword %r', + 'Used when a keyword which is not available in rpython is used.'), + 'E1202': ('Using unavailable builtin %r', + 'Used when a built-in which is not available in rpython is used.' + ), + 'E1203': ('generator expressions are not supported', + 'Used when a generator expression is used while generator are \ +not available in rpython.'), + 'E1204': ('multiple inheritance only supported under specific rules \ +which doesn\'t seems to be satisfied', + 'Multiple inheritance is only supported using pure mixin (no \ +instance attribute defined in the mix-in) with "_mixin_" class attribute set \ +to True.'), + 'E1205': ('Using unavailable protocol %r', + 'Used when a special method not handled by rpython is used *and*' + ' that may not be used explicitly is used (see W1201).'), + + + 'E1210': ('Multiple types assigned to %s %r', + 'Used when an identifier or attribut is infered as having values' + ' of different types assigned.'), + 'E1211': ('Can\'t mix %s and None on %s %r', + 'Used when an int or float variable is assigned to None.'), + 'E1212': ('Non homogeneous values in list', + 'Used when a list is not containing homogeneous values.'), + + +## 'E1205': ('Identifier %r is not properly initialized', +## 'Used when an identifier is used in some conditional block \ +## without being properly initialized before that block.'), + + 'E1220': ('Modifying global %r from module %s', + 'Used when a module variable is modified, which is not allowed ' + 'in rpython since globals are considered as constants.'), + + + 'E1230': ('Using negative slice %s %s (infered to %s)', + 'Used when a negative integer is used as lower, upper or step of' + ' a slice.'), + 'E1231': ('Using non constant step', + 'Used when a variable not annotated as a constant is used as ' + 'step of a slice.'), + + 'E1240': ('%r format is not supported', + 'Used when the unavailable "%r" formatting instruction is used.' + ), + + 'W1201': ('special method %s has to be called explicitly', + 'Used when a special method is defined on a class, as rpython ' + 'won\'t call the explicitly.'), + + } + +# XXX: nested functions/classes +# XXX: properties not supported ? + +# 'global' is available even if it doesn't help anything since globals are +# considered immutable +UNAVAILABLE_KEYWORDS = set(('yield', 'exec', 'lambda', 'print')) + +import __builtin__ +BUILTINLIST = set([x for x in dir(__builtin__) if x[0].islower()]) +AUTHORIZED = set(('abs', 'apply', + 'basestring', 'bool', + 'chr', 'cmp', 'coerce', + 'float', 'hasattr', 'hash', 'hex', + 'int', 'isinstance', + 'len', 'list', 'max', 'min', 'oct', 'ord', + 'range', 'slice', 'str', 'tuple', 'type', + 'unichr', 'xrange', 'zip' + )) + +UNAVAILABLE_BUILTINS = { + '__builtin__': BUILTINLIST - AUTHORIZED, + 'site': 'help', + } +#from pprint import pprint +#pprint(sorted(BUILTINLIST - AUTHORIZED)) +del BUILTINLIST, AUTHORIZED + +BUILTIN_MODIFIERS = {'dict': set(('clear', 'fromkeys', 'pop', 'popitem', + 'setdefault', 'update')), + 'list': set(('append', 'extend', 'insert', 'pop', + 'remove', 'reverse', 'sort')),} + +UNAVAILABLE_PROTOCOLS = set(('__new__',)) + +REPR_NAMED_FORMAT_INSTR = re.compile('%\([^)]+\)r') + + + +def is_pure_mixin(node): + """return true if the given class node can be considered as a mixin class + according to rpython conventions + """ + if node.instance_attrs: + return False + try: + for infered in node.igetattr('_mixin_'): + if isinstance(infered, astng.Const) and infered.value: + return True + except astng.InferenceError: + return False + + +class RPythonChecker(BaseChecker): + """check a python program is `Restricted Python`_ compliant. Restricted + python is used in the PyPy_ project to make a python program compilable. + + .. _`Restricted Python`: http://codespeak.net/pypy/dist/pypy/doc/coding-guide.html + .. _`PyPy`: http://codespeak.net/pypy/ + """ + + __implements__ = (IASTNGChecker,) + + # configuration section name + name = 'rpython' + enabled = False # disabled by default + # messages + msgs = MSGS + priority = -1 + # configuration options + options = () + + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self._rpython = True + + + def visit_name(self, node): + """check unavailable builtins are not used""" + if not self._rpython: + return + try: + infered = node.infer().next() + except astng.InferenceError: + return # XXX + if infered is astng.YES: + return # XXX + name = node.name + module = infered.root().name + if module in UNAVAILABLE_BUILTINS and \ + name in UNAVAILABLE_BUILTINS[module]: + self.add_message('E1202', node=node, args=name) +## # E1205 check, example: +## # +## # ... +## # if bla: +## # a = 4 +## # else: +## # a = 5 +## # print a +## # +## # in such a case a should be defined before the if/else block. +## # So here if name is a local name we have to ckeck it's defined in +## # the same block or in a parent block +## frame, stmts = node.lookup(name) +## if frame is node.frame() and len(stmts) > 1: +## # XXX only consider the first assignment ? +## assstmt = stmts[0].statement().parent +## _node = node.statement() +## while _node: +## if _node is assstmt: +## break +## _node = _node.parent +## else: +## self.add_message('E1205', node=node, args=name) + try: + for infered in node.infer(): + if infered is astng.YES: + continue + break + else: + return + except astng.InferenceError: + return + frame = infered.frame() + # check for more global alteration + if not frame is node.frame() and isinstance(frame, astng.Module): + # accessing to a global + if isinstance(infered, (astng.List, astng.Dict)): + # is it a tuple/dict item assignment ? + ass = node.parent + last = node + while ass and not isinstance(ass, astng.Assign): + last = last + ass = ass.parent + # "not last is ass.expr" is checking the global isn't in the rhs + if ass is not None and not last is ass.expr: + self.add_message('E1220', node=node, + args=(node.name, node.root().name)) + return + # is it a call to a tuple/dict method modifying it ? + if isinstance(node.parent, astng.Getattr) and \ + isinstance(node.parent.parent, astng.CallFunc): + if node.parent.attrname in BUILTIN_MODIFIERS[infered.name]: + self.add_message('E1220', node=node, + args=(node.name, node.root().name)) + + def visit_class(self, node): + """check class attributes have homogeneous types""" + if not self._rpython: + return + for name in node.instance_attrs.keys(): + self.check_types(node, name, astng.Instance(node).igetattr(name), + 'attribute') + # XXX recurs ? + ancestors = list(node.ancestors(recurs=False)) + if len(ancestors) > 1: + for parent in ancestors[:]: + if is_pure_mixin(parent): + ancestors.remove(parent) + if len(ancestors) > 1: + self.add_message('E1204', node=node) + + def visit_function(self, node): + """check function locals have homogeneous types""" + docstring = node.doc + if docstring is not None and 'NOT RPYTHON' in docstring: + self._rpython = False + if node.name in UNAVAILABLE_PROTOCOLS: + self.add_message('E1205', node=node, args=node.name) + elif node.name.startswith('__') and node.name.endswith('__') and \ + node.name != '__init__': + self.add_message('W1201', node=node, args=node.name) + + if not self._rpython: + return + for name in node.locals.keys(): + self.check_types(node, name, node.ilookup(name), 'identifier') + + def leave_function(self, node): + docstring = node.doc + if docstring is not None and 'NOT RPYTHON' in docstring: + self._rpython = True + + def visit_list(self, node): + """check list contains homogeneous types""" + if not self._rpython: + return + types = set() + for node in node.nodes: + try: + # XXX use ifilter + filter to factorize filtering below + for infered in node.infer(): + if infered is astng.YES: + continue + # XXX skip None ? + if isinstance(infered, astng.Const) and \ + infered.value is None: + continue + types.add(str(infered)) + except astng.InferenceError: + continue + if len(types) > 1: + self.add_message('E1212', node=node) + + + def visit_assattr(self, node): + """check we are not modifying a module attribute""" + if not self._rpython: + return + try: + infered = node.expr.infer().next() + except astng.InferenceError: + return # XXX + if isinstance(infered, astng.Module): + self.add_message('E1220', node=node, + args=(node.attrname, infered.name)) + + def visit_assname(self, node): + """check we are not modifying a module attribute""" + if not self._rpython: + return + frame = node.frame() + if not node.name in node.frame().locals: + self.add_message('E1220', node=node, + args=(node.name, node.root().name)) + + + def visit_slice(self, node): + """no negative index""" + if not self._rpython: + return + self.check_slice(node.lower, node.upper) + + def visit_sliceobj(self, node): + """no negative index""" + if not self._rpython: + return + sdef = [] + for bound in node.nodes: + if isinstance(bound, astng.Const) and bound.value is None: + sdef.append(None) + else: + sdef.append(bound) + self.check_slice(*sdef) + + def visit_genexpr(self, node): + self.add_message('E1203', node=node) + # avoid E1220 false positive due to particular gen expr variable scope + self._rpython = False + + def leave_genexpr(self, node): + self._rpython = True + + def visit_mod(self, node): + try: + for infered in node.left.infer(): + if infered is astng.YES: + continue + if not (isinstance(infered, astng.Const) and + isinstance(infered.value, basestring)): + self.add_message('F0004', node=node, args=infered) + continue + value = infered.value.replace('%%', '%%') + if '%r' in value or REPR_NAMED_FORMAT_INSTR.search(value): + self.add_message('E1240', node=infered) + + except astng.InferenceError: + pass + + + def check_types(self, node, name, inferednodes, vtype): + """check types assigned to a name (vtype is a string telling if it's a + local or attribute + + node is the starting node (function or class node usually) + infered the infered value for the name + """ + types = set() + hasnone = False + for infered in inferednodes: + if infered is astng.YES: + continue + # skip None + if isinstance(infered, astng.Const) and infered.value is None: + hasnone = True + continue + types.add(infered.pytype()) + if len(types) > 1: + self.add_message('E1210', node=node, args=(vtype, name)) + elif hasnone and types: + ptype = types.pop() + # XXX long ? they should not been supported but this is not handled + # do that in visit_const ? + if ptype in ('__builtin__.int', '__builtin__.float'): + ptype = ptype.split('.')[1] + self.add_message('E1211', node=node, args=(ptype, vtype, name)) + + def check_slice(self, start, stop, step=None): + """ + * step has to be annotated as a constant and >= 0 + * start >= 0 + * stop >= 0 + * [:-1] et [0:-1] OK + """ + + if start is not None: + value = self.check_positive_integer(start, 'start index') + if stop is not None: + self.check_positive_integer(stop, 'stop index', + start is None or value == 0) + if step is not None: + try: + for infered in step.infer(): + if infered is astng.YES: + self.add_message('E1231', node=step) + return + except astng.InferenceError: + self.add_message('E1231', node=step) + return + self.check_positive_integer(step, 'step') + + def check_positive_integer(self, node, msg, minus_one_allowed=False): + value = None + try: + for infered in node.infer(): + if infered is astng.YES: + continue + if not (isinstance(infered, astng.Const) and + isinstance(infered.value, int)): + self.add_message('F0004', node=node, args=infered) + continue + if infered.value < 0: + if minus_one_allowed and infered.value == -1: + value = infered.value + continue + self.add_message('E1230', node=node, + args=(msg, node.as_string(), + infered.value)) + else: + value = infered.value + except astng.InferenceError: + pass + return value + + + +# XXX: checking rpython should do an "entry point search", not a "project +# search" (eg from a modules/packages list) +# moreover we should differentiate between initial import vs runtime imports, +# no ? + +for _kw in UNAVAILABLE_KEYWORDS: + def visit_unavailable_keyword(self, node, name=_kw): + if not self._rpython: + return + self.add_message('E1201', node=node, args=name) + setattr(RPythonChecker, 'visit_%s' % _kw, visit_unavailable_keyword) +del _kw + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(RPythonChecker(linter)) diff --git a/checkers/similar.py b/checkers/similar.py new file mode 100644 index 0000000..0c5fe7b --- /dev/null +++ b/checkers/similar.py @@ -0,0 +1,334 @@ +# pylint: disable-msg=W0622 +# Copyright (c) 2004-2006 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""a similarties / code duplication command line tool and pylint checker +""" +from __future__ import generators + +__revision__ = '$Id: similar.py,v 1.14 2006-03-29 08:24:32 syt Exp $' + +import sys + +from logilab.common.compat import set, izip, sum, enumerate +from logilab.common.ureports import Table + +from pylint.interfaces import IRawChecker +from pylint.checkers import BaseChecker, table_lines_from_stats + + +class Similar: + """finds copy-pasted lines of code in a project""" + + def __init__(self, min_lines=4, ignore_comments=False, + ignore_docstrings=False): + self.min_lines = min_lines + self.ignore_comments = ignore_comments + self.ignore_docstrings = ignore_docstrings + self.linesets = [] + + def append_stream(self, streamid, stream): + """append a file to search for similarities""" + self.linesets.append(LineSet(streamid, + stream.readlines(), + self.ignore_comments, + self.ignore_docstrings)) + + def run(self): + """start looking for similarities and display results on stdout""" + self._display_sims(self._compute_sims()) + + def _compute_sims(self): + """compute similarities in appended files""" + no_duplicates = {} + for num, lineset1, idx1, lineset2, idx2 in self._iter_sims(): + duplicate = no_duplicates.setdefault(num, []) + for couples in duplicate: + if (lineset1, idx1) in couples or (lineset2, idx2) in couples: + couples.add( (lineset1, idx1) ) + couples.add( (lineset2, idx2) ) + break + else: + duplicate.append( set([(lineset1, idx1), (lineset2, idx2)]) ) + sims = [] + for num, ensembles in no_duplicates.iteritems(): + for couples in ensembles: + sims.append( (num, couples) ) + sims.sort() + sims.reverse() + return sims + + def _display_sims(self, sims): + """display computed similarities on stdout""" + nb_lignes_dupliquees = 0 + for num, couples in sims: + print + print num, "similar lines in", len(couples), "files" + couples = list(couples) + couples.sort() + for lineset, idx in couples: + print "==%s:%s" % (lineset.name, idx) + # pylint: disable-msg=W0631 + for line in lineset._real_lines[idx:idx+num]: + print " ", line, + nb_lignes_dupliquees += num * (len(couples)-1) + nb_total_lignes = sum([len(lineset) for lineset in self.linesets]) + print "TOTAL lines=%s duplicates=%s percent=%s" \ + % (nb_total_lignes, nb_lignes_dupliquees, + nb_lignes_dupliquees*1. / nb_total_lignes) + + def _find_common(self, lineset1, lineset2): + """find similarities in the two given linesets""" + lines1 = lineset1.enumerate_stripped + lines2 = lineset2.enumerate_stripped + find = lineset2.find + index1 = 0 + min_lines = self.min_lines + while index1 < len(lineset1): + skip = 1 + num = 0 + for index2 in find( lineset1[index1] ): + non_blank = 0 + for num, ((_, line1), (_, line2)) in enumerate( + izip(lines1(index1), lines2(index2))): + if line1 != line2: + if non_blank > min_lines: + yield num, lineset1, index1, lineset2, index2 + skip = max(skip, num) + break + if line1: + non_blank += 1 + else: + # we may have reach the end + num += 1 + if non_blank > min_lines: + yield num, lineset1, index1, lineset2, index2 + skip = max(skip, num) + index1 += skip + + def _iter_sims(self): + """iterate on similarities among all files, by making a cartesian + product + """ + for idx, lineset in enumerate(self.linesets[:-1]): + for lineset2 in self.linesets[idx+1:]: + for sim in self._find_common(lineset, lineset2): + yield sim + +def stripped_lines(lines, ignore_comments, ignore_docstrings): + strippedlines = [] + docstring = None + for line in lines: + line = line.strip() + if ignore_docstrings: + if not docstring and \ + (line.startswith('"""') or line.startswith("'''")): + docstring = line[:3] + line = line[3:] + if docstring: + if line.endswith(docstring): + docstring = None + line = '' + # XXX cut when a line begins with code but end with a comment + if ignore_comments and line.startswith('#'): + line = '' + strippedlines.append(line) + return strippedlines + +class LineSet: + """Holds and indexes all the lines of a single source file""" + def __init__(self, name, lines, ignore_comments=False, + ignore_docstrings=False): + self.name = name + self._real_lines = lines + self._stripped_lines = stripped_lines(lines, ignore_comments, + ignore_docstrings) + self._index = self._mk_index() + + def __str__(self): + return '<Lineset for %s>' % self.name + + def __len__(self): + return len(self._real_lines) + + def __getitem__(self, index): + return self._stripped_lines[index] + + def __cmp__(self, other): + return cmp(self.name, other.name) + + def __hash__(self): + return id(self) + + def enumerate_stripped(self, start_at=0): + """return an iterator on stripped lines, starting from a given index + if specified, else 0 + """ + idx = start_at + if start_at: + lines = self._stripped_lines[start_at:] + else: + lines = self._stripped_lines + for line in lines: + #if line: + yield idx, line + idx += 1 + + def find(self, stripped_line): + """return positions of the given stripped line in this set""" + return self._index.get(stripped_line, ()) + + def _mk_index(self): + """create the index for this set""" + index = {} + for line_no, line in enumerate(self._stripped_lines): + if line: + index.setdefault(line, []).append( line_no ) + return index + + +MSGS = {'R0801': ('Similar lines in %s files\n%s', + 'Indicates that a set of similar lines has been detected \ + among multiple file. This usually means that the code should \ + be refactored to avoid this duplication.')} + +def report_similarities(sect, stats, old_stats): + """make a layout with some stats about duplication""" + lines = ['', 'now', 'previous', 'difference'] + lines += table_lines_from_stats(stats, old_stats, + ('nb_duplicated_lines', + 'percent_duplicated_lines')) + sect.append(Table(children=lines, cols=4, rheaders=1, cheaders=1)) + + +# wrapper to get a pylint checker from the similar class +class SimilarChecker(BaseChecker, Similar): + """checks for similarities and duplicated code. This computation may be + memory / CPU intensive, so you should disable it if you experiments some + problems. + """ + + __implements__ = (IRawChecker,) + # configuration section name + name = 'similarities' + # messages + msgs = MSGS + # configuration options + # for available dict keys/values see the optik parser 'add_option' method + options = (('min-similarity-lines', + {'default' : 4, 'type' : "int", 'metavar' : '<int>', + 'help' : 'Minimum lines number of a similarity.'}), + ('ignore-comments', + {'default' : True, 'type' : 'yn', 'metavar' : '<y or n>', + 'help': 'Ignore comments when computing similarities.'} + ), + ('ignore-docstrings', + {'default' : True, 'type' : 'yn', 'metavar' : '<y or n>', + 'help': 'Ignore docstrings when computing similarities.'} + ), + ) + # reports + reports = ( ('R0801', 'Duplication', report_similarities), ) + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + Similar.__init__(self, min_lines=4, + ignore_comments=True, ignore_docstrings=True) + self.stats = None + + def set_option(self, opt_name, value, action=None, opt_dict=None): + """method called to set an option (registered in the options list) + + overridden to report options setting to Similar + """ + BaseChecker.set_option(self, opt_name, value, action, opt_dict) + if opt_name == 'min-similarity-lines': + self.min_lines = self.config.min_similarity_lines + elif opt_name == 'ignore-comments': + self.ignore_comments = self.config.ignore_comments + elif opt_name == 'ignore-docstrings': + self.ignore_docstrings = self.config.ignore_docstrings + + def open(self): + """init the checkers: reset linesets and statistics information""" + self.linesets = [] + self.stats = self.linter.add_stats(nb_duplicated_lines=0, + percent_duplicated_lines=0) + + def process_module(self, stream): + """process a module + + the module's content is accessible via the stream object + + stream must implements the readlines method + """ + self.append_stream(self.linter.current_name, stream) + + def close(self): + """compute and display similarities on closing (i.e. end of parsing)""" + total = sum([len(lineset) for lineset in self.linesets]) + duplicated = 0 + stats = self.stats + for num, couples in self._compute_sims(): + msg = [] + for lineset, idx in couples: + msg.append("==%s:%s" % (lineset.name, idx)) + msg.sort() + # pylint: disable-msg=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: similar [-d|--duplicates min_duplicated_lines] \ +[--ignore-comments] file1...' + sys.exit(status) + +def run(argv=None): + """standalone command line access point""" + argv = argv or sys.argv[1:] + from getopt import getopt + s_opts = 'hd:' + l_opts = ('help', 'duplicates=', 'ignore-comments') + min_lines = 4 + ignore_comments = False + opts, args = getopt(argv, s_opts, l_opts) + for opt, val in opts: + if opt in ('-d', '--duplicates'): + min_lines = int(val) + elif opt in ('-h', '--help'): + usage() + elif opt == '--ignore-comments': + ignore_comments = True + if not args: + usage(1) + sim = Similar(min_lines, ignore_comments) + for filename in args: + sim.append_stream(filename, open(filename)) + sim.run() + +if __name__ == '__main__': + run() diff --git a/checkers/typecheck.py b/checkers/typecheck.py new file mode 100644 index 0000000..cbcbddd --- /dev/null +++ b/checkers/typecheck.py @@ -0,0 +1,202 @@ +# Copyright (c) 2006 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""try to find more bugs in the code using astng inference capabilities +""" + +from logilab.common.compat import set +from logilab import astng + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker +from pylint.checkers.utils import safe_infer, is_super, display_type + +MSGS = { + 'E1101': ('%s %r has no %r member', + 'Used when a variable is accessed for an unexistant member.'), + 'E1102': ('%s is not callable', + 'Used when an object being called has been infered to a non \ + callable object'), + 'E1103': ('%s %r has no %r member (but some types could not be inferred)', + 'Used when a variable is accessed for an unexistant member, but \ + astng was not able to interpret all possible types of this \ + variable.'), + 'E1111': ('Assigning to function call which doesn\'t return', + 'Used when an assigment is done on a function call but the \ + infered function doesn\'t return anything.'), + 'W1111': ('Assigning to function call which only returns None', + 'Used when an assigment is done on a function call but the \ + infered function returns nothing but None.'), + } + +class TypeChecker(BaseChecker): + """try to find bugs in the code using type inference + """ + + __implements__ = (IASTNGChecker,) + + # configuration section name + name = 'typecheck' + # messages + msgs = MSGS + priority = -1 + # configuration options + options = (('ignore-mixin-members', + {'default' : True, 'type' : 'yn', 'metavar': '<y_or_n>', + 'help' : 'Tells wether missing members accessed in mixin \ +class should be ignored. A mixin class is detected if its name ends with \ +"mixin" (case insensitive).'} + ), + + ('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 dynamicaly 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' : 'csv', + '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.'} + ), + ) + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self.generated_members = list(self.config.generated_members) + if self.config.zope: + self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent')) + + 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 infered nodes has the accessed attribute. + + function/method, super call and metaclasses are ignored + """ + if node.attrname in self.config.generated_members: + # attribute is marked as generated, stop here + return + try: + infered = list(node.expr.infer()) + except astng.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 astng.YES: + inference_failure = True + continue + # skip None anyway + if isinstance(owner, astng.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: + owner.getattr(node.attrname) + except AttributeError: + # XXX method / function + continue + except astng.NotFoundError, ex: + if isinstance(owner, astng.Instance) \ + and owner.has_dynamic_getattr(): + continue + # explicit skipping of optparse'Values class + if owner.name == 'Values' and \ + owner.root().name in ('optik', '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, astng.Instance): + actual = owner._proxied + else: + actual = owner + if actual in done: + continue + done.add(actual) + if inference_failure: + msgid = 'E1103' + else: + msgid = 'E1101' + self.add_message(msgid, node=node, + args=(display_type(owner), name, + node.attrname)) + + + def visit_assign(self, node): + """check that if assigning to a function call, the function is + possibly returning something valuable + """ + if not isinstance(node.expr, astng.CallFunc): + return + function_node = safe_infer(node.expr.node) + # skip class, generator and uncomplete function definition + if not (isinstance(function_node, astng.Function) and + function_node.root().fully_defined()): + return + if function_node.is_generator() \ + or function_node.is_abstract(pass_is_abstract=False): + return + returns = list(function_node.nodes_of_class(astng.Return, + skip_klass=astng.Function)) + if len(returns) == 0: + self.add_message('E1111', node=node) + else: + for rnode in returns: + if not (isinstance(rnode.value, astng.Name) + and rnode.value.name == 'None'): + break + else: + self.add_message('W1111', node=node) + + def visit_callfunc(self, node): + """check that called method are infered to callable objects + """ + called = safe_infer(node.node) + # only function, generator and object defining __call__ are allowed + if called is not None and not called.callable(): + self.add_message('E1102', node=node, args=node.node.as_string()) + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(TypeChecker(linter)) diff --git a/checkers/utils.py b/checkers/utils.py new file mode 100644 index 0000000..575a715 --- /dev/null +++ b/checkers/utils.py @@ -0,0 +1,181 @@ +# pylint: disable-msg=W0611 +# +# Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""some functions that may be usefull for various checkers +""" + +from logilab.common import flatten + +from logilab import astng +from logilab.astng.utils import are_exclusive +try: + # python >= 2.4 + COMP_NODE_TYPES = (astng.ListComp, astng.GenExpr) + FOR_NODE_TYPES = (astng.For, astng.ListCompFor, astng.GenExprFor) +except AttributeError: + COMP_NODE_TYPES = astng.ListComp + FOR_NODE_TYPES = (astng.For, astng.ListCompFor) + +def safe_infer(node): + """return the infered value for the given node. + Return None if inference failed or if there is some ambiguity (more than + one node has been infered) + """ + try: + inferit = node.infer() + value = inferit.next() + except astng.InferenceError: + return + try: + inferit.next() + return # None if there is ambiguity on the infered node + except StopIteration: + return value + +def is_super(node): + """return True if the node is referencing the "super" builtin function + """ + if getattr(node, 'name', None) == 'super' and \ + node.root().name == '__builtin__': + return True + return False + +def is_error(node): + """return true if the function does nothing but raising an exception""" + for child_node in node.code.getChildNodes(): + if isinstance(child_node, astng.Raise): + return True + return False + +def is_raising(stmt): + """return true if the given statement node raise an exception + """ + for node in stmt.nodes: + if isinstance(node, astng.Raise): + return True + return False + +def is_empty(node): + """return true if the given node does nothing but 'pass'""" + children = node.getChildNodes() + return len(children) == 1 and isinstance(children[0], astng.Pass) + +builtins = __builtins__.copy() +SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__') + +def is_builtin(name): # was is_native_builtin + """return true if <name> could be considered as a builtin defined by python + """ + if builtins.has_key(name): + return True + if name in SPECIAL_BUILTINS: + return True + return False + +def is_defined_before(var_node, comp_node_types=COMP_NODE_TYPES): + """return True if the variable node is defined by a parent node (list + or generator comprehension, lambda) or in a previous sibling node + one the same line (statement_defining ; statement_using) + """ + varname = var_node.name + _node = var_node.parent + while _node: + if isinstance(_node, comp_node_types): + for ass_node in _node.nodes_of_class(astng.AssName): + if ass_node.name == varname: + return True + elif isinstance(_node, astng.For): + for ass_node in _node.assign.nodes_of_class(astng.AssName): + if ass_node.name == varname: + return True + elif isinstance(_node, (astng.Lambda, astng.Function)): + if varname in flatten(_node.argnames): + return True + if getattr(_node, 'name', None) == varname: + return True + break + _node = _node.parent + # possibly multiple statements on the same line using semi colon separator + stmt = var_node.statement() + _node = stmt.previous_sibling() + lineno = stmt.lineno + while _node and _node.lineno == lineno: + for ass_node in _node.nodes_of_class(astng.AssName): + if ass_node.name == varname: + return True + for imp_node in _node.nodes_of_class( (astng.From, astng.Import)): + if varname in [name[1] or name[0] for name in imp_node.names]: + return True + _node = _node.previous_sibling() + return False + +def is_func_default(node): + """return true if the name is used in function default argument's value + """ + parent = node.parent + if parent is None: + return 0 + if isinstance(parent, astng.Function) and parent.defaults and \ + node in parent.defaults: + return 1 + return is_func_default(parent) + +def is_ancestor_name(frame, node): + """return True if `frame` is a astng.Class node with `node` in the + subtree of its bases attribute + """ + try: + bases = frame.bases + except AttributeError: + return False + for base in bases: + if node in base.nodes_of_class(astng.Name): + return True + return False + +def assign_parent(node): + """return the higher parent which is not an AssName, AssTuple or AssList + node + """ + while node and isinstance(node, (astng.AssName, + astng.AssTuple, + astng.AssList)): + node = node.parent + return node + +def overrides_an_abstract_method(class_node, name): + """return True if pnode is a parent of node""" + for ancestor in class_node.ancestors(): + if name in ancestor and isinstance(ancestor[name], astng.Function) and \ + ancestor[name].is_abstract(pass_is_abstract=False): + return True + return False + +def overrides_a_method(class_node, name): + """return True if <name> is a method overridden from an ancestor""" + for ancestor in class_node.ancestors(): + if name in ancestor and isinstance(ancestor[name], astng.Function): + return True + return False + +def display_type(node): + """return the type of this node for screen display""" + if isinstance(node, astng.Instance): + return 'Instance of' + elif isinstance(node, astng.Module): + return 'Module' + return 'Class' diff --git a/checkers/variables.py b/checkers/variables.py new file mode 100644 index 0000000..71fb0eb --- /dev/null +++ b/checkers/variables.py @@ -0,0 +1,444 @@ +# Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""variables checkers for Python code +""" + +from copy import copy + +from logilab.common.compat import enumerate +from logilab import astng +from logilab.astng.lookup import builtin_lookup + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker +from pylint.checkers.utils import is_error, is_builtin, is_func_default, \ + is_ancestor_name, assign_parent, are_exclusive, \ + is_defined_before #, is_parent, FOR_NODE_TYPES + + + +MSGS = { + 'E0601': ('Using variable %r before assignment', + 'Used when a local variable is accessed before it\'s \ + assignment.'), + 'E0602': ('Undefined variable %r', + 'Used when an undefined variable is accessed.'), + + 'E0611': ('No name %r in module %r', + 'Used when a name cannot be found in a module.'), + + 'W0601': ('Global variable %r undefined at the module level', + 'Used when a variable is defined through the "global" statement \ + but the variable is not defined in the module scope.'), + 'W0602': ('Using global for %r but no assigment is done', + 'Used when a variable is defined through the "global" statement \ + but no assigment to this variable is done.'), + 'W0603': ('Using the global statement', # W0121 + 'Used when you use the "global" statement to update a global \ + variable. PyLint just try to discourage this \ + usage. That doesn\'t mean you can not use it !'), + 'W0604': ('Using the global statement at the module level', # W0103 + 'Used when you use the "global" statement at the module level \ + since it has no effect'), + 'W0611': ('Unused import %s', + 'Used when an imported module or variable is not used.'), + 'W0612': ('Unused variable %r', + 'Used when a variable is defined but not used.'), + 'W0613': ('Unused argument %r', + 'Used when a function or method argument is not used.'), + 'W0614': ('Unused import %s from 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)', + 'Used when a variable\'s name hide a name defined in the outer \ + scope.'), + 'W0622': ('Redefining built-in %r', + 'Used when a variable or function override a built-in.'), + + 'W0631': ('Using possibly undefined loop variable %r', + 'Used when an loop variable (i.e. defined by a for loop or \ + a list comprehension or a generator expression) is used outside \ + the loop.'), + } + +class VariablesChecker(BaseChecker): + """checks for + * unused variables / imports + * undefined variables + * redefinition of variable from builtins or from an outer scope + * use of variable before assigment + """ + + __implements__ = IASTNGChecker + + name = 'variables' + msgs = MSGS + priority = -1 + options = ( + ("init-import", + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'help' : 'Tells wether we should check for unused import in \ +__init__ files.'}), + ("dummy-variables-rgx", + {'default': ('_|dummy'), + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'A regular expression matching names used \ + for dummy variables (i.e. not used).'}), + ("additional-builtins", + {'default': (), 'type' : 'csv', + 'metavar' : '<comma separated list>', + 'help' : 'List of additional names supposed to be defined in \ +builtins. Remember that you should avoid to define new builtins when possible.' + }), + ) + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self._to_consume = None + self._checking_mod_attr = None + self._vars = None + + def visit_module(self, node): + """visit module : update consumption analysis variable + checks globals doesn't overrides builtins + """ + self._to_consume = [(copy(node.locals), {}, 'module')] + self._vars = [] + for name, stmts in node.locals.items(): + if name in ('__name__', '__doc__', '__file__', '__path__') \ + and len(stmts) == 1: + # only the definition added by the astng builder, continue + continue + if self._is_builtin(name): + self.add_message('W0622', args=name, node=stmts[0]) + + def leave_module(self, node): + """leave module: check globals + """ + assert len(self._to_consume) == 1 + not_consumed = self._to_consume.pop()[0] + # don't check unused imports in __init__ files + if not self.config.init_import and node.package: + return + for name, stmts in not_consumed.items(): + stmt = stmts[0] + if isinstance(stmt, astng.Import): + self.add_message('W0611', args=name, node=stmt) + elif isinstance(stmt, astng.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 + del self._vars + + def visit_class(self, node): + """visit class: update consumption analysis variable + """ + self._to_consume.append((copy(node.locals), {}, 'class')) + + def leave_class(self, _): + """leave class: update consumption analysis variable + """ + # do not check for not used locals here (no sense) + self._to_consume.pop() + + def visit_lambda(self, node): + """visit lambda: update consumption analysis variable + """ + self._to_consume.append((copy(node.locals), {}, 'lambda')) + + def leave_lambda(self, _): + """leave lambda: update consumption analysis variable + """ + # do not check for not used locals here + self._to_consume.pop() + + def visit_genexpr(self, node): + """visit genexpr: update consumption analysis variable + """ + self._to_consume.append((copy(node.locals), {}, 'genexpr')) + + def leave_genexpr(self, _): + """leave genexpr: update consumption analysis variable + """ + # do not check for not used locals here + self._to_consume.pop() + + def visit_function(self, node): + """visit function: update consumption analysis variable and check locals + """ + globs = node.root().globals + for name, stmt in node.items(): + if globs.has_key(name) and not isinstance(stmt, astng.Global): + line = globs[name][0].lineno + self.add_message('W0621', args=(name, line), node=stmt) + elif self._is_builtin(name): + self.add_message('W0622', args=name, node=stmt) + self._to_consume.append((copy(node.locals), {}, 'function')) + self._vars.append({}) + + def leave_function(self, node): + """leave function: check function's locals are consumed + """ + not_consumed = self._to_consume.pop()[0] + self._vars.pop(0) + is_method = node.is_method() + klass = node.parent.frame() + # don't check arguments of abstract methods or within an interface + if is_method and (klass.type == 'interface' or node.is_abstract()): + return + if is_error(node): + return + authorized_rgx = self.config.dummy_variables_rgx + for name, stmts in not_consumed.items(): + # ignore some special names specified by user configuration + if authorized_rgx.match(name): + continue + # ignore names imported by the global statement + # FIXME: should only ignore them if it's assigned latter + stmt = stmts[0] + if isinstance(stmt, astng.Global): + continue + # care about functions with unknown argument (builtins) + if node.argnames is not None and name in node.argnames: + # don't warn if the first argument of a method is not used + if is_method and node.argnames and name == node.argnames[0]: + continue + # don't check callback arguments + if node.name.startswith('cb_') or \ + node.name.endswith('_cb'): + continue + self.add_message('W0613', args=name, node=node) + else: + self.add_message('W0612', args=name, node=stmt) + + def visit_global(self, node): + """check names imported exists in the global scope""" + frame = node.frame() + if isinstance(frame, astng.Module): + self.add_message('W0604', node=node) + return + module = frame.root() + default_message = True + for name in node.names: + try: + assign_nodes = module.getattr(name) + except astng.NotFoundError: + # unassigned global, skip + assign_nodes = [] + for anode in assign_nodes: + if anode.frame() is frame: + # same scope level assigment + break + else: + # global but no assigment + self.add_message('W0602', args=name, node=node) + default_message = False + if not assign_nodes: + continue + for anode in assign_nodes: + if anode.frame() is module: + # module level assigment + break + else: + # global undefined at the module scope + self.add_message('W0601', args=name, node=node) + default_message = False + if default_message: + self.add_message('W0603', node=node) + + def _loopvar_name(self, node, name): + # filter variables according to node's scope + astmts = [stmt for stmt in node.lookup(name)[1] + if hasattr(stmt, 'ass_type') and + not stmt.statement().parent_of(node)] + # filter variables according their respective scope + if not astmts or astmts[0].statement().parent_of(node): + _astmts = [] + else: + _astmts = astmts[:1] + for i, stmt in enumerate(astmts[1:]): + if astmts[i].statement().parent_of(stmt): + continue + _astmts.append(stmt) + astmts = _astmts + if len(astmts) == 1: + ass = astmts[0].ass_type() + if isinstance(ass, (astng.For, astng.ListCompFor, astng.GenExpr)) \ + and not ass.statement() is node.statement(): + self.add_message('W0631', args=name, node=node) + + def visit_name(self, node): + """check that a name is defined if the current scope and doesn't + redefine a built-in + """ + name = node.name + stmt = node.statement() + frame = stmt.scope() + # if the name node is used as a function default argument's value, then + # start from the parent frame of the function instead of the function + # frame + if is_func_default(node) or is_ancestor_name(frame, node): + start_index = len(self._to_consume) - 2 + else: + start_index = len(self._to_consume) - 1 + # iterates through parent scopes, from the inner to the outer + for i in range(start_index, -1, -1): + to_consume, consumed, scope_type = self._to_consume[i] + # if the current scope is a class scope but it's not the inner + # scope, ignore it. This prevents to access this scope instead of + # the globals one in function members when there are some common + # names + if scope_type == 'class' and i != start_index: + continue + # the name has already been consumed, only check it's not a loop + # variable used outside the loop + if consumed.has_key(name): + self._loopvar_name(node, name) + break + # mark the name as consumed if it's defined in this scope + # (ie no KeyError is raise by "to_consume[name]" + try: + consumed[name] = to_consume[name] + # checks for use before assigment + # FIXME: the last condition should just check attribute access + # is protected by a try: except NameError: (similar to #9219) + defnode = assign_parent(to_consume[name][0]) + if defnode is not None: + defstmt = defnode.statement() + defframe = defstmt.frame() + maybee0601 = True + if not frame is defframe: + maybee0601 = False + elif defframe.parent is None: + # we are at the module level, check the name is not + # defined in builtins + if builtin_lookup(name)[1]: + maybee0601 = False + else: + # we are in a local scope, check the name is not + # defined in global or builtin scope + if defframe.root().lookup(name)[1]: + maybee0601 = False + if (maybee0601 + and stmt.source_line() <= defstmt.source_line() + and not is_defined_before(node) + and not are_exclusive(stmt, defstmt)): + self.add_message('E0601', args=name, node=node) + del to_consume[name] + # check it's not a loop variable used outside the loop + self._loopvar_name(node, name) + break + except KeyError: + continue + else: + # we have not found the name, if it isn't a builtin, that's an + # undefined name ! + if not self._is_builtin(name): + self.add_message('E0602', args=name, node=node) + + def visit_import(self, node): + """check modules attribute accesses""" + for name, _ in node.names: + parts = name.split('.') + try: + module = node.infer_name_module(parts[0]).next() + except astng.ResolveError: + continue + self._check_module_attrs(node, module, parts[1:]) + + def visit_from(self, node): + """check modules attribute accesses""" + name_parts = node.modname.split('.') + try: + module = node.root().import_module(name_parts[0]) + except KeyboardInterrupt: + raise + except: + return + module = self._check_module_attrs(node, module, name_parts[1:]) + if not module: + return + for name, _ in node.names: + if name == '*': + continue + self._check_module_attrs(node, module, name.split('.')) + +## def leave_getattr(self, node): +## """check modules attribute accesses + +## this function is a "leave_" because when parsing 'a.b.c' +## we want to check the innermost expression first. +## """ +## if isinstance(node.expr, astng.Name): +## try: +## module = node.expr.infer().next() +## except astng.InferenceError: +## return +## if not isinstance(module, astng.Module): +## # Not a module, don't check +## return +## elif self._checking_mod_attr is not None: +## module = self._checking_mod_attr +## else: +## return +## self._checking_mod_attr = self._check_module_attrs(node, module, +## [node.attrname]) + +## def leave_default(self, node): +## """by default, reset the _checking_mod_attr attribute""" +## self._checking_mod_attr = None + + def _check_module_attrs(self, node, module, module_names): + """check that module_names (list of string) are accessible through the + given module + if the latest access name corresponds to a module, return it + """ + assert isinstance(module, astng.Module), module + while module_names: + name = module_names.pop(0) + if name == '__dict__': + module = None + break + try: + module = module.getattr(name)[0].infer().next() + except astng.NotFoundError: + self.add_message('E0611', args=(name, module.name), node=node) + return None + except astng.InferenceError: + return None + if module_names: + # FIXME: other message if name is not the latest part of + # module_names ? + modname = module and module.name or '__dict__' + self.add_message('E0611', node=node, + args=('.'.join(module_names), modname)) + return None + if isinstance(module, astng.Module): + return module + return None + + def _is_builtin(self, name): + """return True if the name is defined in the native builtin or + in the user specific builtins + """ + return is_builtin(name) or name in self.config.additional_builtins + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(VariablesChecker(linter)) diff --git a/config.py b/config.py new file mode 100644 index 0000000..84bd893 --- /dev/null +++ b/config.py @@ -0,0 +1,148 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + + utilities for PyLint configuration : + _ pylintrc + _ pylint.d (PYLINT_HOME) +""" + +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 os.environ.has_key('PYLINTHOME'): + 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') + +if not exists(PYLINT_HOME): + try: + os.mkdir(PYLINT_HOME) + except OSError: + print >> sys.stderr, 'Unable to create directory %s' % PYLINT_HOME + +def get_pdata_path(base_name, recurs): + """return the path of the file which should contain old search data for the + given base_name with the given options values + """ + base_name = base_name.replace(os.sep, '_') + return join(PYLINT_HOME, "%s%s%s"%(base_name, recurs, '.stats')) + +def load_results(base): + """try to unpickle and return data from file if it exists and is not + corrupted + + return an empty dictionary if it doesn't exists + """ + data_file = get_pdata_path(base, 1) + try: + return pickle.load(open(data_file)) + except: + return {} + +def save_results(results, base): + """pickle results""" + data_file = get_pdata_path(base, 1) + try: + pickle.dump(results, open(data_file, 'w')) + except OSError: + print >> sys.stderr, 'Unable to create file %s' % data_file + +# location of the configuration file ########################################## + + +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 os.environ.has_key('PYLINTRC') and exists(os.environ['PYLINTRC']): + pylintrc = os.environ['PYLINTRC'] + else: + if USER_HOME == '~' or USER_HOME == '/root': + pylintrc = ".pylintrc" + else: + pylintrc = join(USER_HOME, '.pylintrc') + if not isfile(pylintrc): + if isfile('/etc/pylintrc'): + pylintrc = '/etc/pylintrc' + else: + pylintrc = None + return pylintrc + +PYLINTRC = find_pylintrc() + +ENV_HELP = ''' +The following environment variables are used : + * PYLINTHOME + path to the directory where data of persistent run will be stored. If not +found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working +directory) . The current PYLINTHOME is %(PYLINT_HOME)s. + * PYLINTRC + path to the configuration file. If not found, it will use the first +existant file in ~/.pylintrc, /etc/pylintrc. The current PYLINTRC is +%(PYLINTRC)s. +''' % globals() + +# evaluation messages ######################################################### + +def get_note_message(note): + """return a message according to note + note is a float < 10 (10 is the highest note) + """ + assert note <= 10, "Note is %.2f. Either you cheated, or pylint's \ +broken!" % note + if note < 0: + msg = 'You have to do something quick !' + elif note < 1: + msg = 'Hey! This is really dreadful. Or maybe pylint is buggy?' + elif note < 2: + msg = "Come on! You can't be proud of this code" + elif note < 3: + msg = 'Hum... Needs work.' + elif note < 4: + msg = 'Wouldn\'t you be a bit lazy?' + elif note < 5: + msg = 'A little more work would make it acceptable.' + elif note < 6: + msg = 'Just the bare minimum. Give it a bit more polish. ' + elif note < 7: + msg = 'This is okay-ish, but I\'m sure you can do better.' + elif note < 8: + msg = 'If you commit now, people should not be making nasty \ +comments about you on c.l.py' + elif note < 9: + msg = 'That\'s pretty good. Good work mate.' + elif note < 10: + msg = 'So close to being perfect...' + else: + msg = 'Wow ! Now this deserves our uttermost respect.\nPlease send \ +your code to python-projects@logilab.org' + return msg diff --git a/debian/NEWS.Debian b/debian/NEWS.Debian new file mode 100644 index 0000000..0a7e3eb --- /dev/null +++ b/debian/NEWS.Debian @@ -0,0 +1,10 @@ +pylint (0.13.2-2) unstable; urgency=low + + * pylint.el is no longer installed for emacs, because it is not + compatible with emacs22 and has a large number of related bugs. It + will be reenabled in a future version, when all the bugs have been + dealt with by upstream (patches are welcome by the way). Emacs21 + users may get it from /usr/share/doc/pylint/examples/pylint.el + + -- Alexandre Fayolle <afayolle@debian.org> Wed, 04 Jul 2007 12:18:23 +0200 + diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..99d4b45 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,318 @@ +pylint (0.14.0-3) unstable; urgency=low + + * Acknowledge NMU: thanks a lot, your patches have been backported in + upstream code (closes: #427244, #448102, #415485, #431653) + + -- + +pylint (0.14.0-2.1) unstable; urgency=low + + * Non-maintainer upload. + * BF: flavor -> debian-emacs-flavor in startup + * NF: added pylint-options variable visible to users to tune up (closes: + #427244) + * BF: reenabled installation of pylint.el (closes: #448102) + * Recent upstream of pylint.el fixed few bugs which weren't closed + in the upload to Debian. Since this is first upload which enables + pylint.el, imho it is ok to close them here (closes: #415485, #431653) + + -- Yaroslav Halchenko <debian@onerussian.com> Mon, 03 Mar 2008 22:22:07 -0500 + +pylint (0.14.0-2) unstable; urgency=low + + * Acknowledge NMU by Kumar Appaiah <akumar@ee.iitm.ac.in> (Closes: #454401) + * Debian upload of the new upstream release + * Install modules for all available python versions (Closes: #438438) + * new standards version, no changes required + + -- Alexandre Fayolle <afayolle@debian.org> Wed, 13 Feb 2008 17:36:30 +0100 + +pylint (0.14.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <Sylvain.Thenault@logilab.fr> Mon, 14 Jan 2008 13:34:24 +0100 + +pylint (0.13.2-2.1) unstable; urgency=low + + * Non-maintainer upload. + * debian/pylint.postrm: + + Added to remove old Emacs mode upon purge. + (Closes: #454401) + + -- Kumar Appaiah <akumar@ee.iitm.ac.in> Thu, 31 Jan 2008 22:27:18 +0530 + +pylint (0.13.2-2) unstable; urgency=low + + * Upload package to debian (closes: #426418) + * tests are no longer installed in /usr/share/doc/pylint, use the source + package to get them + * pylint.el is no longer installed for emacs, because it is not + compatible with emacs22. It will be reenabled in a future version, + when all the bugs have been dealt with by upstream. Emacs21 users may + get it from /usr/share/doc/pylint/examples/pylint.el. Added a note + about this in NEWS.Debian. + * Only Recommend python-tk and give a nicer warning in the pylint-gui + script if tkinter is not available. + * Updated versions of dependencies on logilab-common and astng + + -- Alexandre Fayolle <afayolle@debian.org> Wed, 04 Jul 2007 12:21:24 +0200 + +pylint (0.13.2-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 07 Jun 2007 16:44:53 +0200 + +pylint (0.13.1-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 2 Mar 2007 08:24:08 +0100 + +pylint (0.13.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 22 Feb 2007 11:28:23 +0100 + +pylint (0.12.2-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 23 Nov 2006 16:08:28 +0100 + +pylint (0.12.1-1) unstable; urgency=low + + * new uptream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 25 Sep 2006 16:46:40 +0200 + +pylint (0.12.0-2) unstable; urgency=low + + * Upload to Debian + * Fixed the XS-Python-Version value (closes: #388158) + + -- Alexandre Fayolle <afayolle@debian.org> Tue, 19 Sep 2006 09:51:03 +0200 + +pylint (0.12.0-1) unstable; urgency=low + + * new upstream release + + -- Adrien.DiMascio <Adrien.DiMascio@logilab.fr> Thu, 10 Aug 2006 11:18:50 +0200 + +pylint (0.11.0-2) unstable; urgency=low + + * Updated standards to 3.7.2 + * use debhelper 5 + * new python policy + + -- Alexandre Fayolle <afayolle@debian.org> Thu, 15 Jun 2006 10:38:32 +0200 + +pylint (0.11.0-1) unstable; urgency=low + + * new upstream release, depending on python-astng 0.16 + * Applied Sebastian Rittau's patch to avoid NameError on InferenceError + exception (closes: #358194) + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 19 Apr 2006 18:10:47 +0200 + +pylint (0.10.0-1) unstable; urgency=low + + * new upstream release, depending on python-astng 0.15 + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 6 Mar 2006 09:43:19 +0100 + +pylint (0.9.0-3) unstable; urgency=low + + * Added missing provides/replaces/conflicts on pylint-test (closes: #352316) + + -- Alexandre Fayolle <afayolle@debian.org> Mon, 13 Feb 2006 10:07:26 +0100 + +pylint (0.9.0-2) unstable; urgency=low + + * Build a single package which installs modules in /usr/lib/site-python + (closes: #351130) + * Remove duplication from man page (closes: #349689) + * Fixed typo in control file + * upload new release to Debian + + -- Alexandre Fayolle <afayolle@debian.org> Fri, 10 Feb 2006 16:03:37 +0100 + +pylint (0.9.0-1) unstable; urgency=low + + * fix false positive with staticmethod used on a metaclass (closes: #341121) + * reorganization to install into site-python, removing the need for + pythonX.X- packages and for the pylint-common and pylint-test packages + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 10 Jan 2006 14:19:57 +0100 + +pylint (0.8.1-1) unstable; urgency=low + + * added missing dependancy to logilab-astng + * added missing .docs and .examples files + * update control'standards-version to 3.6.2 + * fixed FSF address in the copyright file + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 7 Nov 2005 15:40:52 +0100 + +pylint (0.8.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 21 Oct 2005 18:44:24 +0200 + +pylint (0.7.0-1) unstable; urgency=low + + * new upstream release (closes: #310957) + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 27 May 2005 11:17:44 +0200 + +pylint (0.6.4-1) unstable; urgency=low + + * new upstream release + * added man page for pylint + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 14 Apr 2005 12:02:15 +0200 + +pylint (0.6.3-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 24 Feb 2005 17:44:35 +0100 + +pylint (0.6.2-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 16 Feb 2005 12:00:47 +0100 + +pylint (0.6.1-1) unstable; urgency=low + + * new upstream release + * added option to specify rc file location (closes: #265159) + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 4 Feb 2005 16:48:09 +0100 + +pylint (0.6.0-1) unstable; urgency=low + + * new upstream release + * build package for python 2.4 + * remove unused directory from logilab-common.dirs + * updated copyright + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 20 Jan 2005 18:06:29 +0100 + +pylint (0.5.0-2) unstable; urgency=low + + * Patched pylint.el using latest upsrteam CVS (closes: #280870) + + -- Alexandre Fayolle <afayolle@debian.org> Mon, 15 Nov 2004 10:59:51 +0100 + +pylint (0.5.0-1) unstable; urgency=low + + * use Build-depends instead of Build-depends-indep in control + * new upstream release + * updated debian/watch file to version 2 + + -- Alexandre Fayolle <afayolle@debian.org> Tue, 9 Nov 2004 16:22:47 +0100 + +pylint (0.4.2-2) unstable; urgency=low + + * fixed typos in debian/control (closes: #265156) + * updated description of pylint-test + * changed dependency on pylint-common to a recommendation (closes: #265157) + * updated maintainer address + + -- Alexandre Fayolle <afayolle@debian.org> Sun, 15 Aug 2004 10:39:06 +0200 + +pylint (0.4.2-1) unstable; urgency=low + + * new upstream release + * initial upload to Debian (closes: #258235) + + -- Alexandre Fayolle <alexandre.fayolle@logilab.fr> Thu, 8 Jul 2004 12:54:18 +0200 + +pylint (0.4.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 10 May 2004 17:03:04 +0200 + +pylint (0.3.3-1) unstable; urgency=low + + * new upstream release + * emacs lisp for pylint in a new pylint-common package + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 16 Feb 2004 18:09:23 +0100 + +pylint (0.3.2-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 23 Dec 2003 14:56:04 +0100 + +pylint (0.3.1-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 5 Dec 2003 16:20:44 +0100 + +pylint (0.3.0-1) unstable; urgency=low + + * new upstream release + * depends on logilab.common >= 0.4 + * build depends on debhelper >= 4.0 + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 19 Nov 2003 11:07:45 +0100 + +pylint (0.2.1-2) unstable; urgency=low + + * fixed dependency on logilab-common (>=0.3.4) since earlier versions + caused bugs with some python2.3 code + * included sample pylintrc files with the documentation + * added documentation that had disappeared in the previous 0.2.1-1 + * only puts html documentation in doc/html/, all others in doc/ + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 10 Oct 2003 12:11:53 +0200 + +pylint (0.2.1-1) unstable; urgency=low + + * new upstream release + * package renamed to pylint instead of logilab-pylint + * move tests in a separated package + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 10 Oct 2003 09:39:22 +0200 + +logilab-pylint (0.2.0-1) unstable; urgency=low + + * new upstream release + * dropped python2.1 support + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 12 Sep 2003 18:26:15 +0200 + +logilab-pylint (0.1.2-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 11 Jun 2003 15:21:44 +0200 + +logilab-pylint (0.1.1-2) unstable; urgency=low + + * fix dependencie to logilab.common + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 4 Jun 2003 18:07:45 +0200 + +logilab-pylint (0.1.1-1) unstable; urgency=low + + * New upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 19 May 2003 15:10:25 +0200 + +logilab-pylint (0.1.0-1) unstable; urgency=low + + * Initial Release. + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 23 Apr 2003 14:42:05 +0200 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..76b38b3 --- /dev/null +++ b/debian/control @@ -0,0 +1,36 @@ +Source: pylint +Section: python +Priority: optional +Maintainer: Sylvain Thénault <sylvain.thenault@logilab.fr> +Uploaders: Alexandre Fayolle <afayolle@debian.org> +Build-Depends: debhelper (>= 5.0.38) +Build-Depends-Indep: python (>=2.3.5-7), python-central (>=0.5.6) +Standards-Version: 3.7.3 +Homepage: http://www.logilab.org/Project/name/pylint +XS-Python-Version: all + +Package: pylint +Architecture: all +Depends: python, python-logilab-common (>= 0.22.2), python-logilab-astng (>= 0.17.1) +Recommends: python-tk +XB-Python-Version: ${python:Versions} +Conflicts: python2.2-pylint, python2.3-pylint, python2.4-pylint, pylint-common, pylint-test +Replaces: python2.2-pylint, python2.3-pylint, python2.4-pylint, pylint-common, pylint-test +Description: python code static checker + Pylint is a Python source code analyzer which looks for programming + errors, helps enforcing a coding standard and sniffs for some code + smells (as defined in Martin Fowler's Refactoring book) + . + Pylint can be seen as another PyChecker since nearly all tests you + can do with PyChecker can also be done with Pylint. However, Pylint + offers some more features, like checking length of lines of code, + checking if variable names are well-formed according to your coding + standard, or checking if declared interfaces are truly implemented, + and much more. + . + Additionally, it is possible to write plugins to add your own checks. + . + The recommended python-tk package is only for using the pylint-gui + script. + + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..e574a13 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,29 @@ +This package was debianized by Sylvain Thenault <sylvain.thenault@logilab.fr> Sat, 13 Apr 2002 19:05:23 +0200. + +It was downloaded from ftp://ftp.logilab.org/pub/pylint + +Upstream Author: + + Sylvain Thenault <sylvain.thenault@logilab.fr> + +Copyright: + +Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com). +Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE). +http://www.logilab.fr/ -- mailto:contact@logilab.fr + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +On Debian systems, the complete text of the GNU General Public License +may be found in '/usr/share/common-licenses/GPL'. diff --git a/debian/pycompat b/debian/pycompat new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/debian/pycompat @@ -0,0 +1 @@ +2 diff --git a/debian/pylint.dirs b/debian/pylint.dirs new file mode 100644 index 0000000..6a39af5 --- /dev/null +++ b/debian/pylint.dirs @@ -0,0 +1,2 @@ +usr/share/doc/pylint/test +usr/share/emacs/site-lisp/pylint diff --git a/debian/pylint.docs b/debian/pylint.docs new file mode 100644 index 0000000..27ad787 --- /dev/null +++ b/debian/pylint.docs @@ -0,0 +1,6 @@ +doc/features.html +doc/quickstart.html +doc/FAQ.html +doc/FAQ.txt +doc/quickstart.txt +doc/features.txt diff --git a/debian/pylint.emacsen-install b/debian/pylint.emacsen-install new file mode 100644 index 0000000..66a8480 --- /dev/null +++ b/debian/pylint.emacsen-install @@ -0,0 +1,45 @@ +#! /bin/sh -e +# /usr/lib/emacsen-common/packages/install/#PACKAGE# + +# Written by Jim Van Zandt <jrv@vanzandt.mv.com>, borrowing heavily +# from the install scripts for gettext by Santiago Vila +# <sanvila@ctv.es> and octave by Dirk Eddelbuettel <edd@debian.org>. + +FLAVOR=$1 +PACKAGE=pylint + +if [ ${FLAVOR} = emacs ]; then exit 0; fi + +echo install/${PACKAGE}: Handling install for emacsen flavor ${FLAVOR} + +#FLAVORTEST=`echo $FLAVOR | cut -c-6` +#if [ ${FLAVORTEST} = xemacs ] ; then +# SITEFLAG="-no-site-file" +#else +# SITEFLAG="--no-site-file" +#fi +FLAGS="${SITEFLAG} -q -batch -l path.el -f batch-byte-compile" + +ELDIR=/usr/share/emacs/site-lisp/${PACKAGE} +ELCDIR=/usr/share/${FLAVOR}/site-lisp/${PACKAGE} + +# Install-info-altdir does not actually exist. +# Maybe somebody will write it. +if test -x /usr/sbin/install-info-altdir; then + echo install/${PACKAGE}: install Info links for ${FLAVOR} + install-info-altdir --quiet --section "" "" --dirname=${FLAVOR} /usr/info/${PACKAGE}.info.gz +fi + +install -m 755 -d ${ELCDIR} +cd ${ELDIR} +FILES=`echo *.el` +cp ${FILES} ${ELCDIR} +cd ${ELCDIR} + +cat << EOF > path.el +(setq load-path (cons "." load-path) byte-compile-warnings nil) +EOF +${FLAVOR} ${FLAGS} ${FILES} +rm -f *.el path.el + +exit 0 diff --git a/debian/pylint.emacsen-remove b/debian/pylint.emacsen-remove new file mode 100644 index 0000000..9795dc4 --- /dev/null +++ b/debian/pylint.emacsen-remove @@ -0,0 +1,14 @@ +#!/bin/sh -e + +FLAVOR=$1 +PACKAGE=pylint + +if [ ${FLAVOR} != emacs ]; then + if test -x /usr/sbin/install-info-altdir; then + echo remove/${PACKAGE}: removing Info links for ${FLAVOR} + install-info-altdir --quiet --remove --dirname=${FLAVOR} /usr/info/${PACKAGE}.info.gz + fi + + echo remove/${PACKAGE}: purging byte-compiled files for ${FLAVOR} + rm -rf /usr/share/${FLAVOR}/site-lisp/${PACKAGE} +fi diff --git a/debian/pylint.emacsen-startup b/debian/pylint.emacsen-startup new file mode 100644 index 0000000..646bd81 --- /dev/null +++ b/debian/pylint.emacsen-startup @@ -0,0 +1,17 @@ +;; -*-emacs-lisp-*- +;; +;; Emacs startup file for the Debian GNU/Linux pylint package +;; +;; Originally contributed by Nils Naumann <naumann@unileoben.ac.at> +;; Modified by Dirk Eddelbuettel <edd@debian.org> +;; Adapted for dh-make by Jim Van Zandt <jrv@vanzandt.mv.com> + +;; The pylint package follows the Debian/GNU Linux 'emacsen' policy and +;; byte-compiles its elisp files for each 'emacs flavor' (emacs19, +;; xemacs19, emacs20, xemacs20...). The compiled code is then +;; installed in a subdirectory of the respective site-lisp directory. +;; We have to add this to the load-path: +(setq load-path (cons (concat "/usr/share/" + (symbol-name flavor) + "/site-lisp/pylint") load-path)) +(load-library "pylint") diff --git a/debian/pylint.examples b/debian/pylint.examples new file mode 100644 index 0000000..bc35a69 --- /dev/null +++ b/debian/pylint.examples @@ -0,0 +1,2 @@ +examples/* +elisp/pylint.el diff --git a/debian/pylint.manpages b/debian/pylint.manpages new file mode 100644 index 0000000..12b9a33 --- /dev/null +++ b/debian/pylint.manpages @@ -0,0 +1 @@ +man/pylint.1 diff --git a/debian/pylint.postinst b/debian/pylint.postinst new file mode 100644 index 0000000..c426afd --- /dev/null +++ b/debian/pylint.postinst @@ -0,0 +1,6 @@ +#! /bin/sh -e +# + +#DEBHELPER# + +exit 0 diff --git a/debian/pylint.postrm b/debian/pylint.postrm new file mode 100644 index 0000000..35920aa --- /dev/null +++ b/debian/pylint.postrm @@ -0,0 +1,8 @@ +#! /bin/sh -e + +if [ "$1" = "purge" ]; then + # remove old emacs file + rm -f /etc/emacs/site-start.d/50pylint.el +fi + +#DEBHELPER# diff --git a/debian/pylint.prerm b/debian/pylint.prerm new file mode 100644 index 0000000..c426afd --- /dev/null +++ b/debian/pylint.prerm @@ -0,0 +1,6 @@ +#! /bin/sh -e +# + +#DEBHELPER# + +exit 0 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..06c3942 --- /dev/null +++ b/debian/rules @@ -0,0 +1,78 @@ +#!/usr/bin/make -f +# Sample debian/rules that uses debhelper. +# GNU copyright 1997 to 1999 by Joey Hess. +# +# adapted by Logilab for automatic generation by debianize +# (part of the devtools project, http://www.logilab.org/projects/devtools) +# +# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 +build: build-stamp +build-stamp: + dh_testdir + python setup.py -q build + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + rm -rf build + find . -name "*.pyc" | xargs rm -f + rm -f changelog.gz + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + python setup.py -q install --no-compile --prefix=debian/pylint/usr/ + + rm -rf debian/pylint/usr/lib/python*/site-packages/pylint/test + if head -1 debian/pylint/usr/bin/pylint | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \ + sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/pylint; \ + fi + chmod a+x debian/pylint/usr/bin/pylint + if head -1 debian/pylint/usr/bin/pylint-gui | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \ + sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/pylint-gui; \ + fi + chmod a+x debian/pylint/usr/bin/pylint-gui + if head -1 debian/pylint/usr/bin/symilar | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \ + sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/symilar; \ + fi + chmod a+x debian/pylint/usr/bin/symilar + install -m 644 elisp/pylint.el debian/pylint/usr/share/emacs/site-lisp/pylint/ + + # install tests + #(cd test && find . -type f -not \( -path '*/CVS/*' -or -name '*.pyc' \) -exec install -D --mode=644 {} ../debian/pylint/usr/share/doc/pylint/test/{} \;) + + +# Build architecture-independent files here. +binary-indep: build install + dh_testdir + dh_testroot + dh_install -i + dh_pycentral -i + gzip -9 -c ChangeLog > changelog.gz + dh_installchangelogs -i + dh_installexamples -i + dh_installdocs -i README TODO changelog.gz debian/NEWS.Debian + dh_installman -i + dh_installemacsen + dh_link -i + dh_compress -i -X.py -X.ini -X.xml -Xtest + dh_fixperms -i + dh_installdeb -i + dh_gencontrol -i + dh_md5sums -i + dh_builddeb -i + +binary-arch: + +binary: binary-indep +.PHONY: build clean binary binary-indep binary-arch + diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..52cc0a2 --- /dev/null +++ b/debian/watch @@ -0,0 +1,3 @@ +version=2 +ftp://ftp.logilab.org/pub/pylint/pylint-(.*)\.tar\.gz debian uupdate + diff --git a/doc/FAQ.txt b/doc/FAQ.txt new file mode 100644 index 0000000..9fa496c --- /dev/null +++ b/doc/FAQ.txt @@ -0,0 +1,186 @@ +Frequently Asked Questions +========================== + + +Question: + Is it possible to give file as argument to pylint, instead of module ? + +Answer: + pylint expects the name of a package or module as argument. As a convenience, + you can give to it a file name if it's possible to guess a module name from + the file's path, using the python path. Some examples : + + "pylint mymodule.py" should always works since the current working + directory is automatically added on top of the python path + + "pylint directory/mymodule.py" will work if "directory" is a python + package (i.e. has an __init__.py file) or if "directory" is in the + python path. + + "pylint /whatever/directory/mymodule.py" will work if either: + + - "/whatever/directory" is in the python path + + - your cwd is "/whatever/directory" + + - "directory" is a python package and "/whatever" is in the python + path + + - "directory" is a python package and your cwd is "/whatever" + and so on... + + + +Question: + I'm using psyobj from psyco_ and get a lot of spurious "unused variables + messages". Is it normal ? + +Answer: + Yes. That's actually due to a bug in psyco, making the locals() + function for objects inheriting from *psyobj* returning an empty + dictionary. For the moment, the only way to fix this is to use the + PYLINT_IMPORT environment variable to not use psyco during pylint + checking. Sample code :: + + import os + try: + if os.environ.has_key('PYLINT_IMPORT'): + raise ImportError() + from psyco.classes import psyobj + except ImportError: + class psyobj: + pass + + NOTICE: this problem should not occurs with pylint >= 0.5 since from + this version pylint is not looking anymore for information in living + objects (i.e. it doesn't anymore import analysed modules) + + + +Question: + I've a function / method which is a callback where I do not have any + control on received argument, and pylint is complaining about unused + arguments. What can I do to avoid those warnings ? + +Answer: + prefix (ui) the callback's name by `cb_`, as in cb_onclick(...). By + doing so arguments usage won't be checked. Another solution is to + use one of the name defined in the "dummy-variables" configuration + variable for unused argument ("_" and "dummy" by default). + + + +Question: + When is pylint considering a class as an interface ? + +Answer: + A class is considered as an interface if there is a class named + "Interface" somewhere in it ancestor's tree. + + + +Question: + When is pylint considering that a class is implementing a given + interface ? + +Answer: + Pylint is using the Zope 2 interfaces conventions, and so is + considering that a class is implementing interfaces listed in its + __implements__ attribute. + + + +Question: + When is pylint considering a class as an abstract class ? + +Answer: + A class is considered as an abstract class if at least one of its + methods is doing nothing but raising NotImplementedError + + + +Question: + Is there some way to disable some message for a particular module + only ? + +Answer: + Yes, you can disable or enable (globally disabled) message at the + module level by adding the corresponding option in a comment at the + top of the file: :: + + # pylint: disable-msg=W0401, E0202 + # pylint: enable-msg=C0302 + + + +Question: + I have a mixin class relying on attributes of the mixed class, and I + would like to not have the "access to undefined member" message on + this class. Is it possible ? + +Answer: + Yes :o) To do so you have to set the ignore-mixin-members option to + "yes" (this is the default value) and to name your mixin class with + a name which ends with "mixin" (whatever case) + + + +Question: + Is it possible to locally disable a particular message for a block + of code or for a single line of code ? + +Answer: + Yes, this feature has been added in pylint 0.11. This may be done by + adding "#pylint: disable-msg=W0123,E4567" at the desired block level + or at the end of the desired line of code + + + +Question: + Where is the persistent data stored to make comparison between + two successive runs ? + +Answer: + Analysis data are stored as pickle file in a directory which is + localized using the following rules: + + * value of the PYLINTHOME environment variable if set + + * ".pylint.d" subdirectory of the user's home directory if it is found + (not always findable on Windows platforms) + + * ".pylint.d" directory in the current directory + + + +Question: + How can I know the option name (for pylintrc) corresponding to a + specific command line option ? + +Answer: + You can always generate a sample pylintrc file with --generate-rcfile + Every option present on the command line before this will be included in + the rc file + + For example:: + + pylint --disable-msg=W0702,C0103 --class-rgx='[A-Z][a-z]+' --generate-rcfile + + + +Question: + pychecker_ has no problem finding the imports and reporting on problems with + them, while pylint seems unable to deal with the same imports. Why ? + +Answer: + pychecker and pylint use different approaches. pychecker + imports the modules and rummages around in the result, hence it sees my + mangled sys.path. pylint doesn't import any of the candidate modules and + thus doesn't include any of import's side effects (good and bad). It + traverses an AST representation of the code. + + + + +.. _psyco: http://psyco.sf.net +.. _pychecker: http://pychecker.sf.net diff --git a/doc/beginner_pylint_tutorial.txt b/doc/beginner_pylint_tutorial.txt new file mode 100644 index 0000000..20875d5 --- /dev/null +++ b/doc/beginner_pylint_tutorial.txt @@ -0,0 +1,376 @@ +================================================================ +A Beginner's Guide to Code Standards in Python - Pylint Tutorial +================================================================ + +:Author: Robert Kirkpatrick + +For a detailed description of Pylint, see http://www.logilab.org/project/pylint. + + +Intro +----- + +Beginner to coding standards? Pylint can be your guide to reveal what's really +going on behind the scenes and help you to become a more aware programmer. + +Sharing code is a rewarding endeavor. Putting your code 'out there' can be +either an act of philanthropy, 'coming of age', or a basic extension of belief +in open source. Whatever the motivation, your good intentions may not have the +desired outcome if people find your code hard to use or understand. The Python +community has formalized some recommended programming styles to help everyone +write code in a common, agreed-upon style that makes the most sense for shared +code. This style is captured in PEP-8. Pylint can be a quick and easy way of +seeing if your code has captured the essence of PEP-8 and is therefore +'friendly' to other potential users. + +Perhaps you're not ready to share your code but you'd like to learn a bit more +about writing better code and don't know where to start. Pylint can tell you +where you may have run astray and point you in the direction to figure out what +you have done and how to do better. + +This tutorial is all about approaching coding standards with little or no +knowledge of in-depth programming or the code standards themselves. It's the +equivalent of skipping the manual and jumping right in. + +My command line prompt for these examples is: :: + + robertk01 Desktop$ + +Getting Started +--------------- + +Running Pylint with no arguments will invoke the help dialogue and give you a +idea of the arguments available to you. Do that now, i.e.: :: + + + robertk01 Desktop$ pylint + ... + a bunch of stuff + ... + + +A couple of the options that we'll focus on here are: :: + + Master: + --rcfile=<file> + Commands: + --help-msg=<msg-id> + Commands: + --help-msg=<msg-id> + Message control: + --disable-msg=<msg-ids> + Reports: + --files-output=<y_or_n> + --reports=<y_or_n> + --include-ids=<y_or_n> + --output-format=<format> + +Also pay attention to the last bit of help output. This gives you a hint of what +Pylint is going to 'pick on': :: + + Output: + Using the default text output, the message format is : + MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE + There are 5 kind of message types : + * (C) convention, for programming standard violation + * (R) refactor, for bad code smell + * (W) warning, for python specific problems + * (E) error, for much probably bugs in the code + * (F) fatal, if an error occured which prevented pylint from doing + further processing. + +When Pylint is first run on a fresh piece of code, a common complaint is that it +is too 'noisy'. The current default configuration is set to enforce all possible +warnings. We'll use some of the options I noted above to make it suit your +preferences a bit better (and thus make it 'scream only when needed'). + + +Your First Pylint'ing +--------------------- + +We'll use a basic python script as fodder for our tutorial. I borrowed +extensively from the code here: http://www.daniweb.com/code/snippet748.html +The starting code we will use is called simplecaeser.py and is here in it's +entirety: :: + + 1 #!/usr/bin/env python + 2 + 3 import string + 4 + 5 shift = 3 + 6 choice = raw_input("would you like to encode or decode?") + 7 word = (raw_input("Please enter text")) + 8 letters = string.ascii_letters + string.punctuation + string.digits + 9 encoded = '' + 10 if choice == "encode": + 11 for letter in word: + 12 if letter == ' ': + 13 encoded = encoded + ' ' + 14 else: + 15 x = letters.index(letter) + shift + 16 encoded=encoded + letters[x] + 17 if choice == "decode": + 18 for letter in word: + 19 if letter == ' ': + 20 encoded = encoded + ' ' + 21 else: + 22 x = letters.index(letter) - shift + 23 encoded = encoded + letters[x] + 24 + 25 print encoded + + +Let's get started. + +If we run this: :: + + robertk01 Desktop$ pylint simplecaeser.py + No config file found, using default configuration + ************* Module simplecaeser + C: 1: Missing docstring + W: 3: Uses of a deprecated module 'string' + C: 5: Invalid name "shift" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C: 6: Invalid name "choice" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C: 7: Invalid name "word" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C: 8: Invalid name "letters" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C: 9: Invalid name "encoded" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C: 16: Operator not preceded by a space + encoded=encoded + letters[x] + ^ + + + Report + ====== + 19 statements analysed. + + Duplication + ----------- + + +-------------------------+------+---------+-----------+ + | |now |previous |difference | + +=========================+======+=========+===========+ + |nb duplicated lines |0 |0 |= | + +-------------------------+------+---------+-----------+ + |percent duplicated lines |0.000 |0.000 |= | + +-------------------------+------+---------+-----------+ + + + + Raw metrics + ----------- + + +----------+-------+------+---------+-----------+ + |type |number |% |previous |difference | + +==========+=======+======+=========+===========+ + |code |21 |87.50 |21 |= | + +----------+-------+------+---------+-----------+ + |docstring |0 |0.00 |0 |= | + +----------+-------+------+---------+-----------+ + |comment |1 |4.17 |1 |= | + +----------+-------+------+---------+-----------+ + |empty |2 |8.33 |2 |= | + +----------+-------+------+---------+-----------+ + + + + Statistics by type + ------------------ + + +---------+-------+-----------+-----------+------------+---------+ + |type |number |old number |difference |%documented |%badname | + +=========+=======+===========+===========+============+=========+ + |module |1 |1 |= |0.00 |0.00 | + +---------+-------+-----------+-----------+------------+---------+ + |class |0 |0 |= |0.00 |0.00 | + +---------+-------+-----------+-----------+------------+---------+ + |method |0 |0 |= |0.00 |0.00 | + +---------+-------+-----------+-----------+------------+---------+ + |function |0 |0 |= |0.00 |0.00 | + +---------+-------+-----------+-----------+------------+---------+ + + + + Messages by category + -------------------- + + +-----------+-------+---------+-----------+ + |type |number |previous |difference | + +===========+=======+=========+===========+ + |convention |7 |7 |= | + +-----------+-------+---------+-----------+ + |refactor |0 |0 |= | + +-----------+-------+---------+-----------+ + |warning |1 |1 |= | + +-----------+-------+---------+-----------+ + |error |0 |0 |= | + +-----------+-------+---------+-----------+ + + + + Messages + -------- + + +-----------+-----------+ + |message id |occurences | + +===========+===========+ + |C0103 |5 | + +-----------+-----------+ + |W0402 |1 | + +-----------+-----------+ + |C0322 |1 | + +-----------+-----------+ + |C0111 |1 | + +-----------+-----------+ + + + + Global evaluation + ----------------- + Your code has been rated at 5.79/10 + + +Wow. That's a lot of stuff. The first part is the 'messages' section while the +second part is the 'report' section. There are two points I want to tackle here. + +First point is that all the tables of statistics (i.e. the report) are a bit +overwhelming so I want to silence them. To do that, I will use the "--reports=n" option. + +Second, previous experience taught me that the default output for the messages +needed a bit more info. We can see the first line is: :: + + "C: 1: Missing docstring" + +This basically means that line 1 violates a convention 'C'. It's telling me I +really should have a docstring. I agree, but what if I didn't fully understand +what rule I violated. Knowing only that I violated a convention isn't much help +if I'm a newbie. So let's turn on a bit more info by using the option +"--include-ids=y". + +Let's do it again! :: + + robertk01 Desktop$ pylint --reports=n --include-ids=y simplecaeser.py + No config file found, using default configuration + ************* Module simplecaeser + C0111: 1: Missing docstring + W0402: 3: Uses of a deprecated module 'string' + C0103: 5: Invalid name "shift" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C0103: 6: Invalid name "choice" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C0103: 7: Invalid name "word" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C0103: 8: Invalid name "letters" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C0103: 9: Invalid name "encoded" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C0322: 16: Operator not preceded by a space + encoded=encoded + letters[x] + +Oooh. I like that better. Now I know that I violated the convention number +C0111 and now I can read up a bit more about that. Let's go back to the +command line and try this: :: + + robertk01 Desktop$ pylint --help-msg=C0111 + No config file found, using default configuration + :C0111: *Missing docstring* + Used when a module, function, class or method has no docstring. Some special + methods like __init__ doesn't necessary require a docstring. This message + belongs to the basic checker. + +Yeah, ok. That one was a bit of a no-brainer but I have run into error messages +that left me with no clue about what went wrong, simply because I was unfamiliar +with the underlying mechanism of code theory. One error that puzzled my newbie +mind was: :: + + :R0902: *Too many instance attributes (%s/%s)* + +I get it now thanks to Pylint pointing it out to me. If you don't get that one, +pour a fresh cup of coffee and look into it - let your programmer mind grow! + + +The Next Step +------------- + +Now that we got some configuration stuff out of the way, let's see what we can +do with the remaining warnings. + +If we add a docstring to describe what the code is meant to do that will help. +I'm also going to be a bit cowboy and ignore the W0402 message because I like to +take risks in life. A deprecation warning means that future versions of Python +may not support that code so my code may break in the future. There are 5 C0103 +messages that we will get to later. Lastly, I violated the convention of using +spaces around an operator such as "=" so I'll fix that too. To sum up, I'll add +a docstring to line 2, put spaces around the = sign on line 16 and use the +"--disable-msg=W0402" to ignore the deprecation warning. + +Here's the updated code: :: + + 1 #!/usr/bin/env python + 2 """This script prompts a user to enter a messsage to encode or decode + 3 using a classic Caeser shift substitution (3 letter shift)""" + 4 + 5 import string + 6 + 7 shift = 3 + 8 choice = raw_input("would you like to encode or decode?") + 9 word = (raw_input("Please enter text")) + 10 letters = string.ascii_letters + string.punctuation + string.digits + 11 encoded = '' + 12 if choice == "encode": + 13 for letter in word: + 14 if letter == ' ': + 15 encoded = encoded + ' ' + 16 else: + 17 x = letters.index(letter) + shift + 18 encoded = encoded + letters[x] + 19 if choice == "decode": + 20 for letter in word: + 21 if letter == ' ': + 22 encoded = encoded + ' ' + 23 else: + 24 x = letters.index(letter) - shift + 25 encoded = encoded + letters[x] + 26 + 27 print encoded + +And here's what happens when we run it with our --disable-msg=W0402 option: :: + + robertk01 Desktop$ pylint --reports=n --include-ids=y --disable-msg=W0402 simplecaeser.py + No config file found, using default configuration + ************* Module simplecaeser + C0103: 7: Invalid name "shift" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C0103: 8: Invalid name "choice" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C0103: 9: Invalid name "word" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C0103: 10: Invalid name "letters" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + C0103: 11: Invalid name "encoded" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) + +Nice! We're down to just the C0103 messages. + +There are fairly well defined conventions around naming things like instance +variables, functions, classes, etc. The conventions focus on the use of +UPPERCASE and lowercase as well as the characters that separate multiple words +in the name. This lends itself well to checking via a regular expression, thus +the "should match (([A-Z\_][A-Z1-9\_]*)|(__.*__))$". + +In this case Pylint is telling me that those variables appear to be constants +and should be all UPPERCASE. This rule is in fact a naming convention that is +specific to the folks at Logilab who created Pylint. That is the way they have +chosen to name those variables. You too can create your own in-house naming +conventions but for the purpose of this tutorial, we want to stick to the PEP-8 +standard. In this case, the variables I declared should follow the convention +of all lowercase. The appropriate rule would be something like: +"should match [a-z\_][a-z0-9\_]{2,30}$". Notice the lowercase letters in the +regular expression (a-z versus A-Z). + +If we run that rule using a --const-rgx='[a-z\_][a-z0-9\_]{2,30}$' option, it +will now be quite quiet: :: + + robertk01 Desktop$ pylint --reports=n --include-ids=y --disable-msg=W0402 --const-rgx='[a-z_][a-z0-9_]{2,30}$' simplecaeser.py + No config file found, using default configuration + +Regular expressions can be quite a beast so take my word on this particular +example but go ahead and read up on them if you want. + +It would really be a pain in the butt to have to use all these options on the +command line all the time. That's what the rc file is for. We can configure +our Pylint to store our options for us so we don't have to declare them on the +command line. Using the rc file is a nice way of formalizing your rules and +quickly sharing them with others and/or forcing your style on them. Go ahead, +conquer the standards world by spreading your rc file...I dare you. + +That's it for the basic intro. More tutorials will follow. diff --git a/doc/features.txt b/doc/features.txt new file mode 100644 index 0000000..3ef5136 --- /dev/null +++ b/doc/features.txt @@ -0,0 +1,735 @@ +PyLint features +=============== + +.. contents:: + +General options +~~~~~~~~~~~~~~~ +:rcfile: + Specify a configuration file. +:init-hook: + Python code to execute, usually for sys.path manipulation such as + pygtk.require(). +:rpython-mode: + enable the rpython checker which is disabled by default +:errors-only: + In debug mode, checkers without error messages are disabled and for others, + only the ERROR messages are displayed, and no reports are done by default +:profile: + Profiled execution. +:ignore: + Add <file or directory> to the black list. It should be a base name, not a + path. You may set this option multiple times. + Default: CVS +:persistent: + Pickle collected data for later comparisons. + Default: yes +:cache-size: + Set the cache size for astng objects. + Default: 500 +:load-plugins: + List of plugins (as comma separated values of python modules names) to load, + usually to register additional checkers. + +Commands options +~~~~~~~~~~~~~~~~ +:help-msg: + Display a help message for the given message id and exit. The value may be a + comma separated list of message ids. +:list-msgs: + Generate pylint's full documentation. +: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. +:generate-man: + Generate pylint's man page. + +Messages control options +~~~~~~~~~~~~~~~~~~~~~~~~ +:enable-checker: + Enable only checker(s) with the given id(s). This option conflicts with the + disable-checker option +:disable-checker: + Enable all checker(s) except those with the given id(s). This option + conflicts with the enable-checker option +:enable-msg-cat: + Enable all messages in the listed categories. +:disable-msg-cat: + Disable all messages in the listed categories. +:enable-msg: + Enable the message(s) with the given id(s). +:disable-msg: + Disable the message(s) with the given id(s). + +Reports options +~~~~~~~~~~~~~~~ +:output-format: + Set the output format. Available formats are text, parseable, colorized, msvs + (visual studio) and html + Default: text +:include-ids: + Include message's id in output +:files-output: + 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: + Tells wether to display a full report or only the messages + Default: yes +:evaluation: + Python expression which should return a note less than 10 (10 is the highest + note). You have access to the variables errors warning, statement which + respectivly contain the number of errors / warnings messages and the total + number of statements analyzed. This is used by the global evaluation report + (R0004). + Default: 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) +:comment: + Add a comment according to your evaluation note. This is used by the global + evaluation report (R0004). +:enable-report: + Enable the report(s) with the given id(s). +:disable-report: + Disable the report(s) with the given id(s). + +Main messages +~~~~~~~~~~~~~ +:E0001: + Used when a syntax error is raised for a module. +:E0011: *Unrecognized file option %r* + Used when an unknown inline option is encountered. +:E0012: *Bad option value %r* + Used when a bad value for an inline option is encountered. +:I0001: *Unable to run raw checkers on built-in module %s* + Used to inform that a built-in module has not been checked using the raw + checkers. +:I0010: *Unable to consider inline option %r* + Used when an inline option is either badly formatted or can't be used inside + modules. +:I0011: *Locally disabling %s* + Used when an inline option disables a message or a messages category. +:I0012: *Locally enabling %s* + Used when an inline option enables a message or a messages category. +:I0013: *Ignoring entire file* + Used to inform that the file will not be checked +:F0001: + Used when an error occured preventing the analysis of a module (unable to find + it for instance). +:F0002: *%s: %s* + Used when an unexpected error occured while building the ASTNG representation. + This is usually accompanied by a traceback. Please report such errors ! +:F0003: *ignored builtin module %s* + Used to indicate that the user asked to analyze a builtin module which has + been skipped. +:F0004: *unexpected infered value %s* + Used to indicate that some value of an unexpected type has been infered. + +Main reports +~~~~~~~~~~~~ +:R0001: Messages by category +:R0002: % errors / warnings by module +:R0003: Messages +:R0004: Global evaluation + + +Basic checker +------------- +checks for : +* doc strings +* modules / classes / functions / methods / arguments / variables name +* number of arguments, local variables, branchs, returns and statements in +functions, methods +* required module attributes +* dangerous default values as arguments +* redefinition of function / method / class +* uses of the global statement + +Options +~~~~~~~ +:required-attributes: + Required attributes for module, separated by a comma +:no-docstring-rgx: + Regular expression which should only match functions or classes name which do + not require a docstring + Default: __.*__ +:module-rgx: + Regular expression which should only match correct module names + Default: `(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$` +:const-rgx: + Regular expression which should only match correct module level names + Default: `(([A-Z_][A-Z1-9_]*)|(__.*__))$` +:class-rgx: + Regular expression which should only match correct class names + Default: `[A-Z_][a-zA-Z0-9]+$` +:function-rgx: + Regular expression which should only match correct function names + Default: `[a-z_][a-z0-9_]{2,30}$` +:method-rgx: + Regular expression which should only match correct method names + Default: `[a-z_][a-z0-9_]{2,30}$` +:attr-rgx: + Regular expression which should only match correct instance attribute names + Default: `[a-z_][a-z0-9_]{2,30}$` +:argument-rgx: + Regular expression which should only match correct argument names + Default: `[a-z_][a-z0-9_]{2,30}$` +:variable-rgx: + Regular expression which should only match correct variable names + Default: `[a-z_][a-z0-9_]{2,30}$` +:inlinevar-rgx: + Regular expression which should only match correct list comprehension / + generator expression variable names + Default: `[A-Za-z_][A-Za-z0-9_]*$` +:good-names: + Good variable names which should always be accepted, separated by a comma + Default: i,j,k,ex,Run,_ +:bad-names: + Bad variable names which should always be refused, separated by a comma + Default: foo,bar,baz,toto,tutu,tata +:bad-functions: + List of builtins function names that should not be used, separated by a comma + Default: map,filter,apply,input + +Messages +~~~~~~~~ +:E0100: *__init__ method is a generator* + Used when the special class method __init__ is turned into a generator by a + yield in its body. +:E0101: *Explicit return in __init__* + Used when the special class method __init__ has an explicit return value. +:E0102: *%s already defined line %s* + Used when a function / class / method is redefined. +:E0103: *%r not properly in loop* + Used when break or continue keywords are used outside a loop. +:E0104: *return outside function* + Used when a "return" statement is found outside a function or method. +:E0105: *yield outside function* + Used when a "yield" statement is found outside a function or method. +:E0106: *return with argument inside generator* + Used when a "return" statement with an argument is found outside in a + generator function or method (e.g. with some "yield" statements). +:W0101: *Unreachable code* + Used when there is some code behind a "return" or "raise" statement, which + will never be accessed. +:W0102: *Dangerous default value %s as argument* + Used when a mutable value as list or dictionary is detected in a default value + for an argument. +:W0104: *Statement seems to have no effect* + Used when a statement doesn't have (or at least seems to) any effect. +: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: *Unnecessary semicolon* + Used when a statement is endend by a semi-colon (";"), which isn't necessary + (that's python, not C ;). +:W0107: *Unnecessary pass statement* + Used when a "pass" statement that can be avoided is encountered.) +:W0122: *Use of the exec statement* + Used when you use the "exec" statement, to discourage its usage. That doesn't + mean you can not use it ! +:W0141: *Used builtin function %r* + Used when a black listed builtin function is used (see the bad-function + option). Usual black listed functions are the ones like map, or filter , where + Python offers now some cleaner alternative like list comprehension. +:W0142: *Used * or ** magic* + Used when a function or method is called using `*args` or `**kwargs` to + dispatch arguments. This doesn't improve readability and should be used with + care. +:C0102: *Black listed name "%s"* + Used when the name is listed in the black list (unauthorized names). +:C0103: *Invalid name "%s" (should match %s)* + Used when the name doesn't match the regular expression associated to its type + (constant, variable, class...). +:C0111: *Missing docstring* + Used when a module, function, class or method has no docstring. Some special + methods like __init__ doesn't necessary require a docstring. +:C0112: *Empty docstring* + Used when a module, function, class or method has an empty docstring (it would + be too easy ;). +:C0121: *Missing required attribute "%s"* + Used when an attribute required for modules is missing. + +Reports +~~~~~~~ +:R0101: Statistics by type + + +Typecheck checker +----------------- +try to find bugs in the code using type inference + +Options +~~~~~~~ +:ignore-mixin-members: + Tells wether missing members accessed in mixin class should be ignored. A + mixin class is detected if its name ends with "mixin" (case insensitive). + Default: yes +:ignored-classes: + List of classes names for which member attributes should not be checked + (useful for classes with attributes dynamicaly set). + Default: SQLObject +:zope: + When zope mode is activated, consider the acquired-members option to ignore + access to some undefined attributes. +:acquired-members: + List of members which are usually get through zope's acquisition mecanism and + so shouldn't trigger E0201 when accessed (need zope=yes to be considered). + Default: REQUEST,acl_users,aq_parent + +Messages +~~~~~~~~ +:E1101: *%s %r has no %r member* + Used when a variable is accessed for an unexistant member. +:E1102: *%s is not callable* + Used when an object being called has been infered to a non callable object +:E1103: *%s %r has no %r member (but some types could not be inferred)* + Used when a variable is accessed for an unexistant member, but astng was not + able to interpret all possible types of this variable. +:E1111: *Assigning to function call which doesn't return* + Used when an assigment is done on a function call but the infered function + doesn't return anything. +:W1111: *Assigning to function call which only returns None* + Used when an assigment is done on a function call but the infered function + returns nothing but None. + + +Variables checker +----------------- +checks for +* unused variables / imports +* undefined variables +* redefinition of variable from builtins or from an outer scope +* use of variable before assigment + +Options +~~~~~~~ +:init-import: + Tells wether we should check for unused import in __init__ files. +:dummy-variables-rgx: + A regular expression matching names used for dummy variables (i.e. not used). + Default: _|dummy +:additional-builtins: + List of additional names supposed to be defined in builtins. Remember that + you should avoid to define new builtins when possible. + +Messages +~~~~~~~~ +:E0601: *Using variable %r before assignment* + Used when a local variable is accessed before it's assignment. +:E0602: *Undefined variable %r* + Used when an undefined variable is accessed. +:E0611: *No name %r in module %r* + Used when a name cannot be found in a module. +:W0601: *Global variable %r undefined at the module level* + Used when a variable is defined through the "global" statement but the + variable is not defined in the module scope. +:W0602: *Using global for %r but no assigment is done* + Used when a variable is defined through the "global" statement but no + assigment to this variable is done. +:W0603: *Using the global statement* + 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* + Used when you use the "global" statement at the module level since it has no + effect +:W0611: *Unused import %s* + Used when an imported module or variable is not used. +:W0612: *Unused variable %r* + Used when a variable is defined but not used. +:W0613: *Unused argument %r* + Used when a function or method argument is not used. +:W0614: *Unused import %s from 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)* + Used when a variable's name hide a name defined in the outer scope. +:W0622: *Redefining built-in %r* + Used when a variable or function override a built-in. +:W0631: *Using possibly undefined loop variable %r* + Used when an loop variable (i.e. defined by a for loop or a list comprehension + or a generator expression) is used outside the loop. + + +Classes checker +--------------- +checks for : +* methods without self as first argument +* overridden methods signature +* access only to existant members via self +* attributes not defined in the __init__ method +* supported interfaces implementation +* unreachable code + +Options +~~~~~~~ +:ignore-iface-methods: + 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. + Default: isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by +:defining-attr-methods: + List of method names used to declare (i.e. assign) instance attributes. + Default: __init__,__new__,setUp + +Messages +~~~~~~~~ +:E0202: *An attribute inherited from %s hide this method* + Used when a class defines a method which is hiden by an instance attribute + from an ancestor class. +:E0203: *Access to member %r before its definition line %s* + Used when an instance member is accessed before it's actually assigned. +:E0211: *Method has no argument* + Used when a method which should have the bound instance as first argument has + no argument defined. +:E0213: *Method should have "self" as first argument* + Used when a method has an attribute different the "self" as first argument. + This is considered as an error since this is a soooo common convention that + you should'nt break it! +:E0221: *Interface resolved to %s is not a class* + Used when a class claims to implement an interface which is not a class. +:E0222: *Missing method %r from %s interface* + Used when a method declared in an interface is missing from a class + implementing this interface +:W0201: *Attribute %r defined outside __init__* + Used when an instance attribute is defined outside the __init__ method. +:W0211: *Static method with %r as first argument* + Used when a static method has "self" or "cls" as first argument. +:W0212: *Access to a protected member %s of a client class* + 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. +:W0221: *Arguments number differs from %s method* + Used when a method has a different number of arguments than in the implemented + interface or in an overridden method. +:W0222: *Signature differs from %s method* + 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* + Used when an abstract method (ie raise NotImplementedError) is not overridden + in concrete class. +:W0231: *__init__ method from base class %r is not called* + Used when an ancestor class method has an __init__ method which is not called + by a derived class. +:W0232: *Class has no __init__ method* + Used when a class has no __init__ method, neither its parent classes. +:W0233: *__init__ method from a non direct base class %r is called* + Used when an __init__ method is called on a class which is not in the direct + ancestors for the analysed class. +:R0201: *Method could be a function* + Used when a method doesn't use its bound instance, and so could be written as + a function. +:C0202: *Class method should have "cls" as first argument* + Used when a class method has an attribute different than "cls" as first + argument, to easily differentiate them from regular instance methods. +:C0203: *Metaclass method should have "mcs" as first argument* + Used when a metaclass method has an attribute different the "mcs" as first + argument. +:F0202: *Unable to check methods signature (%s / %s)* + Used when PyLint has been unable to check methods signature compatibility for + an unexpected raison. Please report this kind if you don't make sense of it. +:F0220: *failed to resolve interfaces implemented by %s (%s)* + Used when a PyLint as failed to find interfaces implemented by a class + + +Design checker +-------------- +checks for sign of poor/misdesign: +* number of methods, attributes, local variables... +* size, complexity of functions, methods + +Options +~~~~~~~ +:max-args: + Maximum number of arguments for function / method + Default: 5 +:max-locals: + Maximum number of locals for function / method body + Default: 15 +:max-returns: + Maximum number of return / yield for function / method body + Default: 6 +:max-branchs: + Maximum number of branch for function / method body + Default: 12 +:max-statements: + Maximum number of statements in function / method body + Default: 50 +:max-parents: + Maximum number of parents for a class (see R0901). + Default: 7 +:max-attributes: + Maximum number of attributes for a class (see R0902). + Default: 7 +:min-public-methods: + Minimum number of public methods for a class (see R0903). + Default: 2 +:max-public-methods: + Maximum number of public methods for a class (see R0904). + Default: 20 + +Messages +~~~~~~~~ +:R0901: *Too many ancestors (%s/%s)* + Used when class has too many parent classes, try to reduce this to get a more + simple (and so easier to use) class. +:R0902: *Too many instance attributes (%s/%s)* + Used when class has too many instance attributes, try to reduce this to get a + more simple (and so easier to use) class. +:R0903: *Too few public methods (%s/%s)* + Used when class has too few public methods, so be sure it's really worth it. +:R0904: *Too many public methods (%s/%s)* + Used when class has too many public methods, try to reduce this to get a more + simple (and so easier to use) class. +:R0911: *Too many return statements (%s/%s)* + Used when a function or method has too many return statement, making it hard + to follow. +:R0912: *Too many branches (%s/%s)* + Used when a function or method has too many branches, making it hard to + follow. +:R0913: *Too many arguments (%s/%s)* + Used when a function or method takes too many arguments. +:R0914: *Too many local variables (%s/%s)* + Used when a function or method has too many local variables. +:R0915: *Too many statements (%s/%s)* + Used when a function or method has too many statements. You should then split + it in smaller functions / methods. +:R0921: *Abstract class not referenced* + Used when an abstract class is not used as ancestor anywhere. +:R0922: *Abstract class is only referenced %s times* + Used when an abstract class is used less than X times as ancestor. +:R0923: *Interface not implemented* + Used when an interface class is not implemented anywhere. + + +Imports checker +--------------- +checks for +* external modules dependencies +* relative / wildcard imports +* cyclic imports +* uses of deprecated modules + +Options +~~~~~~~ +:deprecated-modules: + Deprecated modules which should not be used, separated by a comma + Default: regsub,string,TERMIOS,Bastion,rexec +:import-graph: + Create a graph of every (i.e. internal and external) dependencies in the + given file (report R0402 must not be disabled) +:ext-import-graph: + Create a graph of external dependencies in the given file (report R0402 must + not be disabled) +:int-import-graph: + Create a graph of internal dependencies in the given file (report R0402 must + not be disabled) + +Messages +~~~~~~~~ +:W0401: *Wildcard import %s* + Used when `from module import *` is detected. +:W0402: *Uses of a deprecated module %r* + Used a module marked as deprecated is imported. +:W0403: *Relative import %r* + Used when an import relative to the package directory is detected. +:W0404: *Reimport %r (imported line %s)* + Used when a module is reimported multiple times. +:W0406: *Module import itself* + Used when a module is importing itself. +:W0410: *__future__ import is not the first non docstring statement* + Python 2.5 and greater require __future__ import to be the first non docstring + statement in the module. +:R0401: *Cyclic import (%s)* + Used when a cyclic import between two or more modules is detected. +:F0401: *Unable to import %r (%s)* + Used when pylint has been unable to import a module. + +Reports +~~~~~~~ +:R0401: External dependencies +:R0402: Modules dependencies graph + + +Newstyle checker +---------------- +checks for usage of new style capabilities on old style classes and +other new/old styles conflicts problems +* use of property, __slots__, super +* "super" usage +* raising a new style class as exception + +Messages +~~~~~~~~ +:E1001: *Use __slots__ on an old style class* + Used when an old style class use the __slots__ attribute. +:E1002: *Use super on an old style class* + Used when an old style class use the super builtin. +:E1003: *Bad first argument %r given to super class* + Used when another argument than the current class is given as first argument + of the super builtin. +:E1010: *Raising a new style class* + Used when a new style class is raised since it's not possible with python < + 2.5. +:W1001: *Use of "property" on an old style class* + Used when PyLint detect the use of the builtin "property" on an old style + class while this is relying on new style classes features +:W1010: *Exception doesn't inherit from standard "Exception" class* + Used when a custom exception class is raised but doesn't inherit from the + builtin "Exception" class. + + +Exceptions checker +------------------ +checks for +* excepts without exception filter +* string exceptions + +Messages +~~~~~~~~ +:E0701: *Bad except clauses order (%s)* + Used when except clauses are not in the correct order (from the more specific + to the more generic). If you don't fix the order, some exceptions may not be + catched by the most specific handler. +:E0702: *Raising %s while only classes, instances or string are allowed* + Used when something which is neither a class, an instance or a string is + raised (i.e. a `TypeError` will be raised). +:W0701: *Raising a string exception* + Used when a string exception is raised. +:W0702: *No exception type(s) specified* + Used when an except clause doesn't specify exceptions type to catch. +:W0703: *Catch "Exception"* + Used when an except catches Exception instances. +:W0704: *Except doesn't do anything* + Used when an except clause does nothing but "pass" and there is no "else" + clause. +:W0706: *Identifier %s used to raise an exception is assigned to %s* + Used when a variable used to raise an exception is initially assigned to a + value which can't be used as an exception. + + +Format checker +-------------- +checks for : +* unauthorized constructions +* strict indentation +* line length +* use of <> instead of != + +Options +~~~~~~~ +:max-line-length: + Maximum number of characters on a single line. + Default: 80 +:max-module-lines: + Maximum number of lines in a module + Default: 1000 +:indent-string: + String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 + tab). + Default: ' ' + +Messages +~~~~~~~~ +:W0311: *Bad indentation. Found %s %s, expected %s* + Used when an unexpected number of indentation's tabulations or spaces has been + found. +:W0312: *Found indentation with %ss instead of %ss* + Used when there are some mixed tabs and spaces in a module. +:W0331: *Use of the <> operator* + Used when the deprecated "<>" operator is used instead of "!=". +:W0332: *Use l as long integer identifier* + Used when a lower case "l" is used to mark a long integer. You should use a + upper case "L" since the letter "l" looks too much like the digit "1" +:W0333: *Use of the `` operator* + Used when the deprecated "``" (backtick) operator is used instead of the str() + function. +:C0301: *Line too long (%s/%s)* + Used when a line is longer than a given number of characters. +:C0302: *Too many lines in module (%s)* + Used when a module has too much lines, reducing its readibility. +:C0321: *More than one statement on a single line* + Used when more than on statement are found on the same line. +:C0322: *Operator not preceded by a space* + Used when one of the following operator (!= | <= | == | >= | < | > | = | \+= | + -= | \*= | /= | %) is not preceded by a space. +:C0323: *Operator not followed by a space* + Used when one of the following operator (!= | <= | == | >= | < | > | = | \+= | + -= | \*= | /= | %) is not followed by a space. +:C0324: *Comma not followed by a space* + Used when a comma (",") is not followed by a space. +:F0321: *Format detection error in %r* + Used when an unexpected error occured in bad format detection.Please report + the error if it occurs. + + +Miscellaneous checker +--------------------- +checks for: +* warning notes in the code like FIXME, XXX +* PEP 263: source code with non ascii character but no encoding declaration + +Options +~~~~~~~ +:notes: + List of note tags to take in consideration, separated by a comma. + Default: FIXME,XXX,TODO + +Messages +~~~~~~~~ +:E0501: *Non ascii characters found but no encoding specified (PEP 263)* + Used when some non ascii characters are detected but now encoding is + specified, as explicited in the PEP 263. +:E0502: *Wrong encoding specified (%s)* + Used when a known encoding is specified but the file doesn't seem to be + actually in this encoding. +:E0503: *Unknown encoding specified (%s)* + Used when an encoding is specified, but it's unknown to Python. +:W0511: + Used when a warning note as FIXME or XXX is detected. + + +Metrics checker +--------------- +does not check anything but gives some raw metrics : +* total number of lines +* total number of code lines +* total number of docstring lines +* total number of comments lines +* total number of empty lines + +Reports +~~~~~~~ +:R0701: Raw metrics + + +Similarities checker +-------------------- +checks for similarities and duplicated code. This computation may be +memory / CPU intensive, so you should disable it if you experiments some +problems. + +Options +~~~~~~~ +:min-similarity-lines: + Minimum lines number of a similarity. + Default: 4 +:ignore-comments: + Ignore comments when computing similarities. + Default: yes +:ignore-docstrings: + Ignore docstrings when computing similarities. + Default: yes + +Messages +~~~~~~~~ +:R0801: *Similar lines in %s files* + 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. + +Reports +~~~~~~~ +:R0801: Duplication + + diff --git a/doc/makefile b/doc/makefile new file mode 100644 index 0000000..4e0481b --- /dev/null +++ b/doc/makefile @@ -0,0 +1,44 @@ +MKHTML=mkdoc +MKHTML_OPT=--doctype article --param toc.section.depth=1 --target html --stylesheet single-file + +SRC=. + + +all: manual.html quickstart.html features.html FAQ.html beginner_pylint_tutorial.html examples man + +FAQ.html: ${SRC}/FAQ.txt + ${MKHTML} ${MKHTML_OPT} ${SRC}/FAQ.txt + +quickstart.html: ${SRC}/quickstart.txt + ${MKHTML} ${MKHTML_OPT} ${SRC}/quickstart.txt + +manual.html: ${SRC}/manual.txt ${SRC}/FAQ.txt + ${MKHTML} ${MKHTML_OPT} ${SRC}/manual.txt + +beginner_pylint_tutorial.html: ${SRC}/beginner_pylint_tutorial.txt + ${MKHTML} ${MKHTML_OPT} ${SRC}/beginner_pylint_tutorial.txt + +features.html: + chmod u+w ${SRC}/features.txt + echo "PyLint features" > ${SRC}/features.txt + echo "===============" >> ${SRC}/features.txt + echo "" >> ${SRC}/features.txt + echo ".. generated by pylint --list-msgs" >> ${SRC}/features.txt + echo "" >> ${SRC}/features.txt + echo ".. contents::" >> ${SRC}/features.txt + echo "" >> ${SRC}/features.txt + pylint --list-msgs >> ${SRC}/features.txt + ${MKHTML} ${MKHTML_OPT} ${SRC}/features.txt + +examples: + chmod u+w ../examples/pylintrc + pylint --generate-rcfile > ../examples/pylintrc + +man: + chmod u+w ../man/pylint.1 + pylint --generate-man > ../man/pylint.1 + +clean: + rm -f *.html + +.PHONY: features.html diff --git a/doc/manual.txt b/doc/manual.txt new file mode 100644 index 0000000..1b3c1eb --- /dev/null +++ b/doc/manual.txt @@ -0,0 +1,637 @@ +================== +Pylint User Manual +================== + +:Author: Sylvain Thénault +:Author: Alexandre Fayolle +:Organization: Logilab + +.. contents:: + + +This document is meant to be the reference user manual for Pylint_. This is a +work in progress so some sections or parts may be missing (sometimes marked by a +XXX). If you think it's lacking some important information, please talk about +it on the python-projects mailing list (see the `Mailing lists`_ section for +more information about the list). + +.. _Pylint: http://www.logilab.org/project/name/pylint + + +Introduction +============ + +What is pylint? +--------------- + +Pylint is a tool that checks for errors in python code, tries to enforce a +coding standard and looks for smelling code. This is similar but nevertheless +different from what pychecker_ provides, especially since pychecker explicitely +does not bother with coding style. The default coding style used by pylint is +close to `PEP 008`_ (aka `Guido's style guide`_). For more information about +code smells, refer to Martin Fowler's `refactoring book`_ + +One important thing to note is that Pylint isn't smarter than you are: it may +warn you about things that you have conscientiously done. That's for example +because it tries to detect things that may be dangerous in a context, but maybe +not in others, or because it checks for some things that you don't care +about. Generally, you shouldn't expect pylint to be totally quiet about your +code, so don't necessarily be alarmed if it gives you a hell lot of messages for +your proudly(XXX) project ;) + +Pylint will display a number of messages as it analyzes the code, as well as +some statistics about the number of warnings and errors found in different +files. The messages are classified under various categories such as errors and +warnings (more below). If you run pylint twice, it will display the statistics +from the previous run together with the ones from the current run, so that you +can see if the code has improved or not. + +Last but not least, the code is given an overall mark, based on the number an +severity of the warnings and errors. This has proven to be very motivating for +programmers. + +.. _pychecker: http://pychecker.sf.net +.. _`PEP 008`: http://www.python.org/dev/peps/pep-0008/ +.. _`Guido's style guide`: http://www.python.org/doc/essays/styleguide.html +.. _`refactoring book`: http://www.refactoring.com/ + +Installation +------------ + +Dependancies +'''''''''''' +Pylint requires the latest `logilab-astng`_ and `logilab-common`_ +packages. It should be compatible with any python version greater than +2.2.0 (python 2.2 users will have to install the optik_ package). + +.. _`logilab-astng`: http://www.logilab.org/project/name/astng +.. _`logilab-common`: http://www.logilab.org/project/name/common +.. _optik: http://optik.sourceforge.net/ + + +Distributions +''''''''''''' +The source tarball is available at ftp://ftp.logilab.fr/pub/pylint. + +You may apt-get a debian package by adding :: + + deb ftp://ftp.logilab.org/pub/debian unstable/ + +to your */etc/apt/sources.list* file. Pylint is also available in the +standard Debian distribution (but add our public debian repository +anyway if you want to get the latest releases and upgrades earlier) + +Contributed RPM packages for pylint and logilab-common are available at +ftp://ftp.nest.pld-linux.org/test. + +Pylint is also available in Gentoo, Fedora 4, Ubuntu, FreeBSD, Darwin +(and maybe other, if si drop us a note please!). + + +Source distribution installation +'''''''''''''''''''''''''''''''' +From the source distribution, extract the tarball, go to the extracted +directory and simply run :: + + python setup.py install + +You'll have to install dependancies in a similar way. + +Windows users may get valuable information about pylint installation on +`this page`_. + +.. _`this page`: http://thinkhole.org/wp/2006/01/16/installing-pylint-on-windows/ + + +Note for Windows users +'''''''''''''''''''''' + +On Windows, once you have installed pylint, the command line usage is :: + + pylint.bat [options] module_or_package + +But this will only work if *pylint.bat* is either in the current +directory, or on your system path. (*setup.py* install install *python.bat* +to the *Scripts* subdirectory of your Python installation -- e.g. +C:\Python24\Scripts.) You can do any of the following to solve this: + +1. change to the appropriate directory before running pylint.bat + +2. add the Scripts directory to your path statement in your autoexec.bat + file (this file is found in the root directory of your boot-drive) + +3. create a 'redirect' batch file in a directory actually on your + systems path + +To effect (2), simply append the appropriate directory name to the PATH= +statement in autoexec.bat. Be sure to use the Windows directory +separator of ';' between entries. Then, once you have rebooted (this is +necessary so that the new path statement will take effect when +autoexec.bat is run), you will be able to invoke PyLint with +pylint.bat on the command line. + +(3) is the best solution. Once done, you can call pylint at the command +line without the .bat, just as do non-Windows users by typing: :: + + pylint [options] module_or_package + +To effect option (3), simply create a plain text file pylint.bat with +the single line: :: + + C:\PythonDirectory\Scripts\pylint.bat + +(where PythonDirectory is replaced by the actual Python installation +directory on your system -- e.g. C:\Python24\Scripts\pylint.bat). + + +Invoking pylint +--------------- + +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. + +It is also possible to analyze python files, with a few +restriction. The thing to keep in mind is that pylint will try to +convert the file name to a module name, and only be able to process +the file if it succeeds. :: + + pylint mymodule.py + +should always work since the current working +directory is automatically added on top of the python path :: + + pylint directory/mymodule.py + +will work if "directory" is a python package (i.e. has an __init__.py +file) or if "directory" is in the python path. + +For more details on this see the `Frequently Asked Questions`_. + +You can also start a thin gui around pylint (require TkInter) by +typing :: + + pylint-gui + +This should open a window where you can enter the name of the package +or module to check, at pylint messages will be displayed in the user +interface. + + +Pylint output +------------- + +The default format for the output is raw text. But passing pylint the +``--html`` option will produce an HTML document. + +There are several sections in pylint's output. + +Source code analysis section +'''''''''''''''''''''''''''' +For each python module, +pylint will first display a few '*' characters followed by the name +of the module. Then, a number of messages with the following +format: :: + + MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE + +You can get another output format, useful since it's recognized by +most editors or other development tools using the ``--parseable=y`` +option. + +The message type can be: + + * [R]efactor for a "good practice" metric violation + * [C]onvention for coding standard violation + * [W]arning for stylistic problems, or minor programming issues + * [E]rror for important programming issues (i.e. most probably bug) + * [F]atal for errors which prevented further processing + +Sometimes the line of code which caused the error is displayed with +a caret pointing to the error. This may be generalized in future +versions of pylint. + +Example (extracted from a run of pylint on itself...): + +:: + + ************* Module pylint.checkers.format + W: 50: Too long line (86/80) + W:108: Operator not followed by a space + print >>sys.stderr, 'Unable to match %r', line + ^ + W:141: Too long line (81/80) + W: 74:searchall: Unreachable code + W:171:FormatChecker.process_tokens: Redefining built-in (type) + W:150:FormatChecker.process_tokens: Too many local variables (20/15) + W:150:FormatChecker.process_tokens: Too many branchs (13/12) + + +Reports section +''''''''''''''' +Following the analysis message, pylint will display a set of reports, +each one focusing on a particular aspect of the project, such as number +of messages by categories, modules dependancies... + +For instance, the metrics report displays summaries gathered from the +current run. + + * the number of processed modules + * for each module, the percentage of errors and warnings + * the total number of errors and warnings + * percentage of classes, functions and modules with docstrings, and + a comparison from the previous run + * percentage of classes, functions and modules with correct name + (according the the coding standard), and a comparison from the + previous run + * a list of external dependencies found in the code, and where they appear + +Also, a global evaluation for the code is computed, and an +optional witty comment is displayed (if ``--comment=y`` was +specified on the command line). + + + +Command line options +-------------------- + +First of all, we have two basic (but useful) options. + +--version show program's version number and exit +-h, --help show help about the command line options + +Pylint is architectured around several checkers. By default all +checkers are enabled. You can disable a specific checker by specifying +``--enable-<checker>=n``, or disable all checkers using +``--disable-all`` and afterwards enable specific checkers with +``--enable-<checker>=y``. See the list of available features_ for a +description of provided checkers with their functionalities. + +Each checker has some specific options, which can take either a yes/no +value, an integer, a python regular expression, or a comma separated +list of values (which are generally used to override a regular +expression in special cases). For a full list of options, use ``--help`` + +Specifying all the options suitable for your setup and coding +standards can be tedious, so it is possible to use a rc file to +specify the default values. Pylint looks for /etc/pylintrc and +~/.pylintrc. The ``--generate-rcfile`` option will generate a +commented configuration file according to the current configuration on +standard output and exit. You can put other options before this one to +use them in the configuration, or start with the default values and +hand tune the configuration. + +Other useful global options include: + +--zope Initialize Zope products before starting +--ignore=file Add <file> (may be a directory) to the black + list. It should be a base name, not a path. + You may set this option multiple times. +--statistics=y_or_n Compute statistics on collected data. +--persistent=y_or_n Pickle collected data for later comparisons. +--comment=y_or_n Add a comment according to your evaluation note. +--parseable=y_or_n Use a parseable output format. +--html=y_or_n Use HTML as output format instead of text. +--enable-msg=msgids Enable the given messages. +--disable-msg=msgids Disable the given messages. +--enable-msg-cat=cats Enable all messages in the given categories. +--disable-msg-cat=cats Disable all messages in the given categories. +--errors-only Enable only checkers from the error category. + +.. _features: features.html + +Daily pylint usage +------------------ +What pylint says is not to be taken as gospel. While getting as +few false positives for errors as possible is a goal for us -- and +python makes it hard enough, it is not the case for warnings. + +:Quoting Alexandre: + My usage pattern for pylint is to generally run pylint -e quite often to + get stupid errors flagged before launching an application (or before + comitting). I generally run pylint with all the bells and whistles + activated some time before a release, when I want to cleanup the code. + And when I do that I simply ignore tons of the false warnings (and I + can do that without being driven mad by this dumb program which is not + smart enough to understand the dynamicity of Python because I only run + it once or twice a week in this mode) + +:Quoting Marteen Ter Huurne: + In our project we just accepted that we have to make some modifications in our + code to please PyLint: + + - stick to more naming conventions (unused variables ending in underscores, + mix-in class names ending in "Mixin") + - making all abstract methods explicit (rather than just not defining them in + the superclass) + - for messages which are useful in general, but not in a specific case: add "# + pylint: disable-msg=X0123" comments + - for PyLint bugs: add "#pylint: disable-msg=X0123" comments + - for PyLint limitations: add "#pylint: disable-msg=X0123" comments + (for instance Twisted's modules create a lot of definitions dynamically so + PyLint does not know about them) + + The effort is worth it, since PyLint helps us a lot in keeping the code clean + and finding errors early. Although most errors found by PyLint would also be + found by the regression tests, by fixing them before committing, we save time. + And our regression tests do not cover all code either, just the most complex + parts. + + +Bug reports, feedback +--------------------- +You think you have found a bug in Pylint? Well, this may be the case +since Pylint is under development. Please take the time to send a bug +report to python-projects@logilab.org if you've not found it already reported on +the `tracker page`_. This mailing list is also a nice place to +discuss Pylint issues, see below for more information about pylint's related +lists. + +You can check for already reported bugs, planned features on pylint's tracker +web page: http://www.logilab.org/project/name/pylint + +Notice that if you don't find something you have expected in pylint's +tracker page, it may be on the tracker page of one of its dependancies, namely +astng and common: + +* http://www.logilab.org/project/name/logilab-astng +* http://www.logilab.org/project/name/logilab-common + +.. _`tracker page`: http://www.logilab.org/project/name/pylint + +Mailing lists +------------- +Use the python-projects@logilab.org mailing list for anything related +to Pylint. This is in most cases better than sending an email directly +to the author, since others will benefit from the exchange, and you'll +be more likely answered by someone subscribed to the list. This is a +moderated mailing list, so if you're not subscribed email you send will have to +be validated first before actually being sent on the list. + +You can subscribe to this mailing list at +http://lists.logilab.org/mailman/listinfo/python-projects + +Archives are available at +http://lists.logilab.org/pipermail/python-projects/ + +If you prefer speaking french instead of english, you can use the +generic forum-fr@logilab.org mailing list: + +* (un)subscribe: http://lists.logilab.org/mailman/listinfo/forum-fr +* archives: http://lists.logilab.org/pipermail/forum-fr + +Notice though that this list has a very low traffic since most pylint related +discussions are done on the python-projects mailing list. + + + +Advanced usage +============== + +Base configuration +------------------ + +To be written... + +Environment +----------- + +To be written... + +Messages control +---------------- + +An example available from the examples directory:: + + """pylint option block-disable-msg""" + + __revision__ = None + + class Foo(object): + """block-disable-msg test""" + + def __init__(self): + pass + + def meth1(self, arg): + """this issues a message""" + print self + + def meth2(self, arg): + """and this one not""" + # pylint: disable-msg=W0613 + print self\ + + "foo" + + def meth3(self): + """test one line disabling""" + # no error + print self.bla # pylint: disable-msg=E1101 + # error + print self.blop + + def meth4(self): + """test re-enabling""" + # pylint: disable-msg=E1101 + # no error + print self.bla + print self.blop + # pylint: enable-msg=E1101 + # error + print self.blip + + def meth5(self): + """test IF sub-block re-enabling""" + # pylint: disable-msg=E1101 + # no error + print self.bla + if self.blop: + # pylint: enable-msg=E1101 + # error + print self.blip + else: + # no error + print self.blip + # no error + print self.blip + + def meth6(self): + """test TRY/EXCEPT sub-block re-enabling""" + # pylint: disable-msg=E1101 + # no error + print self.bla + try: + # pylint: enable-msg=E1101 + # error + print self.blip + except UndefinedName: # pylint: disable-msg=E0602 + # no error + print self.blip + # no error + print self.blip + + def meth7(self): + """test one line block opening disabling""" + if self.blop: # pylint: disable-msg=E1101 + # error + print self.blip + else: + # error + print self.blip + # error + print self.blip + + + def meth8(self): + """test late disabling""" + # error + print self.blip + # pylint: disable-msg=E1101 + # no error + print self.bla + print self.blop + + + +About analysis +============== + +Pylint heuristics +----------------- + +To be written... + +About astng inference +--------------------- + +To be written... + + + +Enhancing Pylint +================ + +Writing your own checker +------------------------ +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 : +* raw checkers, which are analysing each module as a raw file stream +* ast checkers, which are working on an ast representation of the module + +The ast representation used is an extension of the one provided with the +standard python distribution in the `compiler package`_. The extension +adds additional information and methods on the tree nodes to ease +navigation and code introspection. + +An AST checker is a visitor, and should implement +visit_<lowered class name> +leave_<lowered class name> +methods for the nodes it's interested in. To get description of the different +classes used in an ast tree, look at the `compiler.ast documentation`. +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 + +Notice that the source code is probably the best source of +documentation, it should be clear and well documented. Don't hesitate to +ask for any information on the python-projects mailing list. + +.. _`compiler package`: http://python.org/doc/current/lib/module-compiler.html +.. _`compiler.ast documentation`: http://www.python.org/doc/current/lib/module-compiler.ast.html + + +Contribute ! +------------ +All our software is developped using the mercurial_ version control +system. This is a very cool distributed vcs and its usage is very similar to +other ones such as cvs or subversion (though the distributed feature introduced +some different usage patterns). See mercurial home page for installation on +your computer and basic usage. Note that it's very easy to send us patches using +`hg email` command ;). + +You can get the in-development pylint source code from our public mercurial_ +repository: + +http://www.logilab.org/src/pylint + +The same is true for pylint dependancies (if you use pylint code from the +repository, you should usually use code from the repository as well for astng +and common): + +http://www.logilab.org/src/logilab/astng +http://www.logilab.org/src/logilab/common + +.. _mercurial: http://www.selenic.com/mercurial/ + + + +Other information +================= + +IDE integration +--------------- +Pylint is integrated in the following editors/IDEs: + +* emacs (of course) +* eric3 +* eclipse (using the pydev_ plugin, see also + http://msdl.cs.mcgill.ca/MSDL/people/denis/meetings/pythonDev) + +To use pylint from within vim, see +http://www.gonzo.kiev.ua/projects/pylint.vim + +To use pylint from within komodo_, see +http://mateusz.loskot.net/2006/01/15/running-pylint-from-komodo/ + +To use pylint from within gedit_, see +http://live.gnome.org/Gedit/PylintPlugin + +.. _pydev: http://pydev.sourceforge.net +.. _komodo: http://www.activestate.com/Products/Komodo/ +.. _gedit: http://www.gnome.org/projects/gedit/ + +Some projects using Pylint +-------------------------- +The following projects are known to use pylint to help develop better +code: + +* OSAF Chandler (http://www.osafoundation.org/) +* Xen (http://www.xensource.com/) +* CPS (http://www.nuxeo.org) +* ERP5 (http://www.erp5.org/) +* pyxmpp (http://pyxmpp.jabberstudio.org/) +* mercurial +* eXe (http://exelearning.org/) +* PrimaGIS (http://www.primagis.org) +* python-cdd (http://projetos.ossystems.com.br/python-cdd/) +* CDSWare (http://cdsware.cern.ch/) +* ASE (http://dcwww.camp.dtu.dk/campos/ASE/intro.html) +* RunJob (http://projects.fnal.gov/runjob/) +* Slugathon (http://slugathon.python-hosting.com/) +* Topographica (http://topographica.org/Home/index.html) (at least they intend to do so) +* http://browsershots.org +* many more... + +Also notice that the CheeseCake_ kwalitee reporting tool uses pylint to +analyze the source code. + +.. _CheeseCake: http://cheesecake.sourceforge.net/ + + + +.. include:: FAQ.txt + + + + diff --git a/doc/quickstart.txt b/doc/quickstart.txt new file mode 100644 index 0000000..82de686 --- /dev/null +++ b/doc/quickstart.txt @@ -0,0 +1,215 @@ +================= +Pylint Quickstart +================= + +:Author: Alexandre Fayolle +:Organization: Logilab +:Version: $Revision: 1.10 $ +:Date: $Date: 2005-04-15 10:40:17 $ + +.. contents:: + + +This document is meant to get you started with Pylint. It assumes that +you have installed pylint following the instructions in the README +document found in the source documentation. + + +What is pylint? +--------------- + +Pylint is a tool that checks for errors in python code, tries to +enforce a coding standard and looks for smelling code . This is +similar but nevertheless different from what pychecker_ provides, +especially since pychecker explicitly does not bother with coding +style. The default coding style used by pylint is close to +`Guido's style guide`_. For more information about code smells, refer +to Martin Fowler's `refactoring book`_ + +Pylint will display a number of errors and warnings as it analyzes the +code, as well as some statistics about the number of warnings and +errors found in different files. If you run pylint twice, it will +display the statistics from the previous run together with the ones +from the current run, so that you can see if the code has improved or +not. + +Last but not least, the code is given an overall mark, based on the +number an severity of the warnings and errors. This has proven to +be very motivating for programmers. + + +Invoking pylint +--------------- + +Pylint is meant to be called from the 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. + +It is also possible to analyze python files, with a few +restrictions. The thing to keep in mind is that pylint will try to +convert the file name to a module name, and only be able to process +the file if it succeeds. :: + + pylint mymodule.py + +should always works since the current working +directory is automatically added on top of the python path :: + + pylint directory/mymodule.py + +will work if "directory" is a python package (i.e. has an __init__.py +file) or if "directory" is in the python path. + +For more details on this see the FAQ_. + +You can also start a thin gui around pylint (require TkInter) by +typing :: + + pylint-gui + +This should open a window where you can enter the name of the package +or module to check, at pylint messages will be displayed in the user +interface. + + +Pylint output +------------- + +The default format for the output is raw text. But passing pylint the +``--html`` option will produce an HTML document. + +There are several sections in pylint's output. + +Source code analysis section +'''''''''''''''''''''''''''' + +For each python module, +pylint will first display a few '*' characters followed by the name +of the module. Then, a number of messages with the following +format: :: + + MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE + +You can get another output format, useful since it's recognized by +most editors or other development tools using the ``--parseable=y`` +option. + +The message type can be: + + * [R]efactor for a "good practice" metric violation + * [C]onvention for coding standard violation + * [W]arning for stylistic problems, or minor programming issues + * [E]rror for important programming issues (i.e. most probably bug) + * [F]atal for errors which prevented further processing + +Sometimes the line of code which caused the error is displayed with +a caret pointing to the error. This may be generalized in future +versions of pylint. + +Example (extracted from a run of pylint on itself...): + +:: + + ************* Module pylint.checkers.format + W: 50: Too long line (86/80) + W:108: Operator not followed by a space + print >>sys.stderr, 'Unable to match %r', line + ^ + W:141: Too long line (81/80) + W: 74:searchall: Unreachable code + W:171:FormatChecker.process_tokens: Redefining built-in (type) + W:150:FormatChecker.process_tokens: Too many local variables (20/15) + W:150:FormatChecker.process_tokens: Too many branchs (13/12) + + +Reports section +''''''''''''''' + +Following the analysis message, pylint will display a set of reports, +each one focusing on a particular aspect of the project, such as number +of messages by categories, modules dependancies... + +For instance, the metrics report displays summaries gathered from the +current run. + + * the number of processed modules + * for each module, the percentage of errors and warnings + * the total number of errors and warnings + * percentage of classes, functions and modules with docstrings, and + a comparison from the previous run + * percentage of classes, functions and modules with correct name + (according the the coding standard), and a comparison from the + previous run + * a list of external dependencies found in the code, and where they appear + +Also, a global evaluation for the code is computed, and an +optional witty comment is displayed (if ``--comment=y`` was +specified on the command line). + + +Command line options +-------------------- + +First of all, we have two basic (but useful) options. + +--version show program's version number and exit +-h, --help show help about the command line options + +Pylint is architectured around several checkers. By default all +checkers are enabled. You can disable a specific checker by specifying +``--enable-<checker>=n``, or disable all checkers using +``--disable-all`` and afterwards enable specific checkers with +``--enable-<checker>=y``. See the list of available features_ for a +description of provided checkers with their functionalities. + +Each checker has some specific options, which can take either a yes/no +value, an integer, a python regular expression, or a comma separated +list of values (which are generally used to override a regular +expression in special cases). For a full list of options, use ``--help`` + +Specifying all the options suitable for your setup and coding +standards can be tedious, so it is possible to use a rc file to +specify the default values. Pylint looks for /etc/pylintrc and +~/.pylintrc. The ``--generate-rcfile`` option will generate a +commented configuration file according to the current configuration on +standard output and exit. You can put other options before this one to +use them in the configuration, or start with the default values and +hand tune the configuration. + +Other useful global options include: + +--zope Initialize Zope products before starting +--ignore=file Add <file> (may be a directory) to the black + list. It should be a base name, not a path. + You may set this option multiple times. +--statistics=y_or_n Compute statistics on collected data. +--persistent=y_or_n Pickle collected data for later comparisons. +--comment=y_or_n Add a comment according to your evaluation note. +--parseable=y_or_n Use a parseable output format. +--html=y_or_n Use HTML as output format instead of text. +--enable-msg=msgids Enable the given messages. +--disable-msg=msgids Disable the given messages. +--enable-msg-cat=cats Enable all messages in the given categories. +--disable-msg-cat=cats Disable all messages in the given categories. + + + +Bug reports +----------- + +You think you have found a bug in Pylint? Well, this may be the case +since Pylint is under development. Please take the time to send a bug +report to python-projects@logilab.org. This mailing list is also a +nice place to discuss Pylint issues. + + +.. _pychecker: http://pychecker.sf.net +.. _features: features.html +.. _FAQ: FAQ.html +.. _`Guido's style guide`: http://www.python.org/doc/essays/styleguide.html +.. _`refactoring book`: http://www.refactoring.com/ diff --git a/doc/rpython.txt b/doc/rpython.txt new file mode 100644 index 0000000..218f854 --- /dev/null +++ b/doc/rpython.txt @@ -0,0 +1,30 @@ +Since 0.17, pylint contains a Restricted python checker (rpython). +It contains the following checks: + +* unavailable keywords / builtins +* multiple inheritance +* mixing multiple types +* non homogeneous list +* global modification +* negative slice index +* using %r in format string +* warn about special methods that are not implicitly called + +By default the rpython checker is deactivated. Activate it using : + + pylint --rpython-mode -rn ... + +(-rn is disabling statistics reports) or + + pylint --enable-checker=rpython ... + +to get only rpython checks (though in this case you won't be warned about +regular errors). + +Another interesting thing is the rpython dedicated testing framework, +testing that checked things are actually not translatable. I have the idea +that this may be useful to generate some kind of documentation for +features supported by rpython or not, and help spread information when a +feature that wasn't supported is introduced in rpython. That's another +story though... If you're interested, check +pylint/test/test_rpycompilation.py. diff --git a/elisp/pylint-flymake.el b/elisp/pylint-flymake.el new file mode 100644 index 0000000..835409e --- /dev/null +++ b/elisp/pylint-flymake.el @@ -0,0 +1,11 @@ +(when (load "flymake" t) + (defun flymake-pylint-init () + (let* ((temp-file (flymake-init-create-temp-buffer-copy + 'flymake-create-temp-inplace)) + (local-file (file-relative-name + temp-file + (file-name-directory buffer-file-name)))) + (list "epylint" (list local-file)))) + + (add-to-list 'flymake-allowed-file-name-masks + '("\\.py\\'" flymake-pylint-init))) diff --git a/elisp/pylint.el b/elisp/pylint.el new file mode 100644 index 0000000..5862a2e --- /dev/null +++ b/elisp/pylint.el @@ -0,0 +1,60 @@ +(require 'compile) + +;; +;; Modifications done by Yarosav O. Halchenko (2008): +;; - enable user-visible variables +;; distributed under the same copyright/license terms as +;; pylint itself +;; +(require 'compile) + +;; user definable variables +;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + +(defgroup pylint nil + "Emacs support for the Pylint Python checker" + :group 'languages + :prefix "pylint-") + +(defcustom pylint-options "--output-format=parseable" + "*Command line options to be used with pylint call" + :type 'string + :group 'pylint) + + +;; adapted from pychecker for pylint +(defun pylint-python-hook () + (defun pylint () + "Run pylint against the file behind the current buffer after + checking if unsaved buffers should be saved." + + (interactive) + (let* ((file (buffer-file-name (current-buffer))) + (command (concat "pylint " pylint-options " \"" file "\""))) + (save-some-buffers (not compilation-ask-about-save) nil) ; save files. + (compile-internal command "No more errors or warnings" "pylint"))) +;; (local-set-key [f1] 'pylint) +;; (local-set-key [f2] 'previous-error) +;; (local-set-key [f3] 'next-error) + + (define-key + py-mode-map + [menu-bar Python pylint-separator] + '("--" . pylint-seperator)) + + (define-key + py-mode-map + [menu-bar Python next-error] + '("Next error" . next-error)) + (define-key + py-mode-map + [menu-bar Python prev-error] + '("Previous error" . previous-error)) + (define-key + py-mode-map + [menu-bar Python lint] + '("Pylint" . pylint)) + + ) + +(add-hook 'python-mode-hook 'pylint-python-hook) diff --git a/elisp/startup b/elisp/startup new file mode 100644 index 0000000..fd40b42 --- /dev/null +++ b/elisp/startup @@ -0,0 +1,17 @@ +;; -*-emacs-lisp-*- +;; +;; Emacs startup file for the Debian GNU/Linux %PACKAGE% package +;; +;; Originally contributed by Nils Naumann <naumann@unileoben.ac.at> +;; Modified by Dirk Eddelbuettel <edd@debian.org> +;; Adapted for dh-make by Jim Van Zandt <jrv@vanzandt.mv.com> + +;; The %PACKAGE% package follows the Debian/GNU Linux 'emacsen' policy and +;; byte-compiles its elisp files for each 'emacs flavor' (emacs19, +;; xemacs19, emacs20, xemacs20...). The compiled code is then +;; installed in a subdirectory of the respective site-lisp directory. +;; We have to add this to the load-path: +(setq load-path (cons (concat "/usr/share/" + (symbol-name debian-emacs-flavor) + "/site-lisp/%PACKAGE%") load-path)) +(load-library "pylint") diff --git a/examples/custom.py b/examples/custom.py new file mode 100644 index 0000000..73b64e1 --- /dev/null +++ b/examples/custom.py @@ -0,0 +1,38 @@ +from logilab import astng + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker + +class MyASTNGChecker(BaseChecker): + """add member attributes defined using my own "properties" function + to the class locals dictionary + """ + + __implements__ = IASTNGChecker + + name = 'custom' + msgs = {} + options = () + # this is important so that your checker is executed before others + priority = -1 + + def visit_callfunc(self, node): + """called when a CallFunc node is encountered. See compiler.ast + documentation for a description of available nodes: + http://www.python.org/doc/current/lib/module-compiler.ast.html + ) + """ + if not (isinstance(node.node, astng.Getattr) + and isinstance(node.node.expr, astng.Name) + and node.node.expr.name == 'properties' + and node.node.attrname == 'create'): + return + in_class = node.frame() + for param in node.args: + in_class.locals[param.name] = node + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(MyASTNGChecker(linter)) + diff --git a/examples/custom_raw.py b/examples/custom_raw.py new file mode 100644 index 0000000..701f6e9 --- /dev/null +++ b/examples/custom_raw.py @@ -0,0 +1,31 @@ +from pylint.interfaces import IRawChecker +from pylint.checkers import BaseChecker + +class MyRawChecker(BaseChecker): + """check for line continuations with '\' instead of using triple + quoted string or parenthesis + """ + + __implements__ = IRawChecker + + name = 'custom_raw' + msgs = {'W9901': ('use \\ for line continuation', + ('Used when a \\ is used for a line continuation instead' + ' of using triple quoted string or parenthesis.')), + } + options = () + + def process_module(self, stream): + """process a module + + the module's content is accessible via the stream object + """ + for (lineno, line) in enumerate(stream): + if line.rstrip().endswith('\\'): + self.add_message('W9901', line=lineno) + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(MyRawChecker(linter)) + diff --git a/examples/pylintrc b/examples/pylintrc new file mode 100644 index 0000000..f674e19 --- /dev/null +++ b/examples/pylintrc @@ -0,0 +1,309 @@ +# 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 astng checker in order +# to: +# * handle message activation / deactivation at the module level +# * handle some basic but necessary stats'data (number of classes, methods...) +# +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add <file or directory> to the black list. It should be a base name, not a +# path. You may set this option multiple times. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# Set the cache size for astng objects. +cache-size=500 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable only checker(s) with the given id(s). This option conflicts with the +# disable-checker option +#enable-checker= + +# Enable all checker(s) except those with the given id(s). This option +# conflicts with the enable-checker option +#disable-checker= + +# Enable all messages in the listed categories. +#enable-msg-cat= + +# Disable all messages in the listed categories. +#disable-msg-cat= + +# Enable the message(s) with the given id(s). +#enable-msg= + +# Disable the message(s) with the given id(s). +#disable-msg= + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=text + +# Include message's id in output +include-ids=no + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells wether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectivly contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (R0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (R0004). +comment=no + +# Enable the report(s) with the given id(s). +#enable-report= + +# Disable the report(s) with the given id(s). +#disable-report= + + +# checks for : +# * doc strings +# * modules / classes / functions / methods / arguments / variables name +# * number of arguments, local variables, branchs, returns and statements in +# functions, methods +# * required module attributes +# * dangerous default values as arguments +# * redefinition of function / method / class +# * uses of the global statement +# +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + + +# try to find bugs in the code using type inference +# +[TYPECHECK] + +# Tells wether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamicaly set). +ignored-classes=SQLObject + +# When zope mode is activated, consider the acquired-members option to ignore +# access to some undefined attributes. +zope=no + +# List of members which are usually get through zope's acquisition mecanism and +# so shouldn't trigger E0201 when accessed (need zope=yes to be considered). +acquired-members=REQUEST,acl_users,aq_parent + + +# checks for +# * unused variables / imports +# * undefined variables +# * redefinition of variable from builtins or from an outer scope +# * use of variable before assigment +# +[VARIABLES] + +# Tells wether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching names used for dummy variables (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +# checks for : +# * methods without self as first argument +# * overridden methods signature +# * access only to existant members via self +# * attributes not defined in the __init__ method +# * supported interfaces implementation +# * unreachable code +# +[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 + + +# checks for sign of poor/misdesign: +# * number of methods, attributes, local variables... +# * size, complexity of functions, methods +# +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +# checks for +# * external modules dependencies +# * relative / wildcard imports +# * cyclic imports +# * uses of deprecated modules +# +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report R0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report R0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report R0402 must +# not be disabled) +int-import-graph= + + +# checks for : +# * unauthorized constructions +# * strict indentation +# * line length +# * use of <> instead of != +# +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +# checks for: +# * warning notes in the code like FIXME, XXX +# * PEP 263: source code with non ascii character but no encoding declaration +# +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +# checks for similarities and duplicated code. This computation may be +# memory / CPU intensive, so you should disable it if you experiments some +# problems. +# +[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 diff --git a/examples/pylintrc_camelcase b/examples/pylintrc_camelcase new file mode 100644 index 0000000..0dd9266 --- /dev/null +++ b/examples/pylintrc_camelcase @@ -0,0 +1,24 @@ +# This pylintrc file will use the default settings except for the +# naming conventions, which will allow for camel case naming as found +# in Java code or several libraries such as PyQt, etc. + +[BASIC] +# Regular expression which should only match correct module names +module-rgx=(([a-z][a-z0-9]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-zA-Z0-9]*$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-zA-Z0-9]*$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z][a-zA-Z0-9]*$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z][a-zA-Z0-9]*$ + + @@ -0,0 +1,82 @@ +"""Tkinker gui for pylint""" + +from Tkinter import Tk, Frame, Listbox, Entry, Label, Button, Scrollbar +from Tkinter import TOP, LEFT, RIGHT, BOTTOM, END, X, Y, BOTH +import os +import sys + +if sys.platform.startswith('win'): + PYLINT = 'pylint.bat' +else: + PYLINT = 'pylint' + +class LintGui: + """Build and control a window to interact with pylint""" + + def __init__(self, root=None): + self.root = root or Tk() + self.root.title('Pylint') + top_frame = Frame(self.root) + res_frame = Frame(self.root) + btn_frame = Frame(self.root) + top_frame.pack(side=TOP, fill=X) + res_frame.pack(side=TOP, fill=BOTH, expand=True) + btn_frame.pack(side=TOP, fill=X) + + Label(top_frame, text='Module or package').pack(side=LEFT) + self.txtModule = Entry(top_frame, background='white') + self.txtModule.bind('<Return>', self.run_lint) + self.txtModule.pack(side=LEFT, expand=True, fill=X) + Button(top_frame, text='Run', command=self.run_lint).pack(side=LEFT) + + scrl = Scrollbar(res_frame) + self.results = Listbox(res_frame, + background='white', + font='fixedsys', + selectmode='browse', + yscrollcommand=scrl.set) + scrl.configure(command=self.results.yview) + self.results.pack(side=LEFT, expand=True, fill=BOTH) + scrl.pack(side=RIGHT, fill=Y) + + Button(btn_frame, text='Quit', command=self.quit).pack(side=BOTTOM) + #self.root.bind('<ctrl-q>', self.quit) + self.txtModule.focus_set() + + def mainloop(self): + """lauch the mainloop of the application""" + self.root.mainloop() + + def quit(self, _=None): + """quit the application""" + self.root.quit() + + def run_lint(self, _=None): + """lauches pylint""" + colors = {'W:':'red1', 'E:': 'red4', + 'W:': 'red3', '**': 'navy'} + + self.root.configure(cursor='watch') + self.results.focus_set() + self.results.delete(0, END) + self.results.update() + module = self.txtModule.get() + pout = os.popen('%s %s' % (PYLINT, module), 'r') + for line in pout.xreadlines(): + line = line.rstrip() + self.results.insert(END, line) + fg_color = colors.get(line[:2], 'black') + self.results.itemconfigure(END, fg=fg_color) + self.results.update() + self.root.configure(cursor='') + +def Run(args): + """launch pylint gui from args""" + if args: + print 'USAGE: pylint-gui\n launch a simple pylint gui using Tk' + return + gui = LintGui() + gui.mainloop() + +if __name__ == '__main__': + Run(sys.argv[1:]) diff --git a/interfaces.py b/interfaces.py new file mode 100644 index 0000000..9771aaf --- /dev/null +++ b/interfaces.py @@ -0,0 +1,98 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2002-2003 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Interfaces for PyLint objects +""" + +__revision__ = "$Id: interfaces.py,v 1.9 2004-04-24 12:14:53 syt Exp $" + +from logilab.common.interface import Interface + + +class IChecker(Interface): + """This is an base interface, not designed to be used elsewhere than for + sub interfaces definition. + """ + + def open(self): + """called before visiting project (i.e set of modules)""" + + def close(self): + """called after visiting project (i.e set of modules)""" + +## def open_module(self): +## """called before visiting a module""" + +## def close_module(self): +## """called after visiting a module""" + + +class IRawChecker(IChecker): + """interface for checker which need to parse the raw file + """ + + def process_module(self, stream): + """ process a module + + the module's content is accessible via the stream object + """ + + +class IASTNGChecker(IChecker): + """ interface for checker which prefers receive events according to + statement type + """ + + +class ILinter(Interface): + """interface for the linter class + + the linter class will generate events to its registered checkers. + Each ckecker may interact with the linter instance using this API + """ + + def register_checker(self, checker): + """register a new checker class + + checker is a class implementing IrawChecker or / and IASTNGChecker + """ + + def add_message(self, msg_id, line=None, node=None, args=None): + """add the message corresponding to the given id. + + If provided, msg is expanded using args + + astng checkers should provide the node argument, + raw checkers should provide the line argument. + """ + + +class IReporter(Interface): + """ reporter collect messages and display results encapsulated in a layout + """ + def add_message(self, msg_id, location, msg): + """add a message of a given type + + msg_id is a message identifier + location is a 3-uple (module, object, line) + msg is the actual message + """ + + def display_results(self, layout): + """display results encapsulated in the layout tree + """ + + +__all__ = ('IRawChecker', 'IStatable', 'ILinter', 'IReporter') @@ -0,0 +1,957 @@ +# Copyright (c) 2003-2008 Sylvain Thenault (thenault@gmail.com). +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" %prog [options] module_or_package + + Check that a module satisfy a coding standard (and more !). + + %prog --help + + Display this help message and exit. + + %prog --help-msg <msg-id>[,<msg-id>] + + Display help messages about given message identifiers and exit. +""" + +# import this first to avoid further builtins pollution possibilities +from pylint.checkers import utils + +import sys +import os +import re +import tokenize +from os.path import dirname, basename, splitext, exists, isdir, join, normpath + +from logilab.common.configuration import OptionsManagerMixIn, check_csv +from logilab.common.modutils import modpath_from_file, get_module_files, \ + file_from_modpath, load_module_from_name +from logilab.common.interface import implements +from logilab.common.textutils import get_csv +from logilab.common.fileutils import norm_open +from logilab.common.ureports import Table, Text +from logilab.common.__pkginfo__ import version as common_version + +from logilab.astng import ASTNGManager +from logilab.astng.__pkginfo__ import version as astng_version + +from pylint.utils import UnknownMessage, MessagesHandlerMixIn, \ + ReportsHandlerMixIn, MSG_TYPES, sort_checkers +from pylint.interfaces import ILinter, IRawChecker, IASTNGChecker +from pylint.checkers import BaseRawChecker, EmptyReport, \ + table_lines_from_stats +from pylint.reporters.text import TextReporter, ParseableTextReporter, \ + VSTextReporter, ColorizedTextReporter +from pylint.reporters.html import HTMLReporter +from pylint import config + +from pylint.__pkginfo__ import version + + +OPTION_RGX = re.compile('\s*#*\s*pylint:(.*)') +REPORTER_OPT_MAP = {'text': TextReporter, + 'parseable': ParseableTextReporter, + 'msvs': VSTextReporter, + 'colorized': ColorizedTextReporter, + 'html': HTMLReporter,} + +# Python Linter class ######################################################### + +MSGS = { + 'F0001': ('%s', + 'Used when an error occured preventing the analysis of a \ + module (unable to find it for instance).'), + 'F0002': ('%s: %s', + 'Used when an unexpected error occured while building the ASTNG \ + representation. This is usually accompanied by a traceback. \ + Please report such errors !'), + 'F0003': ('ignored builtin module %s', + 'Used to indicate that the user asked to analyze a builtin module\ + which has been skipped.'), + 'F0004': ('unexpected infered value %s', + 'Used to indicate that some value of an unexpected type has been \ + infered.'), + + 'I0001': ('Unable to run raw checkers on built-in module %s', + 'Used to inform that a built-in module has not been checked \ + using the raw checkers.'), + + 'I0010': ('Unable to consider inline option %r', + 'Used when an inline option is either badly formatted or can\'t \ + be used inside modules.'), + + 'I0011': ('Locally disabling %s', + 'Used when an inline option disables a message or a messages \ + category.'), + 'I0012': ('Locally enabling %s', + 'Used when an inline option enables a message or a messages \ + category.'), + 'I0013': ('Ignoring entire file', + 'Used to inform that the file will not be checked'), + + + 'E0001': ('%s', + 'Used when a syntax error is raised for a module.'), + + 'E0011': ('Unrecognized file option %r', + 'Used when an unknown inline option is encountered.'), + 'E0012': ('Bad option value %r', + 'Used when a bad value for an inline option is encountered.'), + } + +class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, + BaseRawChecker): + """lint Python modules using external checkers. + + This is the main checker controlling the other ones and the reports + generation. It is itself both a raw checker and an astng checker in order + to: + * handle message activation / deactivation at the module level + * handle some basic but necessary stats'data (number of classes, methods...) + """ + + __implements__ = (ILinter, IRawChecker, IASTNGChecker) + + name = 'master' + priority = 0 + msgs = MSGS + may_be_disabled = False + + options = (('ignore', + {'type' : 'csv', 'metavar' : '<file>', + 'dest' : 'black_list', 'default' : ('CVS',), + 'help' : 'Add <file or directory> to the black list. It \ +should be a base name, not a path. You may set this option multiple times.'}), + + ('enable-checker', + {'type' : 'csv', 'metavar': '<checker ids>', + 'group': 'Messages control', + 'help' : 'Enable only checker(s) with the given id(s).\ + This option conflicts with the disable-checker option'}), + + ('disable-checker', + {'type' : 'csv', 'metavar': '<checker ids>', + 'group': 'Messages control', + 'help' : 'Enable all checker(s) except those with the \ + given id(s).\ + This option conflicts with the enable-checker option'}), + + ('persistent', + {'default': True, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'help' : 'Pickle collected data for later comparisons.'}), + + ('cache-size', + {'default': 500, 'type' : 'int', 'metavar': '<size>', + 'help' : 'Set the cache size for astng objects.'}), + + ('load-plugins', + {'type' : 'csv', 'metavar' : '<modules>', 'default' : (), + 'help' : 'List of plugins (as comma separated values of \ +python modules names) to load, usually to register additional checkers.'}), + + ('output-format', + {'default': 'text', 'type': 'choice', 'metavar' : '<format>', + 'choices': ('text', 'parseable', 'msvs', 'colorized', 'html'), + 'short': 'f', + 'group': 'Reports', + 'help' : 'Set the output format. Available formats are text,\ + parseable, colorized, msvs (visual studio) and html'}), + + ('include-ids', + {'type' : 'yn', 'metavar' : '<y_or_n>', 'default' : 0, + 'short': 'i', + 'group': 'Reports', + 'help' : 'Include message\'s id in output'}), + + ('files-output', + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'group': 'Reports', + 'help' : 'Put messages in a separate file for each module / \ +package specified on the command line instead of printing them on stdout. \ +Reports (if any) will be written in a file name "pylint_global.[txt|html]".'}), + + ('reports', + {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'short': 'r', + 'group': 'Reports', + 'help' : 'Tells wether to display a full report or only the\ + messages'}), + + ('evaluation', + {'type' : 'string', 'metavar' : '<python_expression>', + 'group': 'Reports', + 'default': '10.0 - ((float(5 * error + warning + refactor + \ +convention) / statement) * 10)', + 'help' : 'Python expression which should return a note less \ +than 10 (10 is the highest note). You have access to the variables errors \ +warning, statement which respectivly contain the number of errors / warnings\ + messages and the total number of statements analyzed. This is used by the \ + global evaluation report (R0004).'}), + + ('comment', + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'group': 'Reports', + 'help' : 'Add a comment according to your evaluation note. \ +This is used by the global evaluation report (R0004).'}), + + ('enable-report', + {'type' : 'csv', 'metavar': '<rpt ids>', + 'group': 'Reports', + 'help' : 'Enable the report(s) with the given id(s).'}), + + ('disable-report', + {'type' : 'csv', 'metavar': '<rpt ids>', + 'group': 'Reports', + 'help' : 'Disable the report(s) with the given id(s).'}), + + ('enable-msg-cat', + {'type' : 'csv', 'metavar': '<msg cats>', + 'group': 'Messages control', + 'help' : 'Enable all messages in the listed categories.'}), + + ('disable-msg-cat', + {'type' : 'csv', 'metavar': '<msg cats>', + 'group': 'Messages control', + 'help' : 'Disable all messages in the listed categories.'}), + + ('enable-msg', + {'type' : 'csv', 'metavar': '<msg ids>', + 'group': 'Messages control', + 'help' : 'Enable the message(s) with the given id(s).'}), + + ('disable-msg', + {'type' : 'csv', 'metavar': '<msg ids>', + 'group': 'Messages control', + 'help' : 'Disable the message(s) with the given id(s).'}), + ) + 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 / astng manager + self.reporter = None + self.manager = ASTNGManager() + 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.options + self.option_groups = option_groups + PyLinter.option_groups + self._options_methods = { + 'enable-report': self.enable_report, + 'disable-report': self.disable_report, + 'enable-msg': self.enable_message, + 'disable-msg': self.disable_message, + 'enable-msg-cat': self.enable_message_category, + 'disable-msg-cat': self.disable_message_category} + full_version = '%%prog %s, \nastng %s, common %s\nPython %s' % ( + version, astng_version, common_version, sys.version) + OptionsManagerMixIn.__init__(self, usage=__doc__, + version=full_version, + config_file=pylintrc or config.PYLINTRC) + MessagesHandlerMixIn.__init__(self) + ReportsHandlerMixIn.__init__(self) + BaseRawChecker.__init__(self) + # provided reports + self.reports = (('R0001', 'Messages by category', + report_total_messages_stats), + ('R0002', '% errors / warnings by module', + report_messages_by_module_stats), + ('R0003', 'Messages', + report_messages_stats), + ('R0004', 'Global evaluation', + self.report_evaluation), + ) + self.register_checker(self) + self._dynamic_plugins = [] + self.load_provider_defaults() + self.set_reporter(reporter or TextReporter(sys.stdout)) + + def load_plugin_modules(self, modnames): + """take a list of module names which are pylint plugins and load + and register them + """ + for modname in modnames: + if modname in self._dynamic_plugins: + continue + self._dynamic_plugins.append(modname) + module = load_module_from_name(modname) + module.register(self) + + def set_reporter(self, reporter): + """set the reporter used to display messages and reports""" + self.reporter = reporter + reporter.linter = self + + def set_option(self, opt_name, value, action=None, opt_dict=None): + """overridden from configuration.OptionsProviderMixin to handle some + special options + """ + if opt_name in self._options_methods: + if value: + meth = self._options_methods[opt_name] + value = check_csv(None, opt_name, value) + if isinstance(value, (list, tuple)): + for _id in value : + meth(_id) + else : + meth(value) + elif opt_name == 'cache-size': + self.manager.set_cache_size(int(value)) + elif opt_name == 'output-format': + self.set_reporter(REPORTER_OPT_MAP[value.lower()]()) + elif opt_name in ('enable-checker', 'disable-checker'): + if not value: + return + checkerids = [v.lower() for v in check_csv(None, opt_name, value)] + self.enable_checkers(checkerids, opt_name == 'enable-checker') + BaseRawChecker.set_option(self, opt_name, value, action, opt_dict) + + # checkers manipulation methods ########################################### + + def register_checker(self, checker): + """register a new checker + + checker is an object implementing IRawChecker or / and IASTNGChecker + """ + assert checker.priority <= 0, 'checker priority can\'t be >= 0' + self._checkers[checker.name] = checker + if hasattr(checker, 'reports'): + 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 enable_checkers(self, listed, enabled): + """only enable/disable checkers from the given list""" + if enabled: # if we are activating a checker; deactivate them all first + for checker in self._checkers.values(): + if not checker.may_be_disabled: + continue + checker.enable(not enabled) + for checkerid in listed: + try: + checker = self._checkers[checkerid] + except KeyError: + raise Exception('no checker named %s' % checkerid) + checker.enable(enabled) + + def disable_noerror_checkers(self): + """disable all checkers without error messages, and the + 'miscellaneous' checker which can be safely deactivated in debug + mode + """ + for checker in self._checkers.values(): + if checker.name == 'miscellaneous': + checker.enable(False) + continue + # if checker is already explicitly disabled (e.g. rpython), don't + # enable it + if checker.enabled: + for msgid in getattr(checker, 'msgs', {}).keys(): + if msgid[0] == 'E': + checker.enable(True) + break + else: + checker.enable(False) + + # block level option handling ############################################# + # + # see func_block_disable_msg.py test case for expected behaviour + + def process_tokens(self, tokens): + """process tokens from the current module to search for module/block + level options + """ + comment = tokenize.COMMENT + newline = tokenize.NEWLINE + #line_num = 0 + for (tok_type, _, start, _, line) in tokens: + if tok_type not in (comment, newline): + continue + #if start[0] == line_num: + # continue + match = OPTION_RGX.search(line) + if match is None: + continue + if match.group(1).strip() == "disable-all": + 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() + #line_num = start[0] + if opt in self._options_methods and not opt.endswith('-report'): + meth = self._options_methods[opt] + for msgid in get_csv(value): + try: + meth(msgid, 'module', start[0]) + except UnknownMessage: + self.add_message('E0012', args=msgid, line=start[0]) + else: + self.add_message('E0011', args=opt, line=start[0]) + + def collect_block_lines(self, node, msg_state): + """walk ast to collect block level options line numbers""" + # recurse on children (depth first) + for child in node.getChildNodes(): + self.collect_block_lines(child, msg_state) + first = node.source_line() + last = node.last_source_line() + for msgid, lines in msg_state.items(): + for lineno, state in lines.items(): + if first <= lineno <= last: + # set state for all lines for this block + first, last = node.block_range(lineno) + for line in xrange(first, last+1): + # do not override existing entries + if not line in self._module_msgs_state.get(msgid, ()): + if line in lines: # state change in the same block + state = lines[line] + try: + self._module_msgs_state[msgid][line] = state + except KeyError: + self._module_msgs_state[msgid] = {line: state} + del lines[lineno] + + + # code checking methods ################################################### + + def check(self, files_or_modules): + """main checking entry: check a list of files or modules from their + name. + """ + self.reporter.include_ids = self.config.include_ids + if not isinstance(files_or_modules, (list, tuple)): + files_or_modules = (files_or_modules,) + filemods = self.expand_files(files_or_modules) + checkers = sort_checkers(self._checkers.values()) + rev_checkers = checkers[:] + rev_checkers.reverse() + # notify global begin + for checker in checkers: + checker.open() + # prebuild ast for all modules to check + for descr in filemods[:]: + modname, filepath = descr['name'], descr['path'] + self.set_current_module(modname, filepath) + # get the module representation + try: + astng = self.get_astng(filepath, modname) + except SyntaxError, ex: + self.add_message('E0001', line=ex.lineno, args=ex.msg) + astng = None + if astng is None: + filemods.remove(descr) + continue + descr['astng'] = astng + # check modules or packages + for descr in filemods: + modname, filepath = descr['name'], descr['path'] + astng = descr['astng'] + self.base_name = descr['basename'] + self.base_file = descr['basepath'] + 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) + self._ignore_file = False + # fix the current file (if the source file was not available or + # if its actually a c extension + self.current_file = astng.file + self.check_astng_module(astng, checkers) + # notify global end + self.set_current_module('') + for checker in rev_checkers: + checker.close() + + def expand_files(self, files_or_modules): + """take a list of files/modules/packages and return the list of tuple + (file, module name) which have to be actually checked + """ + result = [] + 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: + self.set_current_module(modname) + self.add_message('F0003', args=modname) + continue + except ImportError, ex: + #if __debug__: + # import traceback + # traceback.print_exc() + self.set_current_module(modname) + msg = str(ex).replace(os.getcwd() + os.sep, '') + self.add_message('F0001', args=msg) + continue + 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), + self.config.black_list): + if filepath == subfilepath: + continue + submodname = '.'.join(modpath_from_file(subfilepath)) + result.append( {'path': subfilepath, 'name': submodname, + 'basepath': filepath, 'basename': modname} ) + 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.current_name = modname + self.current_file = filepath or modname + self.stats['by_module'][modname] = {} + self.stats['by_module'][modname]['statement'] = 0 + for msg_cat in MSG_TYPES.values(): + self.stats['by_module'][modname][msg_cat] = 0 + # 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._module_msg_cats_state = {} + + def get_astng(self, filepath, modname): + """return a astng representation for a module""" + try: + return self.manager.astng_from_file(filepath, modname) + except SyntaxError, ex: + self.add_message('E0001', line=ex.lineno, args=ex.msg) + except KeyboardInterrupt: + raise + except Exception, ex: + #if __debug__: + # import traceback + # traceback.print_exc() + self.add_message('F0002', args=(ex.__class__, ex)) + + + def check_astng_module(self, astng, checkers): + """check a module from its astng representation, real work""" + # call raw checkers if possible + if not astng.pure_python: + self.add_message('I0001', args=astng.name) + else: + #assert astng.file.endswith('.py') + stream = norm_open(astng.file) + # invoke IRawChecker interface on self to fetch module/block + # level options + self.process_module(stream) + if self._ignore_file: + return False + # walk ast to collect line numbers + orig_state = self._module_msgs_state.copy() + self._module_msgs_state = {} + self.collect_block_lines(astng, orig_state) + for checker in checkers: + if implements(checker, IRawChecker) and checker is not self: + stream.seek(0) + checker.process_module(stream) + # generate events to astng checkers + self.astng_events(astng, [checker for checker in checkers + if implements(checker, IASTNGChecker)]) + return True + + def astng_events(self, astng, checkers, _reversed_checkers=None): + """generate event to astng checkers according to the current astng + node and recurse on its children + """ + if _reversed_checkers is None: + _reversed_checkers = checkers[:] + _reversed_checkers.reverse() + if astng.is_statement(): + self.stats['statement'] += 1 + # generate events for this node on each checkers + for checker in checkers: + checker.visit(astng) + # recurse on children + for child in astng.getChildNodes(): + self.astng_events(child, checkers, _reversed_checkers) + for checker in _reversed_checkers: + checker.leave(astng) + + + # IASTNGChecker interface ################################################# + + def open(self): + """initialize counters""" + self.stats = { 'by_module' : {}, + 'by_msg' : {}, + 'statement' : 0 + } + for msg_cat in MSG_TYPES.values(): + self.stats[msg_cat] = 0 + + def close(self): + """close the whole package /module, it's time to make reports ! + + if persistent run, pickle results for later comparison + """ + if self.base_name is not None: + # load old results if any + old_stats = config.load_results(self.base_name) + if self.config.reports: + self.make_reports(self.stats, old_stats) + # save results if persistent run + if self.config.persistent: + config.save_results(self.stats, self.base_name) + + # specific reports ######################################################## + + def report_evaluation(self, sect, stats, old_stats): + """make the global evaluation report""" + # check with at least check 1 statements (usually 0 when there is a + # syntax error preventing pylint from further processing) + if stats['statement'] == 0: + raise EmptyReport() + # get a global note for the code + evaluation = self.config.evaluation + try: + note = eval(evaluation, {}, self.stats) + except Exception, ex: + msg = 'An exception occured while rating: %s' % ex + else: + stats['global_note'] = note + msg = 'Your code has been rated at %.2f/10' % note + if old_stats.has_key('global_note'): + msg += ' (previous run: %.2f/10)' % old_stats['global_note'] + if self.config.comment: + msg = '%s\n%s' % (msg, config.get_note_message(note)) + sect.append(Text(msg)) + +# some reporting functions #################################################### + +def report_total_messages_stats(sect, stats, old_stats): + """make total errors / warnings report""" + lines = ['type', 'number', 'previous', 'difference'] + lines += table_lines_from_stats(stats, old_stats, + ('convention', 'refactor', + 'warning', 'error')) + sect.append(Table(children=lines, cols=4, rheaders=1)) + +def report_messages_stats(sect, stats, _): + """make messages type report""" + if not stats['by_msg']: + # don't print this report when we didn't detected any errors + raise EmptyReport() + in_order = [(value, msg_id) + for msg_id, value in stats['by_msg'].items() + if not msg_id.startswith('I')] + in_order.sort() + in_order.reverse() + lines = ('message id', 'occurences') + for value, msg_id in in_order: + lines += (msg_id, str(value)) + sect.append(Table(children=lines, cols=2, rheaders=1)) + +def report_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'].keys(): + mod_total = stats['by_module'][module][m_type] + if total == 0: + percent = 0 + else: + percent = float((mod_total)*100) / total + by_mod.setdefault(module, {})[m_type] = percent + sorted_result = [] + for module, mod_info in by_mod.items(): + sorted_result.append((mod_info['error'], + mod_info['warning'], + mod_info['refactor'], + mod_info['convention'], + module)) + sorted_result.sort() + sorted_result.reverse() + lines = ['module', 'error', 'warning', 'refactor', 'convention'] + for line in sorted_result: + if line[0] == 0 and line[1] == 0: + break + lines.append(line[-1]) + for val in line[:-1]: + lines.append('%.2f' % val) + if len(lines) == 5: + raise EmptyReport() + sect.append(Table(children=lines, cols=5, rheaders=1)) + + + +# utilities ################################################################### + +# this may help to import modules using gettext + +try: + __builtins__._ = str +except AttributeError: + __builtins__['_'] = str + + +def preprocess_options(args, search_for): + """look for some options (keys of <search_for>) which have to be processed + before others + + values of <search_for> are callback functions to call when the option is + found + """ + 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] + del args[i] + if takearg and val is None: + val = args[i] + del args[i] + cb(option, val) + except KeyError: + i += 1 + else: + i += 1 + +class Run: + """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): + self._rcfile = None + self._plugins = [] + preprocess_options(args, { + # option: (callback, takearg) + 'rpython-mode': (self.cb_rpython_mode, False), + 'rcfile': (self.cb_set_rcfile, True), + 'load-plugins': (self.cb_add_plugins, True), + }) + 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', 'type' : 'string', 'metavar': '<code>', + 'callback' : cb_init_hook, + 'help' : 'Python code to execute, usually for sys.path \ +manipulation such as pygtk.require().'}), + + ('rpython-mode', + {'action' : 'callback', 'callback' : lambda *args: 1, + 'help' : 'enable the rpython checker which is disabled by default' + }), + #'help' : 'Run into Restricted Python analysis mode.'}), + + ('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', + '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."}), + + ('errors-only', + {'action' : 'callback', 'callback' : self.cb_debug_mode, + 'short': 'e', + 'help' : '''In debug mode, checkers without error messages are \ +disabled and for others, only the ERROR messages are displayed, and no reports \ +are done by default'''}), + + ('profile', + {'type' : 'yn', 'metavar' : '<y_or_n>', + 'default': False, + 'help' : 'Profiled execution.'}), + + ), option_groups=self.option_groups, + reporter=reporter, pylintrc=self._rcfile) + # register standard checkers + from pylint import checkers + checkers.initialize(linter) + # load command line plugins + linter.load_plugin_modules(self._plugins) + # add some help section + linter.add_help_section('Environment variables', config.ENV_HELP) + linter.add_help_section('Output', ''' +Using the default text output, the message format is : + MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE +There are 5 kind of message types : + * (C) convention, for programming standard violation + * (R) refactor, for bad code smell + * (W) warning, for python specific problems + * (E) error, for probable bugs in the code + * (F) fatal, if an error occured which prevented pylint from doing further \ +processing. + ''') + # read configuration + #linter.load_provider_defaults() + linter.read_config_file() + # is there some additional plugins in the file configuration, in + config_parser = linter._config_parser + if config_parser.has_option('MASTER', 'load-plugins'): + plugins = get_csv(config_parser.get('MASTER', 'load-plugins')) + linter.load_plugin_modules(plugins) + # now we can load file config and command line, plugins (which can + # provide options) have been registered + linter.load_config_file() + if reporter: + # if a custom reporter is provided as argument, it may be overriden + # 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) + args = linter.load_command_line_configuration(args) + if not args: + print linter.help() + sys.exit(1) + # insert current working directory to the python path to have a correct + # behaviour + sys.path.insert(0, os.getcwd()) + if self.linter.config.profile: + print >> sys.stderr, '** profiled run' + from hotshot import Profile, stats + prof = Profile('stones.prof') + prof.runcall(linter.check, args) + prof.close() + data = stats.load('stones.prof') + data.strip_dirs() + data.sort_stats('time', 'calls') + data.print_stats(30) + else: + linter.check(args) + sys.path.pop(0) + + def cb_rpython_mode(self, name, value): + from pylint.checkers.rpython import RPythonChecker + RPythonChecker.enabled = True + #from pylint.rlint import RPyLinter + #self.LinterClass = RPyLinter + + def cb_set_rcfile(self, name, value): + """callback for option preprocessing (ie before optik parsing)""" + self._rcfile = value + + def cb_add_plugins(self, name, value): + """callback for option preprocessing (ie before optik parsing)""" + self._plugins.extend(get_csv(value)) + + def cb_debug_mode(self, *args, **kwargs): + """debug mode: + * checkers without error messages are disabled + * for others, only the ERROR messages are displayed + * disable reports + * do not save execution information + """ + self.linter.disable_noerror_checkers() + self.linter.set_option('disable-msg-cat', ('W', 'C', 'R', 'F', 'I')) + self.linter.set_option('reports', False) + self.linter.set_option('persistent', False) + + + def cb_generate_config(self, *args, **kwargs): + """optik callback for sample config file generation""" + self.linter.generate_config(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, opt_name, value, parser): + """optik callback for printing some help about a particular message""" + self.linter.help_message(get_csv(value)) + sys.exit(0) + + def cb_list_messages(self, option, opt_name, value, parser): + """optik callback for printing available messages""" + self.linter.list_messages() + sys.exit(0) + +def cb_init_hook(option, opt_name, value, parser): + """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 new file mode 100644 index 0000000..1e58bc5 --- /dev/null +++ b/man/pylint.1 @@ -0,0 +1,252 @@ +.TH pylint 1 "2008-2-7" pylint +.SH NAME +.B pylint +\- python code static checker + +.SH SYNOPSIS +.B pylint +[ +.I OPTIONS +] [ +.I <arguments> +] + +.SH DESCRIPTION +.B pylint +is a Python source code analyzer which looks for programming +errors, helps enforcing a coding standard and sniffs for some code +smells (as defined in Martin Fowler's Refactoring book) + +Pylint can be seen as another PyChecker since nearly all tests you +can do with PyChecker can also be done with Pylint. However, Pylint +offers some more features, like checking length of lines of code, +checking if variable names are well-formed according to your coding +standard, or checking if declared interfaces are truly implemented, +and much more. + +Additionally, it is possible to write plugins to add your own checks. + +.SH OPTIONS +.IP "--version" +show program's version number and exit +.IP "--help, -h" +show this help message and exit + +.SH MASTER +.IP "--rcfile=<file>" +Specify a configuration file. +.IP "--init-hook=<code>" +Python code to execute, usually for sys.path manipulation such as pygtk.require(). +.IP "--rpython-mode" +enable the rpython checker which is disabled by default +.IP "--errors-only, -e" +In debug mode, checkers without error messages are disabled and for others, only the ERROR messages are displayed, and no reports are done by default +.IP "--profile=<y_or_n>" +Profiled execution. [current: %default] +.IP "--ignore=<file>" +Add <file or directory> to the black list. It should be a base name, not a path. You may set this option multiple times. [current: %default] +.IP "--persistent=<y_or_n>" +Pickle collected data for later comparisons. [current: %default] +.IP "--cache-size=<size>" +Set the cache size for astng objects. [current: %default] +.IP "--load-plugins=<modules>" +List of plugins (as comma separated values of python modules names) to load, usually to register additional checkers. [current: %default] + +.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 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. +.IP "--generate-man" +Generate pylint's man page. + +.SH MESSAGES CONTROL +.IP "--enable-checker=<checker ids>" +Enable only checker(s) with the given id(s). This option conflicts with the disable-checker option +.IP "--disable-checker=<checker ids>" +Enable all checker(s) except those with the given id(s). This option conflicts with the enable-checker option +.IP "--enable-msg-cat=<msg cats>" +Enable all messages in the listed categories. +.IP "--disable-msg-cat=<msg cats>" +Disable all messages in the listed categories. +.IP "--enable-msg=<msg ids>" +Enable the message(s) with the given id(s). +.IP "--disable-msg=<msg ids>" +Disable the message(s) with the given id(s). + +.SH REPORTS +.IP "--output-format=<format>, -f <format>" +Set the output format. Available formats are text, parseable, colorized, msvs (visual studio) and html [current: %default] +.IP "--include-ids=<y_or_n>, -i <y_or_n>" +Include message's id in output [current: %default] +.IP "--files-output=<y_or_n>" +Put messages in a separate file for each module / package specified on the command line instead of printing them on stdout. Reports (if any) will be written in a file name "pylint_global.[txt|html]". [current: %default] +.IP "--reports=<y_or_n>, -r <y_or_n>" +Tells wether to display a full report or only the messages [current: %default] +.IP "--evaluation=<python_expression>" +Python expression which should return a note less than 10 (10 is the highest note). You have access to the variables errors warning, statement which respectivly contain the number of errors / warnings messages and the total number of statements analyzed. This is used by the global evaluation report (R0004). [current: %default] +.IP "--comment=<y_or_n>" +Add a comment according to your evaluation note. This is used by the global evaluation report (R0004). [current: %default] +.IP "--enable-report=<rpt ids>" +Enable the report(s) with the given id(s). +.IP "--disable-report=<rpt ids>" +Disable the report(s) with the given id(s). + +.SH BASIC +.IP "--required-attributes=<attributes>" +Required attributes for module, separated by a comma [current: %default] +.IP "--no-docstring-rgx=<regexp>" +Regular expression which should only match functions or classes name which do not require a docstring [current: %default] +.IP "--module-rgx=<regexp>" +Regular expression which should only match correct module names [current: %default] +.IP "--const-rgx=<regexp>" +Regular expression which should only match correct module level names [current: %default] +.IP "--class-rgx=<regexp>" +Regular expression which should only match correct class names [current: %default] +.IP "--function-rgx=<regexp>" +Regular expression which should only match correct function names [current: %default] +.IP "--method-rgx=<regexp>" +Regular expression which should only match correct method names [current: %default] +.IP "--attr-rgx=<regexp>" +Regular expression which should only match correct instance attribute names [current: %default] +.IP "--argument-rgx=<regexp>" +Regular expression which should only match correct argument names [current: %default] +.IP "--variable-rgx=<regexp>" +Regular expression which should only match correct variable names [current: %default] +.IP "--inlinevar-rgx=<regexp>" +Regular expression which should only match correct list comprehension / generator expression variable names [current: %default] +.IP "--good-names=<names>" +Good variable names which should always be accepted, separated by a comma [current: %default] +.IP "--bad-names=<names>" +Bad variable names which should always be refused, separated by a comma [current: %default] +.IP "--bad-functions=<builtin function names>" +List of builtins function names that should not be used, separated by a comma [current: %default] + +.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: %default] +.IP "--defining-attr-methods=<method names>" +List of method names used to declare (i.e. assign) instance attributes. [current: %default] + +.SH DESIGN +.IP "--max-args=<int>" +Maximum number of arguments for function / method [current: %default] +.IP "--max-locals=<int>" +Maximum number of locals for function / method body [current: %default] +.IP "--max-returns=<int>" +Maximum number of return / yield for function / method body [current: %default] +.IP "--max-branchs=<int>" +Maximum number of branch for function / method body [current: %default] +.IP "--max-statements=<int>" +Maximum number of statements in function / method body [current: %default] +.IP "--max-parents=<num>" +Maximum number of parents for a class (see R0901). [current: %default] +.IP "--max-attributes=<num>" +Maximum number of attributes for a class (see R0902). [current: %default] +.IP "--min-public-methods=<num>" +Minimum number of public methods for a class (see R0903). [current: %default] +.IP "--max-public-methods=<num>" +Maximum number of public methods for a class (see R0904). [current: %default] + +.SH FORMAT +.IP "--max-line-length=<int>" +Maximum number of characters on a single line. [current: %default] +.IP "--max-module-lines=<int>" +Maximum number of lines in a module [current: %default] +.IP "--indent-string=<string>" +String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 tab). [current: %default] + +.SH IMPORTS +.IP "--deprecated-modules=<modules>" +Deprecated modules which should not be used, separated by a comma [current: %default] +.IP "--import-graph=<file.dot>" +Create a graph of every (i.e. internal and external) dependencies in the given file (report R0402 must not be disabled) [current: %default] +.IP "--ext-import-graph=<file.dot>" +Create a graph of external dependencies in the given file (report R0402 must not be disabled) [current: %default] +.IP "--int-import-graph=<file.dot>" +Create a graph of internal dependencies in the given file (report R0402 must not be disabled) [current: %default] + +.SH MISCELLANEOUS +.IP "--notes=<comma separated values>" +List of note tags to take in consideration, separated by a comma. [current: %default] + +.SH SIMILARITIES +.IP "--min-similarity-lines=<int>" +Minimum lines number of a similarity. [current: %default] +.IP "--ignore-comments=<y or n>" +Ignore comments when computing similarities. [current: %default] +.IP "--ignore-docstrings=<y or n>" +Ignore docstrings when computing similarities. [current: %default] + +.SH TYPECHECK +.IP "--ignore-mixin-members=<y_or_n>" +Tells wether missing members accessed in mixin class should be ignored. A mixin class is detected if its name ends with "mixin" (case insensitive). [current: %default] +.IP "--ignored-classes=<members names>" +List of classes names for which member attributes should not be checked (useful for classes with attributes dynamicaly set). [current: %default] +.IP "--zope=<y_or_n>" +When zope mode is activated, consider the acquired-members option to ignore access to some undefined attributes. [current: %default] +.IP "--acquired-members=<members names>" +List of members which are usually get through zope's acquisition mecanism and so shouldn't trigger E0201 when accessed (need zope=yes to be considered). [current: %default] + +.SH VARIABLES +.IP "--init-import=<y_or_n>" +Tells wether we should check for unused import in __init__ files. [current: %default] +.IP "--dummy-variables-rgx=<regexp>" +A regular expression matching names used for dummy variables (i.e. not used). [current: %default] +.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: %default] + +.SH ENVIRONMENT VARIABLES + +The following environment variables are used : + * PYLINTHOME + path to the directory where data of persistent run will be stored. If not +found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working +directory) . The current PYLINTHOME is /home/syt/.pylint.d. + * PYLINTRC + path to the configuration file. If not found, it will use the first +existant file in ~/.pylintrc, /etc/pylintrc. The current PYLINTRC is +None. + +.SH OUTPUT + +Using the default text output, the message format is : + MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE +There are 5 kind of message types : + * (C) convention, for programming standard violation + * (R) refactor, for bad code smell + * (W) warning, for python specific problems + * (E) error, for probable bugs in the code + * (F) fatal, if an error occured which prevented pylint from doing further processing. + +.SH SEE ALSO +/usr/share/doc/pythonX.Y-pylint/ + +.SH COPYRIGHT +Copyright (c) 2003-2008 Sylvain Thenault (thenault@gmail.com). +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., 59 Temple Place, Suite 330, Boston, +MA 02111-1307 USA. +.SH BUGS +Please report bugs on the project's mailing list: +mailto://python-projects@logilab.org + +.SH AUTHOR +Sylvain Thenault <sylvain.thenault@logilab.fr> + diff --git a/reporters/__init__.py b/reporters/__init__.py new file mode 100644 index 0000000..b0e1bd5 --- /dev/null +++ b/reporters/__init__.py @@ -0,0 +1,67 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""utilities methods and classes for reporters + + Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + +import sys + +CMPS = ['=', '-', '+'] + +def diff_string(old, new): + """given a old and new int value, return a string representing the + difference + """ + diff = abs(old - new) + diff_str = "%s%s" % (CMPS[cmp(old, new)], diff and ('%.2f' % diff) or '') + return diff_str + + +class EmptyReport(Exception): + """raised when a report is empty and so should not be displayed""" + +class BaseReporter: + """base class for reporters""" + + extension = '' + + def __init__(self, output=None): + self.linter = None + self.include_ids = None + self.section = 0 + self.out = None + self.set_output(output) + + def set_output(self, output=None): + """set output stream""" + if output is None: + output = sys.stdout + self.out = output + + def writeln(self, string=''): + """write a line in the output buffer""" + print >> self.out, string + + def display_results(self, layout): + """display results encapsulated in the layout tree""" + self.section = 0 + if self.include_ids and hasattr(layout, 'report_id'): + layout.children[0].children[0].data += ' (%s)' % layout.report_id + self._display(layout) + + def _display(self, layout): + """display the layout""" + raise NotImplementedError() + diff --git a/reporters/html.py b/reporters/html.py new file mode 100644 index 0000000..d25cc2e --- /dev/null +++ b/reporters/html.py @@ -0,0 +1,65 @@ +# Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com). +# Copyright (c) 2003-2006 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""HTML reporter +""" + +__revision__ = "$Id: html.py,v 1.14 2006-03-08 15:53:41 syt Exp $" + +import sys +from cgi import escape + +from logilab.common.ureports import HTMLWriter, Section, Table + +from pylint.interfaces import IReporter +from pylint.reporters import BaseReporter + + +class HTMLReporter(BaseReporter): + """report messages and layouts in HTML + """ + + __implements__ = IReporter + extension = 'html' + + def __init__(self, output=sys.stdout): + BaseReporter.__init__(self, output) + self.msgs = [] + + def add_message(self, msg_id, location, msg): + """manage message of different type and in the context of path""" + module, obj, line = location[1:] + if self.include_ids: + sigle = msg_id + else: + sigle = msg_id[0] + self.msgs += [sigle, module, obj, str(line), escape(msg)] + + def _display(self, layout): + """launch layouts display + + 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 = ['type', 'module', 'object', 'line', 'message'] + msgs += self.msgs + sect = Section('Messages') + layout.append(sect) + sect.append(Table(cols=5, children=msgs, rheaders=1)) + self.msgs = [] + HTMLWriter().format(layout, self.out) + diff --git a/reporters/text.py b/reporters/text.py new file mode 100644 index 0000000..40c28ec --- /dev/null +++ b/reporters/text.py @@ -0,0 +1,156 @@ +# Copyright (c) 2003-2007 Sylvain Thenault (thenault@gmail.com). +# Copyright (c) 2003-2007 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""Plain text reporters: + +:text: the default one grouping messages by module +:parseable: + standard parseable output with full module path on each message (for + editor integration) +:colorized: an ANSI colorized text reporter + +""" + +import os +import sys + +from logilab.common.ureports import TextWriter +from logilab.common.textutils import colorize_ansi + +from pylint.interfaces import IReporter +from pylint.reporters import BaseReporter + +TITLE_UNDERLINES = ['', '=', '-', '.'] + + +class TextReporter(BaseReporter): + """reports messages and layouts in plain text + """ + + __implements__ = IReporter + extension = 'txt' + + def __init__(self, output=sys.stdout): + BaseReporter.__init__(self, output) + self._modules = {} + + def add_message(self, msg_id, location, msg): + """manage message of different type and in the context of path""" + module, obj, line = location[1:] + if not self._modules.has_key(module): + if module: + self.writeln('************* Module %s' % module) + self._modules[module] = 1 + else: + self.writeln('************* %s' % module) + if obj: + obj = ':%s' % obj + if self.include_ids: + sigle = msg_id + else: + sigle = msg_id[0] + self.writeln('%s:%3s%s: %s' % (sigle, line, obj, msg)) + + def _display(self, layout): + """launch layouts display""" + print >> self.out + TextWriter().format(layout, self.out) + + +class ParseableTextReporter(TextReporter): + """a reporter very similar to TextReporter, but display messages in a form + recognized by most text editors : + + <filename>:<linenum>:<msg> + """ + line_format = '%(path)s:%(line)s: [%(sigle)s%(obj)s] %(msg)s' + + def __init__(self, output=sys.stdout, relative=True): + TextReporter.__init__(self, output) + if relative: + self._prefix = os.getcwd() + os.sep + else: + self._prefix = '' + + def add_message(self, msg_id, location, msg): + """manage message of different type and in the context of path""" + path, _, obj, line = location + if obj: + obj = ', %s' % obj + if self.include_ids: + sigle = msg_id + else: + sigle = msg_id[0] + if self._prefix: + path = path.replace(self._prefix, '') + self.writeln(self.line_format % locals()) + +class VSTextReporter(ParseableTextReporter): + """Visual studio text reporter""" + line_format = '%(path)s(%(line)s): [%(sigle)s%(obj)s] %(msg)s' + +class ColorizedTextReporter(TextReporter): + """Simple TextReporter that colorizes text output""" + + COLOR_MAPPING = { + "I" : ("green", None), + 'C' : (None, "bold"), + 'R' : ("magenta", "bold, italic"), + 'W' : ("blue", None), + 'E' : ("red", "bold"), + 'F' : ("red", "bold, underline"), + 'S' : ("yellow", "inverse"), # S stands for module Separator + } + + def __init__(self, output=sys.stdout, color_mapping = None): + TextReporter.__init__(self, output) + self.color_mapping = color_mapping or \ + dict(ColorizedTextReporter.COLOR_MAPPING) + + + def _get_decoration(self, msg_id): + """Returns the tuple color, style associated with msg_id as defined + in self.color_mapping + """ + try: + return self.color_mapping[msg_id[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 + """ + module, obj, line = location[1:] + if not self._modules.has_key(module): + color, style = self._get_decoration('S') + if module: + modsep = colorize_ansi('************* Module %s' % module, + color, style) + else: + modsep = colorize_ansi('************* %s' % module, + color, style) + self.writeln(modsep) + self._modules[module] = 1 + if obj: + obj = ':%s' % obj + if self.include_ids: + sigle = msg_id + else: + sigle = msg_id[0] + color, style = self._get_decoration(sigle) + msg = colorize_ansi(msg, color, style) + sigle = colorize_ansi(sigle, color, style) + self.writeln('%s:%3s%s: %s' % (sigle, line, obj, msg)) + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..78cfd76 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[bdist_rpm] +packager = Sylvain Thenault <sylvain.thenault@logilab.fr> +provides = pylint diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..befbfcb --- /dev/null +++ b/setup.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python +# pylint: disable-msg=W0142,W0403,W0404,E0611,W0613,W0622,W0622,W0704 +# pylint: disable-msg=R0904,C0103 +# +# Copyright (c) 2003 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Generic Setup script, takes package info from __pkginfo__.py file """ + +from __future__ import nested_scopes + +__revision__ = '$Id: setup.py,v 1.26 2006-03-05 15:47:17 syt Exp $' + +import os +import sys +import shutil +from distutils.core import setup +from distutils.command import install_lib +from os.path import isdir, exists, join, walk + +# import required features +from __pkginfo__ import modname, version, license, short_desc, long_desc, \ + web, author, author_email, classifiers +# import optional features +try: + from __pkginfo__ import distname +except ImportError: + distname = modname +try: + from __pkginfo__ import scripts +except ImportError: + scripts = [] +try: + from __pkginfo__ import data_files +except ImportError: + data_files = None +try: + from __pkginfo__ import subpackage_of +except ImportError: + subpackage_of = None +try: + from __pkginfo__ import include_dirs +except ImportError: + include_dirs = [] +try: + from __pkginfo__ import ext_modules +except ImportError: + ext_modules = None + +BASE_BLACKLIST = ('CVS', 'debian', 'dist', 'build', '__buildlog', '.svn') +IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc') + + +def ensure_scripts(linux_scripts): + """ + Creates the proper script names required for each platform + (taken from 4Suite) + """ + from distutils import util + if util.get_platform()[:3] == 'win': + scripts_ = [script + '.bat' for script in linux_scripts] + else: + scripts_ = linux_scripts + return scripts_ + + +def get_packages(directory, prefix): + """return a list of subpackages for the given directory + """ + result = [] + for package in os.listdir(directory): + absfile = join(directory, package) + if isdir(absfile): + if exists(join(absfile, '__init__.py')) or \ + package in ('test', 'tests'): + if prefix: + result.append('%s.%s' % (prefix, package)) + else: + result.append(package) + result += get_packages(absfile, result[-1]) + return result + +def export(from_dir, to_dir, + blacklist=BASE_BLACKLIST, + ignore_ext=IGNORED_EXTENSIONS): + """make a mirror of from_dir in to_dir, omitting directories and files + listed in the black list + """ + def make_mirror(arg, directory, fnames): + """walk handler""" + for norecurs in blacklist: + try: + fnames.remove(norecurs) + except ValueError: + pass + for filename in fnames: + # don't include binary files + if filename[-4:] in ignore_ext: + continue + if filename[-1] == '~': + continue + src = '%s/%s' % (directory, filename) + dest = to_dir + src[len(from_dir):] + print >> sys.stderr, src, '->', dest + if os.path.isdir(src): + if not exists(dest): + os.mkdir(dest) + else: + if exists(dest): + os.remove(dest) + shutil.copy2(src, dest) + try: + os.mkdir(to_dir) + except OSError, ex: + # file exists ? + import errno + if ex.errno != errno.EEXIST: + raise + walk(from_dir, make_mirror, None) + + +EMPTY_FILE = '"""generated file, don\'t modify or your data will be lost"""\n' + +class MyInstallLib(install_lib.install_lib): + """extend install_lib command to handle package __init__.py and + include_dirs variable if necessary + """ + def run(self): + """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) + export(directory, dest) + +def install(**kwargs): + """setup entry point""" + if subpackage_of: + package = subpackage_of + '.' + modname + kwargs['package_dir'] = {package : '.'} + packages = [package] + get_packages(os.getcwd(), package) + else: + kwargs['package_dir'] = {modname : '.'} + packages = [modname] + get_packages(os.getcwd(), modname) + kwargs['packages'] = packages + return setup(name = distname, + version = version, + license =license, + description = short_desc, + long_description = long_desc, + author = author, + author_email = author_email, + url = web, + classifiers = classifiers, + scripts = ensure_scripts(scripts), + data_files=data_files, + ext_modules=ext_modules, + cmdclass={'install_lib': MyInstallLib}, + **kwargs + ) + +if __name__ == '__main__' : + install() diff --git a/test/fulltest.sh b/test/fulltest.sh new file mode 100755 index 0000000..a625339 --- /dev/null +++ b/test/fulltest.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +if [ $@ ] ; then + PYVERSIONS=$@ +else + PYVERSIONS="2.2 2.3 2.4" +fi + +for ver in $PYVERSIONS; do + echo "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" + echo `python$ver -V` + echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + python$ver runtests.py + echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + echo `python$ver -V` -OO + python$ver -OO runtests.py +done
\ No newline at end of file diff --git a/test/func_test.py b/test/func_test.py new file mode 100644 index 0000000..b6e32d4 --- /dev/null +++ b/test/func_test.py @@ -0,0 +1,219 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""functional/non regression tests for pylint""" + +import unittest +import sys +import re +import new +from os import linesep +from os.path import exists + +from logilab.common import testlib + +from utils import get_tests_info, fix_path, TestReporter + +from logilab.astng import MANAGER +from pylint.lint import PyLinter +from pylint import checkers + +test_reporter = TestReporter() +linter = PyLinter() +linter.set_reporter(test_reporter) +linter.config.persistent = 0 +checkers.initialize(linter) +linter.global_set_option('required-attributes', ('__revision__',)) + +PY23 = sys.version_info >= (2, 3) +PY24 = sys.version_info >= (2, 4) +PY25 = sys.version_info >= (2, 5) + + +if linesep != '\n': + LINE_RGX = re.compile(linesep) + def ulines(string): + return LINE_RGX.sub('\n', string) +else: + def ulines(string): + return string + +INFO_TEST_RGX = re.compile('^func_i\d\d\d\d$') + +def exception_str(ex): + """function used to replace default __str__ method of exception instances""" + return 'in %s\n:: %s' % (ex.file, ', '.join(ex.args)) + +class LintTestUsingModule(testlib.TestCase): + package = 'input' + linter = linter + def setUp(self): + MANAGER.set_cache_size(200) # reset cache + 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 _test(self, tocheck): + if INFO_TEST_RGX.match(self.module): + self.linter.enable_message_category('I') + else: + self.linter.disable_message_category('I') + try: + self.linter.check(tocheck) + except Exception, ex: + # need finalization to restore a correct state + self.linter.reporter.finalize() + ex.file = tocheck + ex.__str__ = new.instancemethod(exception_str, ex, None) + raise + if self.module.startswith('func_noerror_'): + expected = '' + else: + output = open(self.output) + expected = output.read().strip() + output.close() + got = self.linter.reporter.finalize().strip() + try: + self.assertLinesEquals(got, expected) + except Exception, ex: + # doesn't work with py 2.5 + #ex.file = tocheck + #ex.__str__ = new.instancemethod(exception_str, ex, None) + raise AssertionError('%s: %s' % (self.module, ex)), None, sys.exc_info()[-1] + +class LintTestUsingFile(LintTestUsingModule): + + def test_functionality(self): + tocheck = [self.package+'/' + self.module + '.py'] + if self.depends: + tocheck += [self.package+'/%s' % name for name, file in self.depends] + self._test(tocheck) + + +class TestTests(testlib.TestCase): + """check that all testable messages have been checked""" + def test(self): + # skip rpython checker and fatal messages + todo = [msgid for msgid in linter._messages.keys() if msgid[1:3] != '12' and msgid[0] != 'F'] + for msgid in test_reporter.message_ids.keys(): + try: + todo.remove(msgid) + except ValueError: + continue + todo.sort() + if PY25: + self.assertEqual(todo, ['E0503', 'E1010', 'I0001']) + elif PY23: + self.assertEqual(todo, ['E0503', 'I0001']) + else: # python < 2.3 + self.assertEqual(todo, ['I0001']) + +#bycat = {} +#for msgid in linter._messages.keys(): +# bycat[msgid[0]] = bycat.setdefault(msgid[0], 0) + 1 +#for cat, val in bycat.items(): +# print '%s: %s' % (cat, val) +#print 'total', sum(bycat.values()) +# +# on 2007/02/17: +# +# W: 48 +# E: 42 +# R: 15 +# C: 13 +# F: 7 +# I: 5 +# total 130 + +def make_tests(filter_rgx): + """generate tests classes from test info + + return the list of generated test classes + """ + if filter_rgx: + is_to_run = re.compile(filter_rgx).match + else: + is_to_run = lambda x: 1 + tests = [] + for module_file, messages_file in get_tests_info('func_', '.py') + [('nonexistant', 'messages/nonexistant.txt')]: + # skip those tests with python >= 2.3 since py2.3 detects them by itself + if PY23 and module_file == "func_unknown_encoding.py": #"func_nonascii_noencoding.py"): + continue + if not PY24: + if module_file == "func_noerror_staticmethod_as_decorator.py" or \ + module_file.endswith('py24.py'): + continue + if not is_to_run(module_file): + continue + base = module_file.replace('func_', '').replace('.py', '') + dependancies = get_tests_info(base, '.py') + + class LintTestUsingModuleTC(LintTestUsingModule): + module = module_file.replace('.py', '') + output = messages_file + depends = dependancies or None + tests.append(LintTestUsingModuleTC) + + if MODULES_ONLY: + continue + + class LintTestUsingFileTC(LintTestUsingFile): + module = module_file.replace('.py', '') + output = exists(messages_file + '2') and (messages_file + '2') or messages_file + depends = dependancies or None + tests.append(LintTestUsingFileTC) + +## # special test for f0003 +## module_file, messages_file in get_tests_info('func_f0003', '.pyc') +## class LintTestSubclass(LintTest): +## module = module_file.replace('.pyc', '') +## output = messages_file +## depends = dependancies or None +## tests.append(LintTestSubclass) + + class LintBuiltinModuleTest(LintTestUsingModule): + output = 'messages/builtin_module.txt' + module = 'sys' + def test_functionality(self): + self._test(['sys']) + tests.append(LintBuiltinModuleTest) + + if not filter_rgx: + # test all features are tested :) + tests.append(TestTests) + + return tests + +FILTER_RGX = None +MODULES_ONLY = False + +def suite(): + return unittest.TestSuite([unittest.makeSuite(test) + for test in make_tests(FILTER_RGX)]) + +if __name__=='__main__': + if '-m' in sys.argv: + MODULES_ONLY = True + sys.argv.remove('-m') + + if len(sys.argv) > 1: + FILTER_RGX = sys.argv[1] + del sys.argv[1] + testlib.unittest_main(defaultTest='suite') + + diff --git a/test/func_test_rpython.py b/test/func_test_rpython.py new file mode 100644 index 0000000..2363c92 --- /dev/null +++ b/test/func_test_rpython.py @@ -0,0 +1,114 @@ +# Copyright (c) 2007 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""functional/non regression tests for the rpython mode of pylint +""" + +import unittest +import sys +import re +import new +from os import linesep +from os.path import exists + +from logilab.common import testlib + +from utils import get_tests_info, fix_path, TestReporter + +from pylint.lint import PyLinter +from pylint import checkers + +test_reporter = TestReporter() +linter = PyLinter() +linter.set_reporter(test_reporter) +linter.config.persistent = 0 +checkers.initialize(linter) +linter.enable_checkers(['rpython'], True) + +from func_test import ulines, LintTestUsingFile + +class RLintTestUsingFile(LintTestUsingFile): + package = 'rpythoninput' + linter = linter + def setUp(self): + if sys.version_info[:2] != (2, 4): + self.skip('only python 2.4 supported for now') + + def test_functionality(self): + tocheck = ['rpythoninput/' + self.module + '.py'] + if self.depends: + tocheck += ['rpythoninput/%s' % name for name, file in self.depends] + self._test(tocheck) + + +class TestTests(testlib.TestCase): + """check that all testable messages have been checked""" + def setUp(self): + if sys.version_info[:2] != (2, 4): + self.skip('only python 2.4 supported for now') + + def test(self): + # skip rpython checker messages + missing = [msgid for msgid in linter._messages.keys() + if msgid[1:3] == '12' and not msgid in test_reporter.message_ids] + self.assertEqual(missing, []) + +def make_tests(filter_rgx): + """generate tests classes from test info + + return the list of generated test classes + """ + if filter_rgx: + is_to_run = re.compile(filter_rgx).match + else: + is_to_run = lambda x: 1 + tests = [] + for module_file, messages_file in get_tests_info('func_', '.py', + 'rpythoninput', 'rpythonmessages'): + if not is_to_run(module_file): + continue + base = module_file.replace('func_', '').replace('.py', '') + dependancies = get_tests_info(base, '.py') + + class LintTestUsingFileTC(RLintTestUsingFile): + module = module_file.replace('.py', '') + output = exists(messages_file + '2') and (messages_file + '2') or messages_file + depends = dependancies or None + tests.append(LintTestUsingFileTC) + + if not filter_rgx: + # test all features are tested :) + tests.append(TestTests) + + return tests + +FILTER_RGX = None +MODULES_ONLY = False + +def suite(): + return unittest.TestSuite([unittest.makeSuite(test) + for test in make_tests(FILTER_RGX)]) + +if __name__=='__main__': + if '-m' in sys.argv: + MODULES_ONLY = True + sys.argv.remove('-m') + + if len(sys.argv) > 1: + FILTER_RGX = sys.argv[1] + del sys.argv[1] + testlib.unittest_main(defaultTest='suite') + + diff --git a/test/func_test_sample_config.py b/test/func_test_sample_config.py new file mode 100644 index 0000000..ec471cc --- /dev/null +++ b/test/func_test_sample_config.py @@ -0,0 +1,31 @@ +# Copyright (c) 2002-2004 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""functional tests using the sample configuration file, should behave exactly +as with the default configuration +""" + +from func_test import * + +from os.path import join + +import pylint +sample_config = join(pylint.__path__[0], 'examples', 'pylintrc') +linter.load_file_configuration(sample_config) +linter.global_set_option('required-attributes', ('__revision__',)) +linter.set_reporter(test_reporter) + +if __name__=='__main__': + unittest.main(defaultTest='suite') diff --git a/test/genbuiltins.py b/test/genbuiltins.py new file mode 100644 index 0000000..c1a433f --- /dev/null +++ b/test/genbuiltins.py @@ -0,0 +1,63 @@ +from os import remove +from glob import glob + +code = """ +import os + +def function(): + retval = %s + os.write(2, str(retval) + '\\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None +""" + +msg = "E: 5:function: Using unavailable builtin '%s'" + +checks = [ + 'set([1, 2, 3])', 'frozenset()', + 'dict()', 'unicode("f")', "complex(3, 4)", + 'file("foo.txt")', 'open("foo.txt")', + + 'super(str)', 'object()', + + 'raw_input()', 'input()', 'buffer()', + + 'vars()', 'locals()', 'globals()', 'dir()', + + 'getattr(function, "bar")', 'delattr(function, "bar")', 'setattr(function, "bar", "baz")', + 'staticmethod(function)', 'classmethod(function)', 'property(function)', + + "reduce(function, [])", "filter(function, [])", 'map(function, [])', + 'sum(xrange(4))', 'enumerate(range(5))', + 'sorted(range(5))', 'reversed(range(5))', + + 'callable(function)', 'issubclass(function, str)', + + 'reload(module)', + + 'eval("a == b")', 'compile("a==b", "?", "eval")', 'execfile("foo.py")', + + 'id(function)', 'help(function)', + + 'intern("foo")', 'round(3.4)', 'iter([])', + ] + + +for filename in glob('rpythoninput/func_nobuiltin_*'): + remove(filename) +for filename in glob('rpythonmessages/func_nobuiltin_*'): + remove(filename) +for check in checks: + builtin, _ = check.split('(', 1) + if builtin == 'isinstance': + builtin = 'basestring' + basename = 'func_nobuiltin_%s' % builtin + print basename + file('rpythoninput/%s.py' % basename, 'w').write(code % check) + file('rpythonmessages/%s.txt' % basename, 'w').write(msg % builtin) +print 'generated %s test' % len(checks) diff --git a/test/input/__init__.py b/test/input/__init__.py new file mode 100644 index 0000000..60e92b7 --- /dev/null +++ b/test/input/__init__.py @@ -0,0 +1 @@ +"""test""" diff --git a/test/input/func___future___import_not_first_stmt.py b/test/input/func___future___import_not_first_stmt.py new file mode 100644 index 0000000..2051a7b --- /dev/null +++ b/test/input/func___future___import_not_first_stmt.py @@ -0,0 +1,5 @@ +"""a docstring""" + +__revision__ = 1 +from __future__ import generators + diff --git a/test/input/func___name___access.py b/test/input/func___name___access.py new file mode 100644 index 0000000..25aed5c --- /dev/null +++ b/test/input/func___name___access.py @@ -0,0 +1,21 @@ +# pylint: disable-msg=R0903,W0142 +"""test access to __name__ gives undefined member on new/old class instances +but not on new/old class object +""" + +__revision__ = 1 + +class Aaaa: + """old class""" + def __init__(self): + print self.__name__ + print self.__class__.__name__ +class NewClass(object): + """new class""" + + def __new__(cls, *args, **kwargs): + print 'new', cls.__name__ + return object.__new__(cls, *args, **kwargs) + + def __init__(self): + print 'init', self.__name__ diff --git a/test/input/func_attrs_definition_order.py b/test/input/func_attrs_definition_order.py new file mode 100644 index 0000000..eda02e1 --- /dev/null +++ b/test/input/func_attrs_definition_order.py @@ -0,0 +1,15 @@ +# pylint: disable-msg=R0903 +"""yo""" + +__revision__ = '$I$' + +class Aaaa: + """class with attributes defined in wrong order""" + def __init__(self): + var1 = self._var2 + self._var2 = 3 + print var1 + +class Bbbb(object): + """hop""" + __revision__ = __revision__ # no problemo marge diff --git a/test/input/func_backtick_deprecated.py b/test/input/func_backtick_deprecated.py new file mode 100644 index 0000000..bca85cc --- /dev/null +++ b/test/input/func_backtick_deprecated.py @@ -0,0 +1,4 @@ +"""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 new file mode 100644 index 0000000..b404cb3 --- /dev/null +++ b/test/input/func_bad_assigment_to_exception_var.py @@ -0,0 +1,31 @@ +# pylint:disable-msg=C0103 +"""ho ho ho""" +__revision__ = 'toto' + +import sys + +e = 1 +e2 = 'yo' +e3 = None +try: + raise e, 'toto' +except Exception, ex: + print ex + _, _, tb = sys.exc_info() + raise e2 + + +def func(): + """bla bla bla""" + raise e3 + +def reraise(): + """reraise a catched exception instance""" + try: + raise Exception() + except Exception, exc: + print exc + raise exc + +raise e3 + diff --git a/test/input/func_base_stmt_without_effect.py b/test/input/func_base_stmt_without_effect.py new file mode 100644 index 0000000..8eb8736 --- /dev/null +++ b/test/input/func_base_stmt_without_effect.py @@ -0,0 +1,28 @@ +""" + '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': ('Unnecessary semi-column', + 'Used when a statement is endend by a semi-colon (";"), which \ + isn\'t necessary (that\'s python, not C ;)'), +""" + +__revision__ = '' + +__revision__ + +__revision__ <= 1 + +__revision__.lower() # ok + +[i for i in __revision__] # ko + + +"""inline doc string should use a separated message""" + +__revision__.lower(); # unnecessary ; diff --git a/test/input/func_base_useless_pass.py b/test/input/func_base_useless_pass.py new file mode 100644 index 0000000..67c39fa --- /dev/null +++ b/test/input/func_base_useless_pass.py @@ -0,0 +1,9 @@ +"""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 new file mode 100644 index 0000000..5253411 --- /dev/null +++ b/test/input/func_block_disable_msg.py @@ -0,0 +1,1017 @@ +# pylint: disable-msg=C0302 +"""pylint option block-disable-msg""" +__revision__ = None + +class Foo(object): + """block-disable-msg test""" + + def __init__(self): + pass + + def meth1(self, arg): + """this issues a message""" + print self + + def meth2(self, arg): + """and this one not""" + # pylint: disable-msg=W0613 + print self\ + + "foo" + + def meth3(self): + """test one line disabling""" + # no error + print self.bla # pylint: disable-msg=E1101 + # error + print self.blop + + def meth4(self): + """test re-enabling""" + # pylint: disable-msg=E1101 + # no error + print self.bla + print self.blop + # pylint: enable-msg=E1101 + # error + print self.blip + + def meth5(self): + """test IF sub-block re-enabling""" + # pylint: disable-msg=E1101 + # no error + print self.bla + if self.blop: + # pylint: enable-msg=E1101 + # error + print self.blip + else: + # no error + print self.blip + # no error + print self.blip + + def meth6(self): + """test TRY/EXCEPT sub-block re-enabling""" + # pylint: disable-msg=E1101 + # no error + print self.bla + try: + # pylint: enable-msg=E1101 + # error + print self.blip + except UndefinedName: # pylint: disable-msg=E0602 + # no error + print self.blip + # no error + print self.blip + + def meth7(self): + """test one line block opening disabling""" + if self.blop: # pylint: disable-msg=E1101 + # error + print self.blip + else: + # error + print self.blip + # error + print self.blip + + + def meth8(self): + """test late disabling""" + # error + print self.blip + # pylint: disable-msg=E1101 + # no error + print self.bla + print self.blop + + def meth9(self): + """test re-enabling right after a block with whitespace""" + eris = 5 + + if eris: + print ("In block") + + # pylint: disable-msg=E1101 + # no error + print self.bla + print self.blu + # pylint: enable-msg=E1101 + # error + print self.blip + +class ClassLevelMessage(object): + """should'nt display to much attributes/not enough methods messages + """ + # pylint: disable-msg=R0902,R0903 + + def __init__(self): + self.attr1 = 1 + self.attr2 = 1 + self.attr3 = 1 + self.attr4 = 1 + self.attr5 = 1 + self.attr6 = 1 + self.attr7 = 1 + self.attr8 = 1 + self.attr9 = 1 + self.attr0 = 1 + + def too_complex_but_thats_ok(self, attr1, attr2): + """THIS Method has too much branchs and returns but i don't care + """ + # pylint: disable-msg=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_class_access_protected_members.py b/test/input/func_class_access_protected_members.py new file mode 100644 index 0000000..f588953 --- /dev/null +++ b/test/input/func_class_access_protected_members.py @@ -0,0 +1,30 @@ +# pylint: disable-msg=R0903 +"""test external access to protected class members""" + +__revision__ = '' + +class MyClass: + """class docstring""" + _cls_protected = 5 + + def __init__(self, other): + """init""" + self._protected = 1 + self.public = other + + + def test(self): + """test""" + self._protected += self._cls_protected + print self.public._haha + + def clsmeth(cls): + """this is ok""" + print cls._cls_protected + clsmeth = classmethod(clsmeth) + +INST = MyClass() +print INST.public +print INST._protected +print INST._cls_protected + diff --git a/test/input/func_class_members.py b/test/input/func_class_members.py new file mode 100644 index 0000000..8b2e969 --- /dev/null +++ b/test/input/func_class_members.py @@ -0,0 +1,31 @@ +# pylint: disable-msg=R0903 +"""test class members""" + +__revision__ = '' + +class MyClass: + """class docstring""" + + def __init__(self): + """init""" + self.correct = 1 + + def test(self): + """test""" + self.correct += 2 + self.incorrect += 2 + self.nonexistent1.truc() + self.nonexistent2[1] = 'hehe' + +class XYZMixin: + """access to undefined members should be ignored in mixin classes by + default + """ + def __init__(self): + print self.nonexistent + + +class NewClass(object): + """use object.__setattr__""" + def __init__(self): + self.__setattr__('toto', 'tutu') diff --git a/test/input/func_continue_not_in_loop.py b/test/input/func_continue_not_in_loop.py new file mode 100644 index 0000000..4186aa5 --- /dev/null +++ b/test/input/func_continue_not_in_loop.py @@ -0,0 +1,14 @@ +"""this module produces a SyntaxError at execution time""" + +__revision__ = None + +def run(): + """simple function""" + if True: + continue + else: + break + +if __name__ == '__main__': + run() + diff --git a/test/input/func_dangerous_default.py b/test/input/func_dangerous_default.py new file mode 100644 index 0000000..0bf8727 --- /dev/null +++ b/test/input/func_dangerous_default.py @@ -0,0 +1,17 @@ +"""docstring""" + +__revision__ = '' + +HEHE = {} + +def function1(value = []): + """docstring""" + print value + +def function2(value = HEHE): + """docstring""" + print value + +def function3(value): + """docstring""" + print value diff --git a/test/input/func_docstring.py b/test/input/func_docstring.py new file mode 100644 index 0000000..7f28672 --- /dev/null +++ b/test/input/func_docstring.py @@ -0,0 +1,48 @@ +# pylint: disable-msg=R0201 + +__revision__ = '' + +def function1(value): + # missing docstring + print value + +def function2(value): + """docstring""" + print value + +def function3(value): + """docstring""" + print value + +class AAAA: + # missing docstring + +## class BBBB: +## # missing docstring +## pass + +## class CCCC: +## """yeah !""" +## def method1(self): +## pass + +## def method2(self): +## """ yeah !""" +## pass + + def method1(self): + pass + + def method2(self): + """ yeah !""" + pass + + def __init__(self): + pass + +class DDDD(AAAA): + """yeah !""" + + def __init__(self): + AAAA.__init__(self) + diff --git a/test/input/func_dotted_ancestor.py b/test/input/func_dotted_ancestor.py new file mode 100644 index 0000000..2d2f571 --- /dev/null +++ b/test/input/func_dotted_ancestor.py @@ -0,0 +1,11 @@ +"""bla""" + +__revision__ = 'yo' + + +from input import func_w0233 + +class Aaaa(func_w0233.AAAA): + """test dotted name in ancestors""" + def __init__(self): + func_w0233.AAAA.__init__(self) diff --git a/test/input/func_e0011.py b/test/input/func_e0011.py new file mode 100644 index 0000000..f2bb592 --- /dev/null +++ b/test/input/func_e0011.py @@ -0,0 +1,5 @@ +# pylint:bouboule=1 +"""check unknown option +""" +__revision__ = 1 + diff --git a/test/input/func_e0012.py b/test/input/func_e0012.py new file mode 100644 index 0000000..11ef99f --- /dev/null +++ b/test/input/func_e0012.py @@ -0,0 +1,5 @@ +# pylint:enable-msg=W04044 +"""check unknown option +""" +__revision__ = 1 + diff --git a/test/input/func_e0101.py b/test/input/func_e0101.py new file mode 100644 index 0000000..9158041 --- /dev/null +++ b/test/input/func_e0101.py @@ -0,0 +1,29 @@ +# pylint: disable-msg=R0903 +"""test __init__ return +""" + +__revision__ = 'yo' + +class MyClass: + """dummy class""" + + def __init__(self): + return 1 + +class MyClass2: + """dummy class""" + + def __init__(self): + return + +class MyClass3: + """dummy class""" + + def __init__(self): + return None + +class MyClass4: + """dummy class""" + + def __init__(self): + yield None diff --git a/test/input/func_e0203.py b/test/input/func_e0203.py new file mode 100644 index 0000000..d51de0d --- /dev/null +++ b/test/input/func_e0203.py @@ -0,0 +1,19 @@ +"""check for method without self as first argument +""" + +__revision__ = 0 + + +class Abcd: + """dummy class""" + def __init__(self): + pass + + def abcd(yoo): + """another test""" + + abcd = classmethod(abcd) + + def edf(self): + """justo ne more method""" + print 'yapudju in', self diff --git a/test/input/func_e0204.py b/test/input/func_e0204.py new file mode 100644 index 0000000..ecbc51c --- /dev/null +++ b/test/input/func_e0204.py @@ -0,0 +1,19 @@ +"""check for method without self as first argument +""" + +__revision__ = 0 + + +class Abcd: + """dummy class""" + + def __init__(truc): + """method without self""" + print 1 + + def abdc(yoo): + """another test""" + print yoo + def edf(self): + """just another method""" + print 'yapudju in', self diff --git a/test/input/func_e0205.py b/test/input/func_e0205.py new file mode 100644 index 0000000..2bb1089 --- /dev/null +++ b/test/input/func_e0205.py @@ -0,0 +1,17 @@ +# pylint: disable-msg=R0903 +"""check method hidding ancestor attribute +""" + +__revision__ = '' + +class Abcd: + """dummy""" + def __init__(self): + self.abcd = 1 + +class Cdef(Abcd): + """dummy""" + def abcd(self): + """test + """ + print self diff --git a/test/input/func_e0206.py b/test/input/func_e0206.py new file mode 100644 index 0000000..af32fd8 --- /dev/null +++ b/test/input/func_e0206.py @@ -0,0 +1,20 @@ +# pylint: disable-msg=R0903 +"""check for interface which are not classes""" + +__revision__ = None + +class Abcd: + """dummy""" + __implements__ = __revision__ + + def __init__(self): + self.attr = None + +class Cdef: + """dummy""" + __implements__ = (__revision__, Abcd) + + def __init__(self): + pass + + diff --git a/test/input/func_e0214.py b/test/input/func_e0214.py new file mode 100644 index 0000000..8a4ad32 --- /dev/null +++ b/test/input/func_e0214.py @@ -0,0 +1,18 @@ +"""mcs test""" + +__revision__ = 1 + +class MetaClass(type): + """a very intersting metaclass""" + def __new__(mcs, name, bases, cdict): + print mcs, name, bases, cdict + return type.__new__(mcs, name, bases, cdict) + + def whatever(self): + """should have mcs has first arg""" + print self + + def whatever_really(hop): + """could have anything has first arg""" + print hop + whatever_really = staticmethod(whatever_really) diff --git a/test/input/func_e0601.py b/test/input/func_e0601.py new file mode 100644 index 0000000..b8673df --- /dev/null +++ b/test/input/func_e0601.py @@ -0,0 +1,9 @@ +"""test local variable used before assigment +""" + +__revision__ = 0 + +def function(): + """dummy""" + print aaaa + aaaa = 1 diff --git a/test/input/func_empty_module.py b/test/input/func_empty_module.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/input/func_empty_module.py diff --git a/test/input/func_exceptions_raise_type_error.py b/test/input/func_exceptions_raise_type_error.py new file mode 100644 index 0000000..8c414b6 --- /dev/null +++ b/test/input/func_exceptions_raise_type_error.py @@ -0,0 +1,14 @@ +""" +'E0702': Raising an %s while only classes, instances or string are allowed + +Used when something which is neither a class, an instance or a string is +raised (i.e. a `TypeError` will be raised). +""" + +__revision__ = 1 + +if __revision__: + raise 1 + +if __revision__: + raise None diff --git a/test/input/func_f0001.py b/test/input/func_f0001.py new file mode 100644 index 0000000..af6de24 --- /dev/null +++ b/test/input/func_f0001.py @@ -0,0 +1,4 @@ +"""test astng error +""" +import whatever +__revision__ = None diff --git a/test/input/func_f0401.py b/test/input/func_f0401.py new file mode 100644 index 0000000..f8443fa --- /dev/null +++ b/test/input/func_f0401.py @@ -0,0 +1,9 @@ +"""tset failed import +""" + +__revision__ = 0 + +def function(): + """yo""" + from tutu import toto + print toto diff --git a/test/input/func_fixme.py b/test/input/func_fixme.py new file mode 100644 index 0000000..0cfe2bd --- /dev/null +++ b/test/input/func_fixme.py @@ -0,0 +1,9 @@ +"""docstring""" + +__revision__ = '' + +# FIXME: beep + +def function(): + '''XXX:bop''' + diff --git a/test/input/func_format.py b/test/input/func_format.py new file mode 100644 index 0000000..58649e9 --- /dev/null +++ b/test/input/func_format.py @@ -0,0 +1,64 @@ +# pylint:disable-msg=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>''') + +assert boo <= 10, "Note is %.2f. Either you cheated, or pylint's \ +broken!" % boo diff --git a/test/input/func_genexpr_var_scope_py24.py b/test/input/func_genexpr_var_scope_py24.py new file mode 100644 index 0000000..2b6faf3 --- /dev/null +++ b/test/input/func_genexpr_var_scope_py24.py @@ -0,0 +1,6 @@ +"""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 new file mode 100644 index 0000000..f7e6c65 --- /dev/null +++ b/test/input/func_globals.py @@ -0,0 +1,40 @@ +""" +'W0601': ('global variable %s undefined at the module level', + 'Used when a variable is defined through the "global" statement \ + but the variable is not defined in the module scope.'), +'W0602': ('Using global for %s but no assigment is done', + 'Used when a variable is defined through the "global" statement \ + but no assigment to this variable is done.'), +'W0603': ('Using the global statement', # W0121 + 'Used when you use the "global" statement to update a global \ + variable. PyLint just try to discourage this \ + usage. That doesn\'t mean you can not use it !'), +'W0604': ('Using the global statement at the module level', # W0103 + 'Used when you use the "global" statement at the module level \ + since it has no effect'), +""" + +__revision__ = '' + +CONSTANT = 1 + +def fix_contant(value): + """all this is ok, but try not using global ;)""" + global CONSTANT + print CONSTANT + CONSTANT = value +global CSTE # useless +print CSTE # ko + +def other(): + """global behaviour test""" + global HOP + print HOP # ko + +other() + + +def define_constant(): + """ok but somevar is not defined at the module scope""" + global somevar + somevar = 2 diff --git a/test/input/func_i0010.py b/test/input/func_i0010.py new file mode 100644 index 0000000..11aafeb --- /dev/null +++ b/test/input/func_i0010.py @@ -0,0 +1,3 @@ +# pylint: errors-only +"""errors-only is not usable as an inline option""" +__revision__ = None diff --git a/test/input/func_i0011.py b/test/input/func_i0011.py new file mode 100644 index 0000000..d8202f3 --- /dev/null +++ b/test/input/func_i0011.py @@ -0,0 +1,5 @@ +# pylint:disable-msg=W0404 +"""check warning on local disabling +""" +__revision__ = 1 + diff --git a/test/input/func_i0012.py b/test/input/func_i0012.py new file mode 100644 index 0000000..bf52138 --- /dev/null +++ b/test/input/func_i0012.py @@ -0,0 +1,5 @@ +# pylint:enable-msg=W0404 +"""check warning on local enabling +""" +__revision__ = 1 + diff --git a/test/input/func_i0013.py b/test/input/func_i0013.py new file mode 100644 index 0000000..63ff37c --- /dev/null +++ b/test/input/func_i0013.py @@ -0,0 +1,8 @@ +# pylint: disable-all +"""disable-all is usable as an inline option""" + +# no warning should be issued +try: + import this +except: + pass diff --git a/test/input/func_indent.py b/test/input/func_indent.py new file mode 100644 index 0000000..44eb429 --- /dev/null +++ b/test/input/func_indent.py @@ -0,0 +1,22 @@ +"""docstring""" +__revision__ = '$Id: func_indent.py,v 1.4 2003-10-17 21:59:31 syt Exp $' + +def totoo(): + """docstring""" + print 'malindented' + +def tutuu(): + """docstring""" + print 'good indentation' + +def titii(): + """also malindented""" + +def tataa(kdict): + """blank line unindented""" + for key in ['1', '2', '3']: + key = key.lower() + + if kdict.has_key(key): + del kdict[key] + diff --git a/test/input/func_init_vars.py b/test/input/func_init_vars.py new file mode 100644 index 0000000..9d89505 --- /dev/null +++ b/test/input/func_init_vars.py @@ -0,0 +1,46 @@ +"""Checks that class variables are seen as inherited ! +""" + +__revision__ = '' + + +class MyClass: + """Inherits from nothing + """ + + def __init__(self): + self.var = {} + + def met(self): + """Checks that base_var is seen as defined outside '__init__' + """ + self.var[1] = 'one' + self.base_var = 'one' + print self.base_var, self.var + + def met2(self): + """dummy method""" + print self +class MySubClass(MyClass): + """Inherits from MyClass + """ + class_attr = 1 + + def __init__(self): + MyClass.__init__(self) + self.var2 = 2 + print self.__doc__ + print self.__dict__ + print self.__class__ + + def met2(self): + """Checks that var is seen as defined outside '__init__' + """ + self.var[1] = 'one' + self.var2 += 1 + print self.class_attr + +if __name__ == '__main__': + OBJ = MyClass() + OBJ.met() + diff --git a/test/input/func_interfaces.py b/test/input/func_interfaces.py new file mode 100644 index 0000000..2006074 --- /dev/null +++ b/test/input/func_interfaces.py @@ -0,0 +1,99 @@ +# pylint:disable-msg=R0201 +"""docstring""" +__revision__ = '' + +class Interface: + """base class for interfaces""" + +class IMachin(Interface): + """docstring""" + def truc(self): + """docstring""" + + def troc(self, argument): + """docstring""" + +class Correct1: + """docstring""" + __implements__ = IMachin + + def __init__(self): + pass + + def truc(self): + """docstring""" + pass + + def troc(self, argument): + """docstring""" + pass + +class Correct2: + """docstring""" + __implements__ = (IMachin,) + + def __init__(self): + pass + + def truc(self): + """docstring""" + pass + + def troc(self, argument): + """docstring""" + print argument + +class MissingMethod: + """docstring""" + __implements__ = IMachin, + + def __init__(self): + pass + + def troc(self, argument): + """docstring""" + print argument + + def other(self): + """docstring""" + +class BadArgument: + """docstring""" + __implements__ = (IMachin,) + + def __init__(self): + pass + + def truc(self): + """docstring""" + pass + + def troc(self): + """docstring""" + pass + +class InterfaceCantBeFound: + """docstring""" + __implements__ = undefined + + def __init__(self): + """only to make pylint happier""" + + def please(self): + """public method 1/2""" + + def besilent(self): + """public method 2/2""" + +class InterfaceCanNowBeFound: + """docstring""" + __implements__ = BadArgument.__implements__ + Correct2.__implements__ + + def __init__(self): + """only to make pylint happier""" + + def please(self): + """public method 1/2""" + + def besilent(self): + """public method 2/2""" diff --git a/test/input/func_method_could_be_function.py b/test/input/func_method_could_be_function.py new file mode 100644 index 0000000..5988b68 --- /dev/null +++ b/test/input/func_method_could_be_function.py @@ -0,0 +1,52 @@ +# pylint: disable-msg=R0903,R0922,W0232 +"""test detection of method which could be a function""" + +__revision__ = None + +class Toto(object): + """bla bal abl""" + + def __init__(self): + self.aaa = 2 + + def regular_method(self): + """this method is a real method since it access to self""" + self.function_method() + + def function_method(self): + """this method isn' a real method since it doesn't need self""" + print 'hello' + + +class Base: + """an abstract class""" + + def __init__(self): + self.aaa = 2 + + def check(self, arg): + """an abstract method, could not be a function""" + raise NotImplementedError + + +class Sub(Base): + """a concret class""" + + def check(self, arg): + """a concret method, could not be a function since it need + polymorphism benefits + """ + return arg == 0 + +class Super: + """same as before without abstract""" + x = 1 + def method(self): + """regular""" + print self.x + +class Sub1(Super): + """override method with need for self""" + def method(self): + """no i can not be a function""" + print 42 diff --git a/test/input/func_method_missing_self.py b/test/input/func_method_missing_self.py new file mode 100644 index 0000000..5bdbc69 --- /dev/null +++ b/test/input/func_method_missing_self.py @@ -0,0 +1,25 @@ +"""Checks that missing self in method defs don't crash Pylint ! +""" + +__revision__ = '' + + +class MyClass: + """SimpleClass + """ + + def __init__(self): + self.var = "var" + + def met(): + """Checks that missing self dont crash Pylint ! + """ + + def correct(self): + """yo""" + self.var = "correct" + +if __name__ == '__main__': + OBJ = MyClass() + OBJ.met() + diff --git a/test/input/func_method_without_self_but_self_assignment.py b/test/input/func_method_without_self_but_self_assignment.py new file mode 100644 index 0000000..e39266c --- /dev/null +++ b/test/input/func_method_without_self_but_self_assignment.py @@ -0,0 +1,15 @@ +# pylint: disable-msg=R0903 +"""regression test: setup() leads to "unable to load module..." +""" + +__revision__ = 1 + +class Example: + """bla""" + + def __init__(self): + pass + + def setup(): + "setup without self" + self.foo = 1 diff --git a/test/input/func_nameerror_on_string_substitution.py b/test/input/func_nameerror_on_string_substitution.py new file mode 100644 index 0000000..be7b5c8 --- /dev/null +++ b/test/input/func_nameerror_on_string_substitution.py @@ -0,0 +1,8 @@ +"""pylint doesn't see the NameError in this module""" + +__revision__ = None + +MSG = "hello %s" % MSG + +MSG2 = ("hello %s" % + MSG2) diff --git a/test/input/func_names_imported_from_module.py b/test/input/func_names_imported_from_module.py new file mode 100644 index 0000000..caf7387 --- /dev/null +++ b/test/input/func_names_imported_from_module.py @@ -0,0 +1,31 @@ +#pylint: disable-msg=W0401,W0611 +"""check unexistant names imported are reported""" + +__revision__ = None + +import logilab.common.tutu +from logilab.common import toto +toto.yo() + +from logilab.common import modutils +modutils.nonexistant_function() +modutils.another.nonexistant.function() +print logilab.common.modutils.yo + +import sys +print >> sys.stdout, 'hello world' +print >> sys.stdoout, 'bye bye world' + + +import re +re.finditer('*', 'yo') + +from rie import * +from re import findiiter, compiile + +import os +os.environ.has_key('SOMEVAR') + +import exceptions +print exceptions.__dict__ +print exceptions.__dict__.get('Exception') diff --git a/test/input/func_newstyle___slots__.py b/test/input/func_newstyle___slots__.py new file mode 100644 index 0000000..78909c0 --- /dev/null +++ b/test/input/func_newstyle___slots__.py @@ -0,0 +1,17 @@ +# pylint: disable-msg=R0903 +"""test __slots__ on old style class""" + +__revision__ = 1 + +class OkOk(object): + """correct usage""" + __slots__ = ('a', 'b') + +class HaNonNonNon: + """bad usage""" + __slots__ = ('a', 'b') + + def __init__(self): + pass + +__slots__ = 'hop' # pfff diff --git a/test/input/func_newstyle_exceptions.py b/test/input/func_newstyle_exceptions.py new file mode 100644 index 0000000..273624f --- /dev/null +++ b/test/input/func_newstyle_exceptions.py @@ -0,0 +1,35 @@ +# pylint: disable-msg=C0103 +"""test pb with exceptions and old/new style classes""" + +__revision__ = 1 + +class OkException(Exception): + """bien bien bien""" + +class BofException: + """mouais""" + +class NewException(object): + """non 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 + + diff --git a/test/input/func_newstyle_property.py b/test/input/func_newstyle_property.py new file mode 100644 index 0000000..cdce8fa --- /dev/null +++ b/test/input/func_newstyle_property.py @@ -0,0 +1,19 @@ +# pylint: disable-msg=R0903 +"""test property on old style class""" + +__revision__ = 1 + +def getter(self): + """interesting""" + return self + +class OkOk(object): + """correct usage""" + method = property(getter, doc='hop') + +class HaNonNonNon: + """bad usage""" + method = property(getter, doc='hop') + + def __init__(self): + pass diff --git a/test/input/func_newstyle_super.py b/test/input/func_newstyle_super.py new file mode 100644 index 0000000..e84c908 --- /dev/null +++ b/test/input/func_newstyle_super.py @@ -0,0 +1,22 @@ +# pylint: disable-msg=R0903 +"""check use of super""" +__revision__ = None + +class Aaaa: + """old style""" + def hop(self): + """hop""" + super(Aaaa, self).hop() + + def __init__(self): + super(Aaaa, self).__init__() + +class NewAaaa(object): + """old style""" + def hop(self): + """hop""" + super(NewAaaa, self).hop() + + def __init__(self): + super(object, self).__init__() + diff --git a/test/input/func_noerror___future___import.py b/test/input/func_noerror___future___import.py new file mode 100644 index 0000000..5c77516 --- /dev/null +++ b/test/input/func_noerror___future___import.py @@ -0,0 +1,5 @@ +"""a docstring""" + +from __future__ import generators + +__revision__ = 1 diff --git a/test/input/func_noerror___init___return_from_inner_function.py b/test/input/func_noerror___init___return_from_inner_function.py new file mode 100644 index 0000000..30b2671 --- /dev/null +++ b/test/input/func_noerror___init___return_from_inner_function.py @@ -0,0 +1,13 @@ +# pylint: disable-msg=R0903 +"""#10075""" + +__revision__ = 1 + +class Aaa: + """docstring""" + def __init__(self): + def inner_function(arg): + """inner docstring""" + return arg + 4 + self.func = inner_function + diff --git a/test/input/func_noerror_access_attr_before_def_false_positive.py b/test/input/func_noerror_access_attr_before_def_false_positive.py new file mode 100644 index 0000000..048d938 --- /dev/null +++ b/test/input/func_noerror_access_attr_before_def_false_positive.py @@ -0,0 +1,100 @@ +#pylint: disable-msg=C0103,R0904,R0903,W0201 +""" +This module demonstrates a possible problem of pyLint with calling __init__ s +from inherited classes. +Initializations done there are not considered, which results in Error E0203 for +self.cookedq. +""" + +__revision__ = 'yo' + +import telnetlib + +class SeeTelnet(telnetlib.Telnet): + """ + Extension of telnetlib. + """ + + def __init__(self, host=None, port=0): + """ + Constructor. + When called without arguments, create an unconnected instance. + With a hostname argument, it connects the instance; a port + number is optional. + Parameter: + - host: IP address of the host + - port: Port number + """ + telnetlib.Telnet.__init__(self, host, port) + + def readUntilArray(self, matches, _=None): + """ + Read until a given string is encountered or until timeout. + ... + """ + self.process_rawq() + maxLength = 0 + index = -1 + for match in matches: + index += 1 + if len(match) > maxLength: + maxLength = len(match) + +class Base(object): + """bla bla""" + dougloup_papa = None + + def __init__(self): + self._var = False + +class Derived(Base): + """derived blabla""" + dougloup_moi = None + def Work(self): + """do something""" + # E0203 - Access to member '_var' before its definition + if self._var: + print "True" + else: + print "False" + self._var = True + + # E0203 - Access to member 'dougloup_papa' before its definition + if self.dougloup_papa: + print 'dougloup !' + self.dougloup_papa = True + # E0203 - Access to member 'dougloup_moi' before its definition + if self.dougloup_moi: + print 'dougloup !' + self.dougloup_moi = True + + +class QoSALConnection(object): + """blabla""" + + _the_instance = None + + def __new__(cls): + if cls._the_instance is None: + cls._the_instance = object.__new__(cls) + return cls._the_instance + + def __init__(self): + pass + +class DefinedOutsideInit: + """use_attr is seen as the method defining attr because its in + first position + """ + def __init__(self): + self.reset() + + def use_attr(self): + """use and set members""" + if self.attr: + print 'hop' + self.attr = 10 + + def reset(self): + """reset members""" + self.attr = 4 diff --git a/test/input/func_noerror_base_init_vars.py b/test/input/func_noerror_base_init_vars.py new file mode 100644 index 0000000..cc9882f --- /dev/null +++ b/test/input/func_noerror_base_init_vars.py @@ -0,0 +1,36 @@ +# pylint:disable-msg=R0201 +"""Checks that class variables are seen as inherited ! +""" +__revision__ = '' + +class BaseClass: + """A simple base class + """ + + def __init__(self): + self.base_var = {} + + def met(self): + """yo""" + def meeting(self, with_): + """ye""" + return with_ +class MyClass(BaseClass): + """Inherits from BaseClass + """ + + def __init__(self): + BaseClass.__init__(self) + self.var = {} + + def met(self): + """Checks that base_var is not seen as defined outsite '__init__' + """ + self.var[1] = 'one' + self.base_var[1] = 'one' + print self.base_var, self.var + +if __name__ == '__main__': + OBJ = MyClass() + OBJ.met() + diff --git a/test/input/func_noerror_builtin_module_test.py b/test/input/func_noerror_builtin_module_test.py new file mode 100644 index 0000000..4d6ba0a --- /dev/null +++ b/test/input/func_noerror_builtin_module_test.py @@ -0,0 +1,11 @@ +"""test import from a builtin module""" + +__revision__ = None + +from math import log10 + +def log10_2(): + """bla bla bla""" + return log10(2) + + diff --git a/test/input/func_noerror_classes_meth_could_be_a_function.py b/test/input/func_noerror_classes_meth_could_be_a_function.py new file mode 100644 index 0000000..d97b6cb --- /dev/null +++ b/test/input/func_noerror_classes_meth_could_be_a_function.py @@ -0,0 +1,34 @@ +# pylint: disable-msg=C0111,R0903,W0232 +""" +#2479 + +R0201 (formely W0212), Method could be a function shouldn't be emitted in case +like factory method pattern +""" +__revision__ = 1 + +class XAsub: + pass +class XBsub(XAsub): + pass +class XCsub(XAsub): + pass + +class Aimpl: + # disable "method could be a function" on classes which are not overriding + # the factory method because in that case the usage of polymorphism is not + # detected + # pylint: disable-msg=R0201 + def makex(self): + return XAsub() + +class Bimpl(Aimpl): + + def makex(self): + return XBsub() + +class Cimpl(Aimpl): + + def makex(self): + return XCsub() + diff --git a/test/input/func_noerror_classes_meth_signature.py b/test/input/func_noerror_classes_meth_signature.py new file mode 100644 index 0000000..dea3e6c --- /dev/null +++ b/test/input/func_noerror_classes_meth_signature.py @@ -0,0 +1,12 @@ +# pylint: disable-msg=C0111,R0922,R0903 +"""#2485 +W0222 "Signature differs from overriden method" false positive +""" +__revision__ = 1 +class Super(object): + def method(self, param): + raise NotImplementedError + +class Sub(Super): + def method(self, param = 'abc'): + pass diff --git a/test/input/func_noerror_classes_protected_member_access.py b/test/input/func_noerror_classes_protected_member_access.py new file mode 100644 index 0000000..d7667b8 --- /dev/null +++ b/test/input/func_noerror_classes_protected_member_access.py @@ -0,0 +1,24 @@ +""" +#3123: W0212 false positive on static method +""" +__revision__ = 1 + +class A3123: + """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_defined_and_used_on_same_line.py b/test/input/func_noerror_defined_and_used_on_same_line.py new file mode 100644 index 0000000..17e4557 --- /dev/null +++ b/test/input/func_noerror_defined_and_used_on_same_line.py @@ -0,0 +1,23 @@ +#pylint: disable-msg=C0111,C0321 +"""pylint complains about 'index' being used before definition""" + +__revision__ = None + +print [index + for index in range(10)] + + +FILTER_FUNC = lambda x: not x + +def func(xxx): return xxx + +def func2(xxx): return xxx + func2(1) + +import sys; print sys.exc_info( ) + +for i in range(10): print i + +j = 4; LAMB = lambda x: x+j + +FUNC4 = lambda a, b : a != b +FUNC3 = lambda (a, b) : a != b diff --git a/test/input/func_noerror_defined_and_used_on_same_line_py24.py b/test/input/func_noerror_defined_and_used_on_same_line_py24.py new file mode 100644 index 0000000..8032a8f --- /dev/null +++ b/test/input/func_noerror_defined_and_used_on_same_line_py24.py @@ -0,0 +1,7 @@ +#pylint: disable-msg=C0111,C0321 +"""pylint complains about 'index' being used before definition""" + +__revision__ = None + +print (index + for index in range(10)) diff --git a/test/input/func_noerror_e1101_13784.py b/test/input/func_noerror_e1101_13784.py new file mode 100644 index 0000000..b247b44 --- /dev/null +++ b/test/input/func_noerror_e1101_13784.py @@ -0,0 +1,15 @@ +"""cf #13784 +""" + +__revision__ = None + +def no_conjugate_member(magic_flag): + """should not raise E1101 on something.conjugate""" + if magic_flag: + something = 1.0 + else: + something = 1.0j + if isinstance(something, float): + return something + return something.conjugate() + diff --git a/test/input/func_noerror_e1101_but_getattr.py b/test/input/func_noerror_e1101_but_getattr.py new file mode 100644 index 0000000..143ddc0 --- /dev/null +++ b/test/input/func_noerror_e1101_but_getattr.py @@ -0,0 +1,23 @@ +"""don't want E1101 if __getattr__ is defined""" + +__revision__ = None + +class MyString: + """proxied string""" + + def __init__(self, string): + self.string = string + + def __getattr__(self, attr): + return getattr(self.string, attr) + + def lower(self): + """string.lower""" + return self.string.lower() + + def upper(self): + """string.upper""" + return self.string.upper() + +MYSTRING = MyString("abc") +print MYSTRING.title() diff --git a/test/input/func_noerror_encoding.py b/test/input/func_noerror_encoding.py new file mode 100644 index 0000000..2e945a5 --- /dev/null +++ b/test/input/func_noerror_encoding.py @@ -0,0 +1,6 @@ +# -*- coding: ISO-8859-1 -*- +""" check correct encoding declaration +""" + +__revision__ = 'éééé' + diff --git a/test/input/func_noerror_except_pass.py b/test/input/func_noerror_except_pass.py new file mode 100644 index 0000000..d032f2a --- /dev/null +++ b/test/input/func_noerror_except_pass.py @@ -0,0 +1,11 @@ +""" +#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-msg=W0107 + print A diff --git a/test/input/func_noerror_exception.py b/test/input/func_noerror_exception.py new file mode 100644 index 0000000..1c3d8b5 --- /dev/null +++ b/test/input/func_noerror_exception.py @@ -0,0 +1,7 @@ +""" module doc """ +__revision__ = '' + +class MyException(Exception): + """a custom exception with its *own* __init__ !!""" + def __init__(self, msg): + Exception.__init__(self, msg) diff --git a/test/input/func_noerror_external_classmethod_crash.py b/test/input/func_noerror_external_classmethod_crash.py new file mode 100644 index 0000000..7674388 --- /dev/null +++ b/test/input/func_noerror_external_classmethod_crash.py @@ -0,0 +1,21 @@ +# pylint: disable-msg=W0232,R0903,W0613 +"""tagging a function as a class method cause a crash when checking for +signature overriding +""" + +def fetch_config(mainattr=None): + """return a class method""" + + def fetch_order(cls, attr, var): + """a class method""" + if attr == mainattr: + return var + return None + fetch_order = classmethod(fetch_order) + return fetch_order + +class Aaa: + """hop""" + fetch_order = fetch_config('A') + +__revision__ = None diff --git a/test/input/func_noerror_factory_method.py b/test/input/func_noerror_factory_method.py new file mode 100644 index 0000000..026ec0f --- /dev/null +++ b/test/input/func_noerror_factory_method.py @@ -0,0 +1,23 @@ +# pylint: disable-msg=R0903 +"""use new astng 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_indirect_interface.py b/test/input/func_noerror_indirect_interface.py new file mode 100644 index 0000000..2b240f9 --- /dev/null +++ b/test/input/func_noerror_indirect_interface.py @@ -0,0 +1,16 @@ +"""shows a bug where pylint can't find interfaces when they are +used indirectly. See input/indirect[123].py for details on the +setup""" + +__revision__ = None + +from input.indirect2 import AbstractToto + +class ConcreteToto(AbstractToto): + """abstract to implements an interface requiring machin to be defined""" + def __init__(self): + self.duh = 2 + + def machin(self): + """for ifacd""" + return self.helper()*2 diff --git a/test/input/func_noerror_inner_classes.py b/test/input/func_noerror_inner_classes.py new file mode 100644 index 0000000..7d70d19 --- /dev/null +++ b/test/input/func_noerror_inner_classes.py @@ -0,0 +1,33 @@ +# pylint: disable-msg=R0903 +"""Backend Base Classes for the schwelm user DB""" + +__revision__ = "alpha" + +class Aaa(object): + """docstring""" + def __init__(self): + self.__setattr__('a','b') + + + def one_public(self): + """docstring""" + pass + + def another_public(self): + """docstring""" + pass + +class Bbb(Aaa): + """docstring""" + pass + +class Ccc(Aaa): + """docstring""" + + class Ddd(Aaa): + """docstring""" + pass + + class Eee(Ddd): + """docstring""" + pass diff --git a/test/input/func_noerror_mcs_attr_access.py b/test/input/func_noerror_mcs_attr_access.py new file mode 100644 index 0000000..c42c061 --- /dev/null +++ b/test/input/func_noerror_mcs_attr_access.py @@ -0,0 +1,20 @@ +# pylint: disable-msg=R0903 +"""test attribute access on metaclass""" + + +__revision__ = 'yo' + +class Meta(type): + """the meta class""" + def __init__(mcs, name, bases, dictionary): + super(Meta, mcs).__init__(name, bases, dictionary) + print mcs, mcs._meta_args + delattr(mcs, '_meta_args') + +class Test(object): + """metaclassed class""" + __metaclass__ = Meta + _meta_args = ('foo', 'bar') + + def __init__(self): + print '__init__', self diff --git a/test/input/func_noerror_nested_classes.py b/test/input/func_noerror_nested_classes.py new file mode 100644 index 0000000..96cd366 --- /dev/null +++ b/test/input/func_noerror_nested_classes.py @@ -0,0 +1,18 @@ +# pylint: disable-msg=R0903 +"""crash test""" + +__revision__ = 1 + +class Temelekefe: + """gloubliboulga""" + + def __init__(self): + """nested class with function raise error""" + class Toto: + """toto nested class""" + def __init__(self): + self.attr = 2 + def toto_method(self): + """toto nested class method""" + print self + print 'error ?', self, Toto diff --git a/test/input/func_noerror_new_style_class.py b/test/input/func_noerror_new_style_class.py new file mode 100644 index 0000000..8c9ed40 --- /dev/null +++ b/test/input/func_noerror_new_style_class.py @@ -0,0 +1,45 @@ +"""check builtin data descriptors such as mode and name attributes +on a file are correctly handler + +bug notified by Pierre Rouleau on 2005-04-24 +""" + +__revision__ = None + +class File(file): + """ Testing new-style class inheritance from file""" + + # + def __init__(self, name, mode="r", buffering=-1, verbose=False): + """Constructor""" + + self.was_modified = False + self.verbose = verbose + super(File, self).__init__(name, mode, buffering) + if self.verbose: + print "File %s is opened. The mode is: %s" % (self.name, +self.mode) + + # + def write(self, a_string): + """ Write a string to the file.""" + + super(File, self).write(a_string) + self.was_modified = True + + # + def writelines(self, sequence): + """ Write a sequence of strings to the file. """ + + super(File, self).writelines(sequence) + self.was_modified = True + + # + def close(self) : + """Close the file.""" + + if self.verbose: + print "Closing file %s" % self.name + + super(File, self).close() + self.was_modified = False diff --git a/test/input/func_noerror_nonregr.py b/test/input/func_noerror_nonregr.py new file mode 100644 index 0000000..249e2f0 --- /dev/null +++ b/test/input/func_noerror_nonregr.py @@ -0,0 +1,13 @@ +# pylint: disable-msg=W0104 +"""snippets of codes which have at some point made pylint crash""" + +__revision__ = 1 + +def function1(cbarg = lambda: None): + """ + File "/usr/lib/python2.4/site-packages/logilab/astng/scoped_nodes.py", line +391, in mularg_class + i = self.argnames.index(argname) +ValueError: list.index(x): x not in list + """ + cbarg().x diff --git a/test/input/func_noerror_object_as_class_attribute.py b/test/input/func_noerror_object_as_class_attribute.py new file mode 100644 index 0000000..6b442ea --- /dev/null +++ b/test/input/func_noerror_object_as_class_attribute.py @@ -0,0 +1,19 @@ +# pylint: disable-msg=R0903 +"""Test case for the problem described below : + - A class extends 'object' + - This class defines its own __init__() + * pylint will therefore check that baseclasses' init() + are called + - If this class defines an 'object' attribute, then pylint + will use this new definition when trying to retrieve + object.__init__() +""" + +__revision__ = None + +class Statement(object): + """ ... """ + def __init__(self): + pass + object = None + diff --git a/test/input/func_noerror_overloaded_operator.py b/test/input/func_noerror_overloaded_operator.py new file mode 100644 index 0000000..eb79141 --- /dev/null +++ b/test/input/func_noerror_overloaded_operator.py @@ -0,0 +1,21 @@ +# pylint: disable-msg=C0111,R0903 +"""#3291""" +__revision__ = 1 + +class Myarray: + 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 new file mode 100644 index 0000000..8c79ff5 --- /dev/null +++ b/test/input/func_noerror_socket_member.py @@ -0,0 +1,25 @@ +"""Testing Pylint with the socket module + +Pylint Problem +============== + +Version used: + + - Pylint 0.10.0 + - Logilab common 0.15.0 + - Logilab astng 0.15.1 + +False E1101 positive, line 23: + + Instance of '_socketobject' has no 'connect' member + +""" +__revision__ = None + +import socket + +if __name__ == "__main__": + + SCKT = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + SCKT.connect(('127.0.0.1', 80)) + SCKT.close() diff --git a/test/input/func_noerror_static_method.py b/test/input/func_noerror_static_method.py new file mode 100644 index 0000000..6184961 --- /dev/null +++ b/test/input/func_noerror_static_method.py @@ -0,0 +1,29 @@ +"""Checks if static / class methods works fine in Pylint +""" + +__revision__ = '' + +class MyClass: + """doc + """ + def __init__(self): + pass + + def static_met(var1, var2): + """This is a static method + """ + print var1, var2 + + def class_met(cls, var1): + """This is a class method + """ + print cls, var1 + + static_met = staticmethod(static_met) + class_met = classmethod(class_met) + +if __name__ == '__main__': + MyClass.static_met("var1","var2") + MyClass.class_met("var1") + + diff --git a/test/input/func_noerror_staticmethod_as_decorator.py b/test/input/func_noerror_staticmethod_as_decorator.py new file mode 100644 index 0000000..e5a44f2 --- /dev/null +++ b/test/input/func_noerror_staticmethod_as_decorator.py @@ -0,0 +1,35 @@ +# pylint: disable-msg=R0903 +"""test staticmethod and classmethod as decorator""" + +__revision__ = None + +class StaticMethod1(object): + """staticmethod test""" + def __init__(self): + pass + + @staticmethod + def do_work(): + "Working..." + + @staticmethod + def do_work_with_arg(job): + "Working on something" + print "Working on %s..." % job + + +class ClassMethod2(object): + """classmethod test""" + def __init__(self): + pass + + @classmethod + def do_work(cls): + "Working..." + + @classmethod + def do_work_with_arg(cls, job): + "Working on something" + print "Working on %s..." % job + + diff --git a/test/input/func_noerror_w0232.py b/test/input/func_noerror_w0232.py new file mode 100644 index 0000000..f4c4232 --- /dev/null +++ b/test/input/func_noerror_w0232.py @@ -0,0 +1,10 @@ +# pylint: disable-msg=R0903,R0923 +"""check interface and exception without __init__ doesn't print warnings +""" +__revision__ = '' + +class Interface: + """interface without docstring""" + +class MyError(Exception): + """exception without docstring""" diff --git a/test/input/func_noerror_yield_return_mix.py b/test/input/func_noerror_yield_return_mix.py new file mode 100644 index 0000000..bd78633 --- /dev/null +++ b/test/input/func_noerror_yield_return_mix.py @@ -0,0 +1,7 @@ +""" module doc """ +__revision__ = None + +def somegen(): + """this kind of mix is OK""" + yield 1 + return diff --git a/test/input/func_nonascii_noencoding.py b/test/input/func_nonascii_noencoding.py new file mode 100644 index 0000000..1ba3578 --- /dev/null +++ b/test/input/func_nonascii_noencoding.py @@ -0,0 +1,5 @@ +"""test file with non ascii characters and no encoding declaration""" + +__revision__ = '' + +YOP = 'héhéhé' diff --git a/test/input/func_r0901.py b/test/input/func_r0901.py new file mode 100644 index 0000000..d30d270 --- /dev/null +++ b/test/input/func_r0901.py @@ -0,0 +1,27 @@ +# pylint: disable-msg=W0232, R0903 +"""test max parents""" +__revision__ = None + +class Aaaa: + """yo""" +class Bbbb: + """yo""" +class Cccc: + """yo""" +class Dddd: + """yo""" +class Eeee: + """yo""" +class Ffff: + """yo""" +class Gggg: + """yo""" +class Hhhh: + """yo""" + +class Iiii(Aaaa, Bbbb, Cccc, Dddd, Eeee, Ffff, Gggg, Hhhh): + """yo""" + +class Jjjj(Iiii): + """yo""" + diff --git a/test/input/func_r0902.py b/test/input/func_r0902.py new file mode 100644 index 0000000..59d4100 --- /dev/null +++ b/test/input/func_r0902.py @@ -0,0 +1,28 @@ +# pylint: disable-msg=R0903 +"""test max instance attributes""" +__revision__ = None + +class Aaaa: + """yo""" + def __init__(self): + self.aaaa = 1 + self.bbbb = 2 + self.cccc = 3 + self.dddd = 4 + self.eeee = 5 + self.ffff = 6 + self.gggg = 7 + self.hhhh = 8 + self.iiii = 9 + self.jjjj = 10 + self._aaaa = 1 + self._bbbb = 2 + self._cccc = 3 + self._dddd = 4 + self._eeee = 5 + self._ffff = 6 + self._gggg = 7 + self._hhhh = 8 + self._iiii = 9 + self._jjjj = 10 + self.tomuch = None diff --git a/test/input/func_r0903.py b/test/input/func_r0903.py new file mode 100644 index 0000000..4c11929 --- /dev/null +++ b/test/input/func_r0903.py @@ -0,0 +1,13 @@ +"""test min methods""" +__revision__ = None + +class Aaaa: + """yo""" + def __init__(self): + pass + def meth1(self): + """hehehe""" + print self + def _dontcount(self): + """not public""" + print self diff --git a/test/input/func_r0904.py b/test/input/func_r0904.py new file mode 100644 index 0000000..abb4d06 --- /dev/null +++ b/test/input/func_r0904.py @@ -0,0 +1,73 @@ +# pylint: disable-msg=R0201 +"""test max methods""" +__revision__ = None +class Aaaa: + """yo""" + def __init__(self): + pass + + def meth1(self): + """hehehe""" + + def meth2(self): + """hehehe""" + + def meth3(self): + """hehehe""" + + def meth4(self): + """hehehe""" + + def meth5(self): + """hehehe""" + + def meth6(self): + """hehehe""" + + def meth7(self): + """hehehe""" + + def meth8(self): + """hehehe""" + + def meth9(self): + """hehehe""" + + def meth10(self): + """hehehe""" + + def meth11(self): + """hehehe""" + + def meth12(self): + """hehehe""" + + def meth13(self): + """hehehe""" + + def meth14(self): + """hehehe""" + + def meth15(self): + """hehehe""" + + def meth16(self): + """hehehe""" + + def meth17(self): + """hehehe""" + + def meth18(self): + """hehehe""" + + def meth19(self): + """hehehe""" + + def meth20(self): + """hehehe""" + + def meth21(self): + """hehehe""" + + def _dontcount(self): + """not public""" diff --git a/test/input/func_r0921.py b/test/input/func_r0921.py new file mode 100644 index 0000000..b9f2de2 --- /dev/null +++ b/test/input/func_r0921.py @@ -0,0 +1,15 @@ +"""test max methods""" +__revision__ = None + +class Aaaa: + """yo""" + def __init__(self): + pass + + def meth1(self): + """hehehe""" + raise NotImplementedError + + def meth2(self): + """hehehe""" + return 'Yo', self diff --git a/test/input/func_r0922.py b/test/input/func_r0922.py new file mode 100644 index 0000000..da7dfcf --- /dev/null +++ b/test/input/func_r0922.py @@ -0,0 +1,21 @@ +"""test max methods""" +__revision__ = None + +class Aaaa: + """yo""" + def __init__(self): + pass + + def meth1(self): + """hehehe""" + raise NotImplementedError + + def meth2(self): + """hehehe""" + return 'Yo', self + +class Bbbb(Aaaa): + """yeah""" + def meth1(self): + """hehehe bis""" + return "yeah", self diff --git a/test/input/func_r0923.py b/test/input/func_r0923.py new file mode 100644 index 0000000..8dacf1e --- /dev/null +++ b/test/input/func_r0923.py @@ -0,0 +1,32 @@ +"""test max methods""" +__revision__ = None + +from logilab.common.interface import Interface + +class IAaaa(Interface): + """yo""" + + def meth1(self): + """hehehe""" + +class IBbbb(Interface): + """yo""" + + def meth1(self): + """hehehe""" + +class Concret: + """implements IBbbb""" + __implements__ = IBbbb + + def __init__(self): + pass + + def meth1(self): + """hehehe""" + return "et hop", self + + def meth2(self): + """hehehe""" + return "et hop", self + diff --git a/test/input/func_reqattrs.py b/test/input/func_reqattrs.py new file mode 100644 index 0000000..fb1e2b6 --- /dev/null +++ b/test/input/func_reqattrs.py @@ -0,0 +1 @@ +"""docstring""" diff --git a/test/input/func_return_outside_func.py b/test/input/func_return_outside_func.py new file mode 100644 index 0000000..440798d --- /dev/null +++ b/test/input/func_return_outside_func.py @@ -0,0 +1,3 @@ +"""This is gramatically correct, but it's still a SyntaxError""" +__revision__ = None +return diff --git a/test/input/func_return_yield_mix.py b/test/input/func_return_yield_mix.py new file mode 100644 index 0000000..93b8051 --- /dev/null +++ b/test/input/func_return_yield_mix.py @@ -0,0 +1,8 @@ +"""pylint should detect yield and return mix inside genrators""" +__revision__ = None +def somegen(): + """this is a bad generator""" + if True: + return 1 + else: + yield 2 diff --git a/test/input/func_return_yield_mix2.py b/test/input/func_return_yield_mix2.py new file mode 100644 index 0000000..ad85c62 --- /dev/null +++ b/test/input/func_return_yield_mix2.py @@ -0,0 +1,8 @@ +"""pylint should detect yield and return mix inside genrators""" +__revision__ = None +def somegen(): + """this is a bad generator""" + if True: + yield 1 + else: + return 2 diff --git a/test/input/func_scope_regrtest.py b/test/input/func_scope_regrtest.py new file mode 100644 index 0000000..98dd8be --- /dev/null +++ b/test/input/func_scope_regrtest.py @@ -0,0 +1,15 @@ +# pylint: disable-msg=R0903,W0232 +"""check for scope problems""" + +__revision__ = None + +class Well(object): + """well""" + class Data: + """base hidden class""" + class Sub(Data): + """whaou, is Data found???""" + yo = Data() + def func(self): + """check Sub is not defined here""" + return Sub(), self diff --git a/test/input/func_syntax_error.py b/test/input/func_syntax_error.py new file mode 100644 index 0000000..43fa087 --- /dev/null +++ b/test/input/func_syntax_error.py @@ -0,0 +1 @@ +def toto diff --git a/test/input/func_toolonglines.py b/test/input/func_toolonglines.py new file mode 100644 index 0000000..76a4018 --- /dev/null +++ b/test/input/func_toolonglines.py @@ -0,0 +1,4 @@ +########################################################################################## +""" that one is too long tooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo""" + +__revision__ = '' diff --git a/test/input/func_typecheck_callfunc_assigment.py b/test/input/func_typecheck_callfunc_assigment.py new file mode 100644 index 0000000..82130e3 --- /dev/null +++ b/test/input/func_typecheck_callfunc_assigment.py @@ -0,0 +1,56 @@ +# pylint: disable-msg=R0921 +"""check assigment to function call where the function doesn't return + + 'E1111': ('Assigning to function call which doesn\'t return', + 'Used when an assigment is done on a function call but the \ + infered function doesn\'t return anything.'), + 'W1111': ('Assigning to function call which only returns None', + 'Used when an assigment is done on a function call but the \ + infered function returns nothing but None.'), + +""" +from __future__ import generators +__revision__ = None + + +def func_no_return(): + """function without return""" + print 'dougloup' + +A = func_no_return() + + +def func_return_none(): + """function returning none""" + print 'dougloup' + return None + +A = func_return_none() + + +def func_return_none_and_smth(): + """function returning none and something else""" + print 'dougloup' + if __revision__: + return None + return 3 + +A = func_return_none_and_smth() + +def generator(): + """no problemo""" + yield __revision__ + +A = generator() + +class Abstract(object): + """bla bla""" + + def abstract_method(self): + """use to return something in concrete implementation""" + raise NotImplementedError + + def use_abstract(self): + """should not issue E1111""" + var = self.abstract_method() + print var diff --git a/test/input/func_typecheck_getattr.py b/test/input/func_typecheck_getattr.py new file mode 100644 index 0000000..1b628fd --- /dev/null +++ b/test/input/func_typecheck_getattr.py @@ -0,0 +1,66 @@ +# pylint: disable-msg= +"""check getattr if inference succeed""" + +__revision__ = None + +class Provider: + """provide some attributes and method""" + cattr = 4 + def __init__(self): + self.attr = 4 + def method(self, val): + """impressive method""" + return self.attr * val + def hophop(self): + """hop method""" + print 'hop hop hop', self + + +class Client: + """use provider class""" + + def __init__(self): + self._prov = Provider() + self._prov_attr = Provider.cattr + self._prov_attr2 = Provider.cattribute + 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() + uni = u'toto' + print uni.loower() + integer = 1 + print integer.whatever + +print object.__init__ +print property.__init__ +print Client().set_later.lower() diff --git a/test/input/func_typecheck_non_callable_call.py b/test/input/func_typecheck_non_callable_call.py new file mode 100644 index 0000000..75b1224 --- /dev/null +++ b/test/input/func_typecheck_non_callable_call.py @@ -0,0 +1,37 @@ +# pylint: disable-msg=R0903 +""" + 'E1102': ('%s is not callable', + 'Used when an object being called has been infered to a non \ + callable object'), +""" + +__revision__ = None + +__revision__() + +def correct(): + """callable object""" + return 1 + +__revision__ = correct() + +class Correct(object): + """callable object""" + +class MetaCorrect(object): + """callable object""" + def __call__(self): + return self + +INSTANCE = Correct() +CALLABLE_INSTANCE = MetaCorrect() +CORRECT = CALLABLE_INSTANCE() +INCORRECT = INSTANCE() +LIST = [] +INCORRECT = LIST() +DICT = {} +INCORRECT = DICT() +TUPLE = () +INCORRECT = TUPLE() +INT = 1 +INCORRECT = INT() diff --git a/test/input/func_undefined_var.py b/test/input/func_undefined_var.py new file mode 100644 index 0000000..3166260 --- /dev/null +++ b/test/input/func_undefined_var.py @@ -0,0 +1,26 @@ +"""test access to undefined variables""" + +__revision__ = '$Id:' + +DEFINED = 1 + +if DEFINED != 1: + if DEFINED in (unknown, DEFINED): + DEFINED += 1 + + +def in_method(var): + """method doc""" + var = nomoreknown + assert var + +DEFINED = {DEFINED:__revision__} +DEFINED[__revision__] = OTHER = 'move this is astng test' + +OTHER += '$' + +def bad_default(var, default=unknown2): + """function with defaut arg's value set to an unexistant name""" + print var, default + print xxxx + print xxxx #see story #1000 diff --git a/test/input/func_unknown_encoding.py b/test/input/func_unknown_encoding.py new file mode 100644 index 0000000..31deabd --- /dev/null +++ b/test/input/func_unknown_encoding.py @@ -0,0 +1,6 @@ +# -*- coding: IBO-8859-1 -*- +""" check correct unknown encoding declaration +""" + +__revision__ = 'éééé' + diff --git a/test/input/func_unreachable.py b/test/input/func_unreachable.py new file mode 100644 index 0000000..1cabce7 --- /dev/null +++ b/test/input/func_unreachable.py @@ -0,0 +1,22 @@ +"""docstring""" + +__revision__ = '' + +def func1(): + """docstring""" + return 1 + print 'unreachable' + +def func2(): + """docstring""" + while 1: + break + print 'unreachable' + +def func3(): + """docstring""" + for i in (1, 2, 3): + print i + continue + print 'unreachable' + diff --git a/test/input/func_use_for_or_listcomp_var.py b/test/input/func_use_for_or_listcomp_var.py new file mode 100644 index 0000000..b0db336 --- /dev/null +++ b/test/input/func_use_for_or_listcomp_var.py @@ -0,0 +1,21 @@ +"""test a warning is triggered when using for a lists comprehension variable""" + +__revision__ = 'yo' + +TEST_LC = [C for C in __revision__ if C.isalpha()] +print C # WARN +C = 4 +print C # this one shouldn't trigger any warning + +B = [B for B in __revision__ if B.isalpha()] +print B # nor this one + +for var1, var2 in TEST_LC: + var1 = var2 + 4 +print var1 # WARN + +for note in __revision__: + note.something() +for line in __revision__: + for note in line: + A = note.anotherthing() diff --git a/test/input/func_variables_unused_name_from_wilcard_import.py b/test/input/func_variables_unused_name_from_wilcard_import.py new file mode 100644 index 0000000..c59b731 --- /dev/null +++ b/test/input/func_variables_unused_name_from_wilcard_import.py @@ -0,0 +1,3 @@ +"""check unused import from a wildcard import""" +from input.func_w0611 import * +__revision__ = None diff --git a/test/input/func_w0101.py b/test/input/func_w0101.py new file mode 100644 index 0000000..fe543aa --- /dev/null +++ b/test/input/func_w0101.py @@ -0,0 +1,28 @@ +"""test max returns +""" + +__revision__ = '' + +def stupid_function(arg): + """reallly stupid function""" + if arg == 1: + return 1 + elif arg == 2: + return 2 + elif arg == 3: + return 3 + elif arg == 4: + return 4 + elif arg == 5: + return 5 + elif arg == 6: + return 6 + elif arg == 7: + return 7 + elif arg == 8: + return 8 + elif arg == 9: + return 9 + elif arg == 10: + return 10 + return None diff --git a/test/input/func_w0102.py b/test/input/func_w0102.py new file mode 100644 index 0000000..b096fff --- /dev/null +++ b/test/input/func_w0102.py @@ -0,0 +1,53 @@ +# pylint: disable-msg=R0201 +"""docstring""" +__revision__ = '' + +class AAAA: + """docstring""" + def __init__(self): + pass + def method1(self): + """docstring""" + + def method2(self): + """docstring""" + + def method2(self): + """docstring""" + +class AAAA: + """docstring""" + def __init__(self): + pass + def yeah(self): + """hehehe""" + def yoo(self): + """yoo""" +def func1(): + """docstring""" + +def func2(): + """docstring""" + +def func2(): + """docstring""" + __revision__ = 1 + return __revision__ + +if __revision__: + def exclusive_func(): + "docstring" +else: + def exclusive_func(): + "docstring" + +try: + def exclusive_func2(): + "docstring" +except TypeError: + def exclusive_func2(): + "docstring" +else: + def exclusive_func2(): + "this one redefine the one defined line 42" + diff --git a/test/input/func_w0103.py b/test/input/func_w0103.py new file mode 100644 index 0000000..9417cb5 --- /dev/null +++ b/test/input/func_w0103.py @@ -0,0 +1,8 @@ +"""test max branch +""" + +__revision__ = '' + +def stupid_function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9): + """reallly stupid function""" + print arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 diff --git a/test/input/func_w0104.py b/test/input/func_w0104.py new file mode 100644 index 0000000..bddf001 --- /dev/null +++ b/test/input/func_w0104.py @@ -0,0 +1,12 @@ +"""test max branch +""" + +__revision__ = '' + +def stupid_function(arg1, arg2, arg3, arg4, arg5): + """reallly stupid function""" + arg6, arg7, arg8, arg9 = arg1, arg2, arg3, arg4 + print arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 + loc1, loc2, loc3, loc4, loc5, loc6, loc7 = arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 + print loc1, loc2, loc3, loc4, loc5, loc6, loc7 diff --git a/test/input/func_w0105.py b/test/input/func_w0105.py new file mode 100644 index 0000000..430cfd4 --- /dev/null +++ b/test/input/func_w0105.py @@ -0,0 +1,62 @@ +"""test max branch +""" + +__revision__ = '' + +def stupid_function(arg): + """reallly stupid function""" + if arg == 1: + print 1 + elif arg == 2: + print 2 + elif arg == 3: + print 3 + elif arg == 4: + print 4 + elif arg == 5: + print 5 + elif arg == 6: + print 6 + elif arg == 7: + print 7 + elif arg == 8: + print 8 + elif arg == 9: + print 9 + elif arg == 10: + print 10 + elif arg < 1: + print 0 + print 100 + arg = 0 + for val in range(arg): + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val diff --git a/test/input/func_w0109.py b/test/input/func_w0109.py new file mode 100644 index 0000000..ba7a679 --- /dev/null +++ b/test/input/func_w0109.py @@ -0,0 +1,7 @@ +"""test empty docstrings +""" + +__revision__ = '' + +def function(): + """""" diff --git a/test/input/func_w0110.py b/test/input/func_w0110.py new file mode 100644 index 0000000..c9d9311 --- /dev/null +++ b/test/input/func_w0110.py @@ -0,0 +1,10 @@ +"""test too short name +""" + +__revision__ = 1 + +A = None + +def a(): + """yo""" + pass diff --git a/test/input/func_w0111.py b/test/input/func_w0111.py new file mode 100644 index 0000000..f8ad440 --- /dev/null +++ b/test/input/func_w0111.py @@ -0,0 +1,10 @@ +"""test black listed name +""" + +__revision__ = 1 + +A = None + +def baz(): + """yo""" + pass diff --git a/test/input/func_w0112.py b/test/input/func_w0112.py new file mode 100644 index 0000000..5cd01ae --- /dev/null +++ b/test/input/func_w0112.py @@ -0,0 +1,37 @@ +"""test max branch +""" + +__revision__ = '' + +def stupid_function(arg): + """reallly stupid function""" + if arg == 1: + print 1 + elif arg == 2: + print 2 + elif arg == 3: + print 3 + elif arg == 4: + print 4 + elif arg == 5: + print 5 + elif arg == 6: + print 6 + elif arg == 7: + print 7 + elif arg == 8: + print 8 + elif arg == 9: + print 9 + elif arg == 10: + print 10 + else: + if arg < 1: + print 0 + else: + print 100 + arg = 0 + if arg: + print None + else: + print arg diff --git a/test/input/func_w0122.py b/test/input/func_w0122.py new file mode 100644 index 0000000..178c252 --- /dev/null +++ b/test/input/func_w0122.py @@ -0,0 +1,13 @@ +"""test global statement""" + +__revision__ = 0 + +exec 'a = __revision__' +exec 'a = 1' in {} + +exec 'a = 1' in globals() + +def func(): + """exec in local scope""" + exec 'b = 1' + diff --git a/test/input/func_w0133.py b/test/input/func_w0133.py new file mode 100644 index 0000000..c0304ec --- /dev/null +++ b/test/input/func_w0133.py @@ -0,0 +1,54 @@ +# pylint: disable-msg=R0903,R0201 +"""test Invalid name""" + +__revision__ = 1 + +def Run(): + """method without any good name""" + class B: + """nested class should not be tested has a variable""" + def __init__(self): + pass + bBb = 1 + return A, bBb + +def run(): + """anothrer method without only good name""" + class Aaa: + """nested class should not be tested has a variable""" + def __init__(self): + pass + bbb = 1 + return Aaa(bbb) + +A = None + +def HOHOHOHO(): + """yo""" + HIHIHI = 1 + print HIHIHI + +class xyz: + """yo""" + def __init__(self): + pass + + def Youplapoum(self): + """bad method name""" + + +def nested_args(arg1, (arg21, arg22)): + """function with nested arguments""" + print arg1, arg21, arg22 + + +GOOD_CONST_NAME = '' +benpasceluila = 0 + +class Correct: + """yo""" + def __init__(self): + self.cava = 12 + self._Ca_va_Pas = None + +V = [WHAT_Ever_inListComp for WHAT_Ever_inListComp in GOOD_CONST_NAME] diff --git a/test/input/func_w0151.py b/test/input/func_w0151.py new file mode 100644 index 0000000..4c55446 --- /dev/null +++ b/test/input/func_w0151.py @@ -0,0 +1,7 @@ +"""check black listed builtins +""" + +__revision__ = apply(map, (str, (1, 2, 3))) + +YYYYY = map(str, (1, 2, 3)) + diff --git a/test/input/func_w0152.py b/test/input/func_w0152.py new file mode 100644 index 0000000..5054d90 --- /dev/null +++ b/test/input/func_w0152.py @@ -0,0 +1,14 @@ +"""check use of * or ** +""" + +from operator import add +__revision__ = reduce(*(add, (1, 2, 3))) + + +def func(arg1, arg2): + """magic function + """ + return arg2, arg1 + +MYDICT = {'arg1':2, 'arg2': 4} +func(**MYDICT) diff --git a/test/input/func_w0202.py b/test/input/func_w0202.py new file mode 100644 index 0000000..4d36d40 --- /dev/null +++ b/test/input/func_w0202.py @@ -0,0 +1,17 @@ +"""check static method with self or cls as first argument""" + +__revision__ = None + +class Abcd: + """dummy""" + + def method1(self): + """hehe""" + method1 = staticmethod(method1) + + def method2(cls): + """hehe""" + method2 = staticmethod(method2) + + def __init__(self): + pass diff --git a/test/input/func_w0205.py b/test/input/func_w0205.py new file mode 100644 index 0000000..d2e3b29 --- /dev/null +++ b/test/input/func_w0205.py @@ -0,0 +1,24 @@ +"""check different signatures""" + +__revision__ = 0 + +class Abcd: + '''dummy''' + def __init__(self): + self.aarg = False + def abcd(self, aaa=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 new file mode 100644 index 0000000..e1e81bf --- /dev/null +++ b/test/input/func_w0223.py @@ -0,0 +1,27 @@ +# pylint: disable-msg=R0903,R0922 +"""test overriding of abstract method +""" + +__revision__ = '$Id: func_w0223.py,v 1.2 2004-09-29 08:35:13 syt Exp $' + +class Abstract: + """abstract class + """ + def aaaa(self): + """should be overridden in concrete class""" + raise NotImplementedError() + + + def bbbb(self): + """should be overridden in concrete class""" + raise NotImplementedError() + + def __init__(self): + pass + +class Concret(Abstract): + """concret class""" + + def aaaa(self): + """overidden form Abstract""" + print self diff --git a/test/input/func_w0231.py b/test/input/func_w0231.py new file mode 100644 index 0000000..3f592ec --- /dev/null +++ b/test/input/func_w0231.py @@ -0,0 +1,38 @@ +# pylint: disable-msg=R0903 +"""test for __init__ not called +""" + +__revision__ = '$Id: func_w0231.py,v 1.3 2004-09-29 08:35:13 syt Exp $' + +class AAAA: + """ancestor 1""" + + def __init__(self): + print 'init', self + +class BBBB: + """ancestor 2""" + + def __init__(self): + print 'init', self + +class CCCC: + """ancestor 3""" + + +class ZZZZ(AAAA, BBBB, CCCC): + """derived class""" + + def __init__(self): + AAAA.__init__(self) + +class NewStyleA(object): + """new style class""" + def __init__(self): + super(NewStyleA, self).__init__() + print 'init', self + +class NewStyleB(NewStyleA): + """derived new style class""" + def __init__(self): + super(NewStyleB, self).__init__() diff --git a/test/input/func_w0233.py b/test/input/func_w0233.py new file mode 100644 index 0000000..a667010 --- /dev/null +++ b/test/input/func_w0233.py @@ -0,0 +1,28 @@ +# pylint: disable-msg=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: + """ancestor 1""" + + def __init__(self): + print 'init', self + BBBBMixin.__init__(self) + +class BBBBMixin: + """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) diff --git a/test/input/func_w0302.py b/test/input/func_w0302.py new file mode 100644 index 0000000..a78f479 --- /dev/null +++ b/test/input/func_w0302.py @@ -0,0 +1,1016 @@ +"""test too much line in modules +""" + +__revision__ = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ZERFZAER = 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +HEHEHE = 2 diff --git a/test/input/func_w0312.py b/test/input/func_w0312.py new file mode 100644 index 0000000..06a8110 --- /dev/null +++ b/test/input/func_w0312.py @@ -0,0 +1,11 @@ +"""test mixed tabs and spaces""" + +__revision__ = 1 + +def spaces_func(): + """yo""" + print "yo" + +def tab_func(): + """yo""" + print "yo" diff --git a/test/input/func_w0331.py b/test/input/func_w0331.py new file mode 100644 index 0000000..7e34523 --- /dev/null +++ b/test/input/func_w0331.py @@ -0,0 +1,7 @@ +"""check use of <> +""" + +__revision__ = 1 + +if __revision__ <> 2: + __revision__ = 2 diff --git a/test/input/func_w0332.py b/test/input/func_w0332.py new file mode 100644 index 0000000..ff078ef --- /dev/null +++ b/test/input/func_w0332.py @@ -0,0 +1,4 @@ +"""check use of l as long int marker +""" + +__revision__ = 1l diff --git a/test/input/func_w0401.py b/test/input/func_w0401.py new file mode 100644 index 0000000..6b7a64b --- /dev/null +++ b/test/input/func_w0401.py @@ -0,0 +1,9 @@ +"""test cyclic import +""" +__revision__ = 0 + + +from input import w0401_cycle + +if __revision__: + print w0401_cycle diff --git a/test/input/func_w0402.py b/test/input/func_w0402.py new file mode 100644 index 0000000..53752db --- /dev/null +++ b/test/input/func_w0402.py @@ -0,0 +1,9 @@ +"""test wildard import +""" +__revision__ = 0 + +from input.func_fixme import * + +def abcd(): + """use imports""" + function() diff --git a/test/input/func_w0403.py b/test/input/func_w0403.py new file mode 100644 index 0000000..72fb795 --- /dev/null +++ b/test/input/func_w0403.py @@ -0,0 +1,12 @@ +"""test deprecated module +""" + +__revision__ = 0 + + +if __revision__: + import Bastion + print Bastion + # false positive (#10061) + import stringfile + print stringfile diff --git a/test/input/func_w0404.py b/test/input/func_w0404.py new file mode 100644 index 0000000..fd38e6a --- /dev/null +++ b/test/input/func_w0404.py @@ -0,0 +1,10 @@ +"""test relative import +""" + +__revision__ = 0 + +import func_w0302 + +if __revision__: + print func_w0302 + diff --git a/test/input/func_w0405.py b/test/input/func_w0405.py new file mode 100644 index 0000000..745c615 --- /dev/null +++ b/test/input/func_w0405.py @@ -0,0 +1,31 @@ +"""check reimport +""" + +__revision__ = 0 + +import os +from os.path import join, exists + +import os +import re as _re + +_re.match('yo', '.*') + +if __revision__: + print os + from os.path import exists + print join, exists + +def func(yooo): + """reimport in different scope""" + import os as ass + ass.remove(yooo) + import re + re.compile('.*') + +if 1: + import sys + print sys.modules +else: + print 'bla' + import sys diff --git a/test/input/func_w0406.py b/test/input/func_w0406.py new file mode 100644 index 0000000..e20508f --- /dev/null +++ b/test/input/func_w0406.py @@ -0,0 +1,9 @@ +"""test module importing itself +""" +__revision__ = 0 + +import func_w0406 + +if __revision__: + print func_w0406 + diff --git a/test/input/func_w0611.py b/test/input/func_w0611.py new file mode 100644 index 0000000..2c37966 --- /dev/null +++ b/test/input/func_w0611.py @@ -0,0 +1,22 @@ +"""check unused import +""" +__revision__ = 1 +import os +import sys + +class NonRegr: + """???""" + def __init__(self): + print 'initialized' + + def sys(self): + """should not get sys from there...""" + print self, sys + + def dummy(self, truc): + """yo""" + return self, truc + + def blop(self): + """yo""" + print self, 'blip' diff --git a/test/input/func_w0612.py b/test/input/func_w0612.py new file mode 100644 index 0000000..43b551c --- /dev/null +++ b/test/input/func_w0612.py @@ -0,0 +1,8 @@ +"""test unused variable +""" + +__revision__ = 0 + +def function(): + """"yo""" + aaaa = 1 diff --git a/test/input/func_w0613.py b/test/input/func_w0613.py new file mode 100644 index 0000000..0725335 --- /dev/null +++ b/test/input/func_w0613.py @@ -0,0 +1,18 @@ +# pylint: disable-msg=R0903 +"""test unused argument +""" + +__revision__ = 1 + +def function(arg=1): + """ignore arg""" + + +class AAAA: + """dummy class""" + + def method(self, arg): + """dummy method""" + print self + def __init__(self): + pass diff --git a/test/input/func_w0622.py b/test/input/func_w0622.py new file mode 100644 index 0000000..ea9d1b2 --- /dev/null +++ b/test/input/func_w0622.py @@ -0,0 +1,11 @@ +# pylint: disable-msg=C0103 +"""test built-in redefinition +""" +__revision__ = 0 + +def function(): + """yo""" + type = 1 + print type + +map = {} diff --git a/test/input/func_w0701.py b/test/input/func_w0701.py new file mode 100644 index 0000000..9c1b727 --- /dev/null +++ b/test/input/func_w0701.py @@ -0,0 +1,12 @@ +"""test string exception +""" + +__revision__ = '' + +def function1(): + """hehehe""" + raise "String Exception" + +def function2(): + """hehehe""" + raise 'exception', 'message' diff --git a/test/input/func_w0702.py b/test/input/func_w0702.py new file mode 100644 index 0000000..38a6417 --- /dev/null +++ b/test/input/func_w0702.py @@ -0,0 +1,17 @@ +"""check empty except statement +""" + +__revision__ = 0 + +if __revision__: + try: + print __revision__ + except: + print 'error' + +try: + __revision__ += 1 +except TypeError: + print 'error' +except Exception: + print 'error' diff --git a/test/input/func_w0703.py b/test/input/func_w0703.py new file mode 100644 index 0000000..4040540 --- /dev/null +++ b/test/input/func_w0703.py @@ -0,0 +1,9 @@ +"""check empty except statement +""" + +__revision__ = 0 + +try: + __revision__ += 1 +except Exception: + print 'error' diff --git a/test/input/func_w0704.py b/test/input/func_w0704.py new file mode 100644 index 0000000..e8c6432 --- /dev/null +++ b/test/input/func_w0704.py @@ -0,0 +1,16 @@ +"""test empty except +""" + +__revision__ = 1 + +try: + __revision__ += 1 +except TypeError: + pass + +try: + __revision__ += 1 +except TypeError: + pass +else: + __revision__ = None diff --git a/test/input/func_w0705.py b/test/input/func_w0705.py new file mode 100644 index 0000000..898e2b8 --- /dev/null +++ b/test/input/func_w0705.py @@ -0,0 +1,46 @@ +"""test misordered except +""" + +__revision__ = 1 + +try: + __revision__ += 1 +except Exception: + __revision__ = None +except TypeError: + __revision__ = None + +try: + __revision__ += 1 +except LookupError: + __revision__ = None +except IndexError: + __revision__ = None + +try: + __revision__ += 1 +except (LookupError, NameError): + __revision__ = None +except (IndexError, UnboundLocalError): + __revision__ = None + +try: + __revision__ += 1 +except: + pass +except Exception: + pass + +try: + __revision__ += 1 +except TypeError: + __revision__ = None +except: + __revision__ = None + +try: + __revision__ += 1 +except Exception: + pass +except: + pass diff --git a/test/input/func_w0801.py b/test/input/func_w0801.py new file mode 100644 index 0000000..cd386ff --- /dev/null +++ b/test/input/func_w0801.py @@ -0,0 +1,11 @@ +"""test code similarities +by defaut docstring are not considered +""" +__revision__ = 'id' +A = 2 +B = 3 +C = A + B +# need more than X lines to trigger the message +C *= 2 +A -= B +# all this should be detected diff --git a/test/input/func_wrong_encoding.py b/test/input/func_wrong_encoding.py new file mode 100644 index 0000000..267fa2c --- /dev/null +++ b/test/input/func_wrong_encoding.py @@ -0,0 +1,6 @@ +# -*- coding: UTF-8 -*- +""" check correct wrong encoding declaration +""" + +__revision__ = 'éééé' + diff --git a/test/input/func_yield_outside_func.py b/test/input/func_yield_outside_func.py new file mode 100644 index 0000000..79d882e --- /dev/null +++ b/test/input/func_yield_outside_func.py @@ -0,0 +1,4 @@ +"""This is gramatically correct, but it's still a SyntaxError""" +__revision__ = None +yield 1 + diff --git a/test/input/indirect1.py b/test/input/indirect1.py new file mode 100644 index 0000000..eac6242 --- /dev/null +++ b/test/input/indirect1.py @@ -0,0 +1,4 @@ +class TotoInterface: + def machin(self): + raise NotImplementedError + diff --git a/test/input/indirect2.py b/test/input/indirect2.py new file mode 100644 index 0000000..6eefece --- /dev/null +++ b/test/input/indirect2.py @@ -0,0 +1,7 @@ +from indirect1 import TotoInterface + +class AbstractToto: + __implements__ = TotoInterface + + def helper(self): + return 'help' diff --git a/test/input/indirect3.py b/test/input/indirect3.py new file mode 100644 index 0000000..dac0853 --- /dev/null +++ b/test/input/indirect3.py @@ -0,0 +1,5 @@ +from indirect2 import AbstractToto + +class ConcreteToto(AbstractToto): + def machin(self): + return self.helper()*2 diff --git a/test/input/noext b/test/input/noext new file mode 100644 index 0000000..8aeda06 --- /dev/null +++ b/test/input/noext @@ -0,0 +1,4 @@ +#!/usr/bin/env python +"""a python file without .py extension""" + +__revision__ = None diff --git a/test/input/similar1 b/test/input/similar1 new file mode 100644 index 0000000..bc88187 --- /dev/null +++ b/test/input/similar1 @@ -0,0 +1,19 @@ +this file is used +to check the similar +command line tool + +see the similar2 file which is almost the +same file as this one. +more than 4 +identical lines should +be +detected + + +héhéhéh + + + + + +Yo ! diff --git a/test/input/similar2 b/test/input/similar2 new file mode 100644 index 0000000..56f9844 --- /dev/null +++ b/test/input/similar2 @@ -0,0 +1,19 @@ +this file is used +to check the similar +command line tool + +see the similar1 file which is almost the +same file as this one. +more than 4 +identical lines should +be +detected + + +hohohoh + + + + + +Yo ! diff --git a/test/input/w0401_cycle.py b/test/input/w0401_cycle.py new file mode 100644 index 0000000..1fbf880 --- /dev/null +++ b/test/input/w0401_cycle.py @@ -0,0 +1,9 @@ +"""w0401 dependancy +""" + +__revision__ = 0 + +import input.func_w0401 + +if __revision__: + print input diff --git a/test/input/w0801_same.py b/test/input/w0801_same.py new file mode 100644 index 0000000..cd386ff --- /dev/null +++ b/test/input/w0801_same.py @@ -0,0 +1,11 @@ +"""test code similarities +by defaut docstring are not considered +""" +__revision__ = 'id' +A = 2 +B = 3 +C = A + B +# need more than X lines to trigger the message +C *= 2 +A -= B +# all this should be detected diff --git a/test/messages/2.5_func_newstyle_exceptions.txt b/test/messages/2.5_func_newstyle_exceptions.txt new file mode 100644 index 0000000..fc3adcb --- /dev/null +++ b/test/messages/2.5_func_newstyle_exceptions.txt @@ -0,0 +1,4 @@ +W: 21:fonctionBof: Exception doesn't inherit from standard "Exception" class +W: 25:fonctionNew: Exception doesn't inherit from standard "Exception" class +W: 29:fonctionBof2: Exception doesn't inherit from standard "Exception" class +W: 33:fonctionNew2: Exception doesn't inherit from standard "Exception" class diff --git a/test/messages/builtin_module.txt b/test/messages/builtin_module.txt new file mode 100644 index 0000000..2616c0e --- /dev/null +++ b/test/messages/builtin_module.txt @@ -0,0 +1 @@ +F: 1: ignored builtin module sys diff --git a/test/messages/func___future___import_not_first_stmt.txt b/test/messages/func___future___import_not_first_stmt.txt new file mode 100644 index 0000000..ff96b55 --- /dev/null +++ b/test/messages/func___future___import_not_first_stmt.txt @@ -0,0 +1 @@ +W: 4: __future__ import is not the first non docstring statement diff --git a/test/messages/func___name___access.txt b/test/messages/func___name___access.txt new file mode 100644 index 0000000..51aeeaa --- /dev/null +++ b/test/messages/func___name___access.txt @@ -0,0 +1,3 @@ +E: 11:Aaaa.__init__: Instance of 'Aaaa' has no '__name__' member +E: 21:NewClass.__init__: Instance of 'NewClass' has no '__name__' member + diff --git a/test/messages/func_attrs_definition_order.txt b/test/messages/func_attrs_definition_order.txt new file mode 100644 index 0000000..895c315 --- /dev/null +++ b/test/messages/func_attrs_definition_order.txt @@ -0,0 +1 @@ +E: 9:Aaaa.__init__: Access to member '_var2' before its definition line 10 diff --git a/test/messages/func_backtick_deprecated.txt b/test/messages/func_backtick_deprecated.txt new file mode 100644 index 0000000..c78baa1 --- /dev/null +++ b/test/messages/func_backtick_deprecated.txt @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..9b9bc02 --- /dev/null +++ b/test/messages/func_bad_assigment_to_exception_var.txt @@ -0,0 +1,5 @@ +W: 11: Identifier e used to raise an exception is assigned to 1 line 7 +W: 15: Identifier e2 used to raise an exception is assigned to 'yo' line 8 +W: 20:func: Identifier e3 used to raise an exception is assigned to None +W: 30: Identifier e3 used to raise an exception is assigned to None + diff --git a/test/messages/func_base_stmt_without_effect.txt b/test/messages/func_base_stmt_without_effect.txt new file mode 100644 index 0000000..3fad2d7 --- /dev/null +++ b/test/messages/func_base_stmt_without_effect.txt @@ -0,0 +1,6 @@ +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 + diff --git a/test/messages/func_base_useless_pass.txt b/test/messages/func_base_useless_pass.txt new file mode 100644 index 0000000..4398e3f --- /dev/null +++ b/test/messages/func_base_useless_pass.txt @@ -0,0 +1 @@ +W: 9: Unnecessary pass statement diff --git a/test/messages/func_block_disable_msg.txt b/test/messages/func_block_disable_msg.txt new file mode 100644 index 0000000..6882b17 --- /dev/null +++ b/test/messages/func_block_disable_msg.txt @@ -0,0 +1,10 @@ +E: 26:Foo.meth3: Instance of 'Foo' has no 'blop' member +E: 36:Foo.meth4: Instance of 'Foo' has no 'blip' member +E: 46:Foo.meth5: Instance of 'Foo' has no 'blip' member +E: 61:Foo.meth6: Instance of 'Foo' has no 'blip' member +E: 72:Foo.meth7: Instance of 'Foo' has no 'blip' member +E: 75:Foo.meth7: Instance of 'Foo' has no 'blip' member +E: 77:Foo.meth7: Instance of 'Foo' has no 'blip' member +E: 83:Foo.meth8: Instance of 'Foo' has no 'blip' member +E:102:Foo.meth9: Instance of 'Foo' has no 'blip' member +W: 11:Foo.meth1: Unused argument 'arg' diff --git a/test/messages/func_class_access_protected_members.txt b/test/messages/func_class_access_protected_members.txt new file mode 100644 index 0000000..8c7bb02 --- /dev/null +++ b/test/messages/func_class_access_protected_members.txt @@ -0,0 +1,3 @@ +W: 19:MyClass.test: Access to a protected member _haha of a client class +W: 28: Access to a protected member _protected of a client class +W: 29: Access to a protected member _cls_protected of a client class diff --git a/test/messages/func_class_members.txt b/test/messages/func_class_members.txt new file mode 100644 index 0000000..ba2c639 --- /dev/null +++ b/test/messages/func_class_members.txt @@ -0,0 +1,3 @@ +E: 16:MyClass.test: Instance of 'MyClass' has no 'incorrect' member +E: 17:MyClass.test: Instance of 'MyClass' has no 'nonexistent1' member +E: 18:MyClass.test: Instance of 'MyClass' has no 'nonexistent2' member diff --git a/test/messages/func_continue_not_in_loop.txt b/test/messages/func_continue_not_in_loop.txt new file mode 100644 index 0000000..d3a3183 --- /dev/null +++ b/test/messages/func_continue_not_in_loop.txt @@ -0,0 +1,2 @@ +E: 8:run: 'continue' not properly in loop +E: 10:run: 'break' not properly in loop diff --git a/test/messages/func_dangerous_default.txt b/test/messages/func_dangerous_default.txt new file mode 100644 index 0000000..21d32e0 --- /dev/null +++ b/test/messages/func_dangerous_default.txt @@ -0,0 +1,2 @@ +W: 7:function1: Dangerous default value [] as argument +W: 11:function2: Dangerous default value HEHE ({}) as argument diff --git a/test/messages/func_docstring.txt b/test/messages/func_docstring.txt new file mode 100644 index 0000000..716de4c --- /dev/null +++ b/test/messages/func_docstring.txt @@ -0,0 +1,4 @@ +C: 1: Missing docstring +C: 5:function1: Missing docstring +C: 17:AAAA: Missing docstring +C: 33:AAAA.method1: Missing docstring diff --git a/test/messages/func_dotted_ancestor.txt b/test/messages/func_dotted_ancestor.txt new file mode 100644 index 0000000..6e2c6fa --- /dev/null +++ b/test/messages/func_dotted_ancestor.txt @@ -0,0 +1 @@ +R: 8:Aaaa: Too few public methods (0/2) diff --git a/test/messages/func_e0011.txt b/test/messages/func_e0011.txt new file mode 100644 index 0000000..55f07b1 --- /dev/null +++ b/test/messages/func_e0011.txt @@ -0,0 +1 @@ +E: 1: Unrecognized file option 'bouboule' diff --git a/test/messages/func_e0012.txt b/test/messages/func_e0012.txt new file mode 100644 index 0000000..a6d1b69 --- /dev/null +++ b/test/messages/func_e0012.txt @@ -0,0 +1 @@ +E: 1: Bad option value 'W04044' diff --git a/test/messages/func_e0101.txt b/test/messages/func_e0101.txt new file mode 100644 index 0000000..203cd32 --- /dev/null +++ b/test/messages/func_e0101.txt @@ -0,0 +1,2 @@ +E: 10:MyClass.__init__: Explicit return in __init__ +E: 28:MyClass4.__init__: __init__ method is a generator diff --git a/test/messages/func_e0203.txt b/test/messages/func_e0203.txt new file mode 100644 index 0000000..869cf22 --- /dev/null +++ b/test/messages/func_e0203.txt @@ -0,0 +1,2 @@ +C: 12:Abcd.abcd: Class method should have "cls" as first argument + diff --git a/test/messages/func_e0204.txt b/test/messages/func_e0204.txt new file mode 100644 index 0000000..8e05efe --- /dev/null +++ b/test/messages/func_e0204.txt @@ -0,0 +1,3 @@ +E: 10:Abcd.__init__: Method should have "self" as first argument +E: 14:Abcd.abdc: Method should have "self" as first argument + diff --git a/test/messages/func_e0205.txt b/test/messages/func_e0205.txt new file mode 100644 index 0000000..1adf408 --- /dev/null +++ b/test/messages/func_e0205.txt @@ -0,0 +1,2 @@ +E: 14:Cdef.abcd: An attribute inherited from Abcd hide this method + diff --git a/test/messages/func_e0206.txt b/test/messages/func_e0206.txt new file mode 100644 index 0000000..c15f841 --- /dev/null +++ b/test/messages/func_e0206.txt @@ -0,0 +1,3 @@ +E: 6:Abcd: Interface resolved to None is not a class +E: 13:Cdef: Interface resolved to None is not a class + diff --git a/test/messages/func_e0214.txt b/test/messages/func_e0214.txt new file mode 100644 index 0000000..e3fb5f5 --- /dev/null +++ b/test/messages/func_e0214.txt @@ -0,0 +1,2 @@ +C: 11:MetaClass.whatever: Metaclass method should have "mcs" as first argument + diff --git a/test/messages/func_e0601.txt b/test/messages/func_e0601.txt new file mode 100644 index 0000000..321c731 --- /dev/null +++ b/test/messages/func_e0601.txt @@ -0,0 +1 @@ +E: 8:function: Using variable 'aaaa' before assignment diff --git a/test/messages/func_empty_module.txt b/test/messages/func_empty_module.txt new file mode 100644 index 0000000..4f0d347 --- /dev/null +++ b/test/messages/func_empty_module.txt @@ -0,0 +1,2 @@ +C: 1: Missing 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 new file mode 100644 index 0000000..8ac5778 --- /dev/null +++ b/test/messages/func_exceptions_raise_type_error.txt @@ -0,0 +1,2 @@ +E: 11: Raising int while only classes, instances or string are allowed +E: 14: Raising None while only classes, instances or string are allowed
\ No newline at end of file diff --git a/test/messages/func_f0001.txt b/test/messages/func_f0001.txt new file mode 100644 index 0000000..87c315d --- /dev/null +++ b/test/messages/func_f0001.txt @@ -0,0 +1 @@ +W: 3: Unused import whatever diff --git a/test/messages/func_f0401.txt b/test/messages/func_f0401.txt new file mode 100644 index 0000000..1ae5ea4 --- /dev/null +++ b/test/messages/func_f0401.txt @@ -0,0 +1,2 @@ +F: 8:function: Unable to import 'tutu.toto' (No module named tutu) + diff --git a/test/messages/func_fixme.txt b/test/messages/func_fixme.txt new file mode 100644 index 0000000..2544ce8 --- /dev/null +++ b/test/messages/func_fixme.txt @@ -0,0 +1,2 @@ +W: 5: FIXME: beep +W: 8: XXX:bop''' diff --git a/test/messages/func_format.txt b/test/messages/func_format.txt new file mode 100644 index 0000000..c87d01d --- /dev/null +++ b/test/messages/func_format.txt @@ -0,0 +1,25 @@ +C: 6: Operator not preceded by a space +notpreceded= 1 + ^ +C: 7: Operator not followed by a space +notfollowed =1 + ^ +C: 8: Operator not followed by a space +notfollowed <=1 + ^^ +C: 19: Comma not followed by a space +aaaa,bbbb = 1,2 + ^^ +C: 24: More than one statement on a single line +C: 26: Comma not followed by a space + aaaa,bbbb = 1,2 + ^^ +C: 27: Comma not followed by a space + aaaa,bbbb = bbbb,aaaa + ^^ +C: 29: Comma not followed by a space +bbbb = (1,2,3) + ^^ +C: 51:other: Operator not preceded by a space + funky= funky+2 + ^ diff --git a/test/messages/func_genexpr_var_scope_py24.txt b/test/messages/func_genexpr_var_scope_py24.txt new file mode 100644 index 0000000..f599112 --- /dev/null +++ b/test/messages/func_genexpr_var_scope_py24.txt @@ -0,0 +1 @@ +E: 6: Undefined variable 'n' diff --git a/test/messages/func_globals.txt b/test/messages/func_globals.txt new file mode 100644 index 0000000..40963f2 --- /dev/null +++ b/test/messages/func_globals.txt @@ -0,0 +1,6 @@ +E: 27: Undefined variable 'CSTE' +E: 32:other: Undefined variable 'HOP' +W: 23:fix_contant: Using the global statement +W: 26: Using the global statement at the module level +W: 31:other: Using global for 'HOP' but no assigment is done +W: 39:define_constant: Global variable 'somevar' undefined at the module level diff --git a/test/messages/func_i0010.txt b/test/messages/func_i0010.txt new file mode 100644 index 0000000..2bc4372 --- /dev/null +++ b/test/messages/func_i0010.txt @@ -0,0 +1 @@ +I: 1: Unable to consider inline option 'errors-only' diff --git a/test/messages/func_i0011.txt b/test/messages/func_i0011.txt new file mode 100644 index 0000000..667e364 --- /dev/null +++ b/test/messages/func_i0011.txt @@ -0,0 +1,2 @@ +I: 1: Locally disabling W0404 + diff --git a/test/messages/func_i0012.txt b/test/messages/func_i0012.txt new file mode 100644 index 0000000..bf0c90f --- /dev/null +++ b/test/messages/func_i0012.txt @@ -0,0 +1,2 @@ +I: 1: Locally enabling W0404 + diff --git a/test/messages/func_i0013.txt b/test/messages/func_i0013.txt new file mode 100644 index 0000000..75d7afd --- /dev/null +++ b/test/messages/func_i0013.txt @@ -0,0 +1 @@ +I: 1: Ignoring entire file diff --git a/test/messages/func_indent.txt b/test/messages/func_indent.txt new file mode 100644 index 0000000..aa3645f --- /dev/null +++ b/test/messages/func_indent.txt @@ -0,0 +1,3 @@ +W: 5: Bad indentation. Found 1 spaces, expected 4 +W: 6: Bad indentation. Found 1 spaces, expected 4 +W: 13: Bad indentation. Found 5 spaces, expected 4 diff --git a/test/messages/func_init_vars.txt b/test/messages/func_init_vars.txt new file mode 100644 index 0000000..44ef6f3 --- /dev/null +++ b/test/messages/func_init_vars.txt @@ -0,0 +1 @@ +W: 18:MyClass.met: Attribute 'base_var' defined outside __init__ diff --git a/test/messages/func_interfaces.txt b/test/messages/func_interfaces.txt new file mode 100644 index 0000000..3b444d7 --- /dev/null +++ b/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 method diff --git a/test/messages/func_method_could_be_function.txt b/test/messages/func_method_could_be_function.txt new file mode 100644 index 0000000..1def89e --- /dev/null +++ b/test/messages/func_method_could_be_function.txt @@ -0,0 +1 @@ +R: 16:Toto.function_method: Method could be a function diff --git a/test/messages/func_method_missing_self.txt b/test/messages/func_method_missing_self.txt new file mode 100644 index 0000000..861871f --- /dev/null +++ b/test/messages/func_method_missing_self.txt @@ -0,0 +1 @@ +E: 14:MyClass.met: Method has no argument diff --git a/test/messages/func_method_without_self_but_self_assignment.txt b/test/messages/func_method_without_self_but_self_assignment.txt new file mode 100644 index 0000000..da5ee02 --- /dev/null +++ b/test/messages/func_method_without_self_but_self_assignment.txt @@ -0,0 +1,2 @@ +E: 13:Example.setup: Method has no argument +E: 15:Example.setup: Undefined variable 'self' diff --git a/test/messages/func_nameerror_on_string_substitution.txt b/test/messages/func_nameerror_on_string_substitution.txt new file mode 100644 index 0000000..aab2102 --- /dev/null +++ b/test/messages/func_nameerror_on_string_substitution.txt @@ -0,0 +1,2 @@ +E: 5: Using variable 'MSG' before assignment +E: 8: Using variable 'MSG2' before assignment diff --git a/test/messages/func_names_imported_from_module.txt b/test/messages/func_names_imported_from_module.txt new file mode 100644 index 0000000..62b0344 --- /dev/null +++ b/test/messages/func_names_imported_from_module.txt @@ -0,0 +1,8 @@ +E: 6: No name 'tutu' in module 'logilab.common' +E: 7: No name 'toto' in module 'logilab.common' +E: 11: Module 'logilab.common.modutils' has no 'nonexistant_function' member +E: 12: Module 'logilab.common.modutils' has no 'another' member +E: 13: Module 'logilab.common.modutils' has no 'yo' member +E: 17: Module 'sys' has no 'stdoout' member +E: 24: No name 'compiile' in module 're' +E: 24: No name 'findiiter' in module 're' diff --git a/test/messages/func_newstyle___slots__.txt b/test/messages/func_newstyle___slots__.txt new file mode 100644 index 0000000..be6b8d1 --- /dev/null +++ b/test/messages/func_newstyle___slots__.txt @@ -0,0 +1 @@ +E: 10:HaNonNonNon: Use __slots__ on an old style class diff --git a/test/messages/func_newstyle_exceptions.txt b/test/messages/func_newstyle_exceptions.txt new file mode 100644 index 0000000..d92e3bf --- /dev/null +++ b/test/messages/func_newstyle_exceptions.txt @@ -0,0 +1,4 @@ +E: 25:fonctionNew: Raising a new style class +E: 33:fonctionNew2: Raising a new style class +W: 21:fonctionBof: Exception doesn't inherit from standard "Exception" class +W: 29:fonctionBof2: Exception doesn't inherit from standard "Exception" class diff --git a/test/messages/func_newstyle_property.txt b/test/messages/func_newstyle_property.txt new file mode 100644 index 0000000..f0ef261 --- /dev/null +++ b/test/messages/func_newstyle_property.txt @@ -0,0 +1 @@ +W: 16:HaNonNonNon: Use of "property" on an old style class diff --git a/test/messages/func_newstyle_super.txt b/test/messages/func_newstyle_super.txt new file mode 100644 index 0000000..d0cdf78 --- /dev/null +++ b/test/messages/func_newstyle_super.txt @@ -0,0 +1,4 @@ +E: 7:Aaaa.hop: Use super on an old style class +E: 11:Aaaa.__init__: Use super on an old style class +E: 20:NewAaaa.__init__: Bad first argument 'object' given to super class + diff --git a/test/messages/func_nonascii_noencoding.txt b/test/messages/func_nonascii_noencoding.txt new file mode 100644 index 0000000..dd68a3c --- /dev/null +++ b/test/messages/func_nonascii_noencoding.txt @@ -0,0 +1 @@ +E: 1: Non ascii characters found but no encoding specified (PEP 263) diff --git a/test/messages/func_r0901.txt b/test/messages/func_r0901.txt new file mode 100644 index 0000000..d20460e --- /dev/null +++ b/test/messages/func_r0901.txt @@ -0,0 +1,2 @@ +R: 22:Iiii: Too many ancestors (8/7) +R: 25:Jjjj: Too many ancestors (9/7) diff --git a/test/messages/func_r0902.txt b/test/messages/func_r0902.txt new file mode 100644 index 0000000..5dcb669 --- /dev/null +++ b/test/messages/func_r0902.txt @@ -0,0 +1 @@ +R: 5:Aaaa: Too many instance attributes (21/7) diff --git a/test/messages/func_r0903.txt b/test/messages/func_r0903.txt new file mode 100644 index 0000000..bd4cc2a --- /dev/null +++ b/test/messages/func_r0903.txt @@ -0,0 +1 @@ +R: 4:Aaaa: Too few public methods (1/2) diff --git a/test/messages/func_r0904.txt b/test/messages/func_r0904.txt new file mode 100644 index 0000000..76baf72 --- /dev/null +++ b/test/messages/func_r0904.txt @@ -0,0 +1 @@ +R: 4:Aaaa: Too many public methods (21/20) diff --git a/test/messages/func_r0921.txt b/test/messages/func_r0921.txt new file mode 100644 index 0000000..7e9a442 --- /dev/null +++ b/test/messages/func_r0921.txt @@ -0,0 +1 @@ +R: 4:Aaaa: Abstract class not referenced diff --git a/test/messages/func_r0922.txt b/test/messages/func_r0922.txt new file mode 100644 index 0000000..70319ee --- /dev/null +++ b/test/messages/func_r0922.txt @@ -0,0 +1 @@ +R: 4:Aaaa: Abstract class is only referenced 1 times diff --git a/test/messages/func_r0923.txt b/test/messages/func_r0923.txt new file mode 100644 index 0000000..11ee61d --- /dev/null +++ b/test/messages/func_r0923.txt @@ -0,0 +1 @@ +R: 6:IAaaa: Interface not implemented diff --git a/test/messages/func_reqattrs.txt b/test/messages/func_reqattrs.txt new file mode 100644 index 0000000..0563049 --- /dev/null +++ b/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/test/messages/func_return_outside_func.txt new file mode 100644 index 0000000..704e917 --- /dev/null +++ b/test/messages/func_return_outside_func.txt @@ -0,0 +1 @@ +E: 3: return outside function diff --git a/test/messages/func_return_yield_mix.txt b/test/messages/func_return_yield_mix.txt new file mode 100644 index 0000000..6875099 --- /dev/null +++ b/test/messages/func_return_yield_mix.txt @@ -0,0 +1 @@ +E: 6:somegen: return with argument inside generator diff --git a/test/messages/func_return_yield_mix2.txt b/test/messages/func_return_yield_mix2.txt new file mode 100644 index 0000000..fea8017 --- /dev/null +++ b/test/messages/func_return_yield_mix2.txt @@ -0,0 +1 @@ +E: 8:somegen: return with argument inside generator diff --git a/test/messages/func_scope_regrtest.txt b/test/messages/func_scope_regrtest.txt new file mode 100644 index 0000000..27ae207 --- /dev/null +++ b/test/messages/func_scope_regrtest.txt @@ -0,0 +1,2 @@ +E: 12:Well.Sub: Undefined variable 'Data' +E: 15:Well.func: Undefined variable 'Sub' diff --git a/test/messages/func_syntax_error.txt b/test/messages/func_syntax_error.txt new file mode 100644 index 0000000..deee535 --- /dev/null +++ b/test/messages/func_syntax_error.txt @@ -0,0 +1,2 @@ +E: 1: invalid syntax + diff --git a/test/messages/func_toolonglines.txt b/test/messages/func_toolonglines.txt new file mode 100644 index 0000000..29b16c6 --- /dev/null +++ b/test/messages/func_toolonglines.txt @@ -0,0 +1,2 @@ +C: 1: Line too long (90/80) +C: 2: Line too long (94/80) diff --git a/test/messages/func_typecheck_callfunc_assigment.txt b/test/messages/func_typecheck_callfunc_assigment.txt new file mode 100644 index 0000000..96ad43e --- /dev/null +++ b/test/messages/func_typecheck_callfunc_assigment.txt @@ -0,0 +1,2 @@ +E: 20: Assigning to function call which doesn't return +W: 28: Assigning to function call which only returns None diff --git a/test/messages/func_typecheck_getattr.txt b/test/messages/func_typecheck_getattr.txt new file mode 100644 index 0000000..cd9f916 --- /dev/null +++ b/test/messages/func_typecheck_getattr.txt @@ -0,0 +1,10 @@ +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: 60:Client.test_bt_types: Instance of 'unicode' 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 new file mode 100644 index 0000000..0218074 --- /dev/null +++ b/test/messages/func_typecheck_non_callable_call.txt @@ -0,0 +1,6 @@ +E: 10: __revision__ is not callable +E: 29: INSTANCE is not callable +E: 31: LIST is not callable +E: 33: DICT is not callable +E: 35: TUPLE is not callable +E: 37: INT is not callable diff --git a/test/messages/func_undefined_var.txt b/test/messages/func_undefined_var.txt new file mode 100644 index 0000000..07666f7 --- /dev/null +++ b/test/messages/func_undefined_var.txt @@ -0,0 +1,5 @@ +E: 8: Undefined variable 'unknown' +E: 14:in_method: Undefined variable 'nomoreknown' +E: 22:bad_default: Undefined variable 'unknown2' +E: 25:bad_default: Undefined variable 'xxxx' +E: 26:bad_default: Undefined variable 'xxxx' diff --git a/test/messages/func_unknown_encoding.txt b/test/messages/func_unknown_encoding.txt new file mode 100644 index 0000000..648af39 --- /dev/null +++ b/test/messages/func_unknown_encoding.txt @@ -0,0 +1 @@ +E: 1: Unknown encoding specified (IBO-8859-1) diff --git a/test/messages/func_unreachable.txt b/test/messages/func_unreachable.txt new file mode 100644 index 0000000..bb25be0 --- /dev/null +++ b/test/messages/func_unreachable.txt @@ -0,0 +1,3 @@ +W: 8:func1: Unreachable code +W: 14:func2: Unreachable code +W: 21:func3: Unreachable code diff --git a/test/messages/func_use_for_or_listcomp_var.txt b/test/messages/func_use_for_or_listcomp_var.txt new file mode 100644 index 0000000..14d61ad --- /dev/null +++ b/test/messages/func_use_for_or_listcomp_var.txt @@ -0,0 +1,3 @@ +W: 6: Using possibly undefined loop variable 'C' +W: 15: Using possibly undefined loop variable 'var1' + diff --git a/test/messages/func_variables_unused_name_from_wilcard_import.txt b/test/messages/func_variables_unused_name_from_wilcard_import.txt new file mode 100644 index 0000000..4768afa --- /dev/null +++ b/test/messages/func_variables_unused_name_from_wilcard_import.txt @@ -0,0 +1,5 @@ +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_w0101.txt b/test/messages/func_w0101.txt new file mode 100644 index 0000000..c42ec2c --- /dev/null +++ b/test/messages/func_w0101.txt @@ -0,0 +1 @@ +R: 6:stupid_function: Too many return statements (11/6) diff --git a/test/messages/func_w0102.txt b/test/messages/func_w0102.txt new file mode 100644 index 0000000..40b6190 --- /dev/null +++ b/test/messages/func_w0102.txt @@ -0,0 +1,5 @@ +E: 15:AAAA.method2: method already defined line 12 +E: 18:AAAA: class already defined line 5 +E: 32:func2: function already defined line 29 +E: 51:exclusive_func2: function already defined line 45 +W: 34:func2: Redefining name '__revision__' from outer scope (line 3) diff --git a/test/messages/func_w0103.txt b/test/messages/func_w0103.txt new file mode 100644 index 0000000..0d6da42 --- /dev/null +++ b/test/messages/func_w0103.txt @@ -0,0 +1 @@ +R: 6:stupid_function: Too many arguments (9/5) diff --git a/test/messages/func_w0104.txt b/test/messages/func_w0104.txt new file mode 100644 index 0000000..71f6f62 --- /dev/null +++ b/test/messages/func_w0104.txt @@ -0,0 +1 @@ +R: 6:stupid_function: Too many local variables (16/15) diff --git a/test/messages/func_w0105.txt b/test/messages/func_w0105.txt new file mode 100644 index 0000000..d664dd4 --- /dev/null +++ b/test/messages/func_w0105.txt @@ -0,0 +1 @@ +R: 6:stupid_function: Too many statements (55/50) diff --git a/test/messages/func_w0109.txt b/test/messages/func_w0109.txt new file mode 100644 index 0000000..4685d38 --- /dev/null +++ b/test/messages/func_w0109.txt @@ -0,0 +1 @@ +C: 6:function: Empty docstring diff --git a/test/messages/func_w0110.txt b/test/messages/func_w0110.txt new file mode 100644 index 0000000..28f48aa --- /dev/null +++ b/test/messages/func_w0110.txt @@ -0,0 +1 @@ +C: 8:a: Invalid name "a" (should match [a-z_][a-z0-9_]{2,30}$) diff --git a/test/messages/func_w0111.txt b/test/messages/func_w0111.txt new file mode 100644 index 0000000..b2d794b --- /dev/null +++ b/test/messages/func_w0111.txt @@ -0,0 +1 @@ +C: 8:baz: Black listed name "baz" diff --git a/test/messages/func_w0112.txt b/test/messages/func_w0112.txt new file mode 100644 index 0000000..19b2da5 --- /dev/null +++ b/test/messages/func_w0112.txt @@ -0,0 +1 @@ +R: 6:stupid_function: Too many branches (15/12) diff --git a/test/messages/func_w0122.txt b/test/messages/func_w0122.txt new file mode 100644 index 0000000..1522cac --- /dev/null +++ b/test/messages/func_w0122.txt @@ -0,0 +1,5 @@ +W: 5: Use of the exec statement +W: 6: Use of the exec statement +W: 8: Use of the exec statement +W: 12:func: Use of the exec statement + diff --git a/test/messages/func_w0133.txt b/test/messages/func_w0133.txt new file mode 100644 index 0000000..f2929ae --- /dev/null +++ b/test/messages/func_w0133.txt @@ -0,0 +1,9 @@ +C: 8:Run.B: Invalid name "B" (should match [A-Z_][a-zA-Z0-9]+$) +C: 12:Run: Invalid name "bBb" (should match [a-z_][a-z0-9_]{2,30}$) +C: 26:HOHOHOHO: Invalid name "HOHOHOHO" (should match [a-z_][a-z0-9_]{2,30}$) +C: 28:HOHOHOHO: Invalid name "HIHIHI" (should match [a-z_][a-z0-9_]{2,30}$) +C: 31:xyz: Invalid name "xyz" (should match [A-Z_][a-zA-Z0-9]+$) +C: 36:xyz.Youplapoum: Invalid name "Youplapoum" (should match [a-z_][a-z0-9_]{2,30}$) +C: 46: Invalid name "benpasceluila" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) +C: 52:Correct.__init__: Invalid name "_Ca_va_Pas" (should match [a-z_][a-z0-9_]{2,30}$) +W: 8:Run.B: Unused variable 'B' diff --git a/test/messages/func_w0151.txt b/test/messages/func_w0151.txt new file mode 100644 index 0000000..dae33d9 --- /dev/null +++ b/test/messages/func_w0151.txt @@ -0,0 +1,2 @@ +W: 4: Used builtin function 'apply' +W: 6: Used builtin function 'map' diff --git a/test/messages/func_w0152.txt b/test/messages/func_w0152.txt new file mode 100644 index 0000000..ddf2d0b --- /dev/null +++ b/test/messages/func_w0152.txt @@ -0,0 +1,3 @@ +W: 5: Used * or ** magic +W: 14: Used * or ** magic + diff --git a/test/messages/func_w0202.txt b/test/messages/func_w0202.txt new file mode 100644 index 0000000..d10e5bc --- /dev/null +++ b/test/messages/func_w0202.txt @@ -0,0 +1,3 @@ +W: 8:Abcd.method1: Static method with 'self' as first argument +W: 12:Abcd.method2: Static method with 'cls' as first argument + diff --git a/test/messages/func_w0205.txt b/test/messages/func_w0205.txt new file mode 100644 index 0000000..4b36dab --- /dev/null +++ b/test/messages/func_w0205.txt @@ -0,0 +1,2 @@ +W: 22:Cdef.abcd: Signature differs from overridden method + diff --git a/test/messages/func_w0223.txt b/test/messages/func_w0223.txt new file mode 100644 index 0000000..fc735da --- /dev/null +++ b/test/messages/func_w0223.txt @@ -0,0 +1,2 @@ +W: 22:Concret: Method 'bbbb' is abstract in class 'Abstract' but is not overridden + diff --git a/test/messages/func_w0231.txt b/test/messages/func_w0231.txt new file mode 100644 index 0000000..84e176d --- /dev/null +++ b/test/messages/func_w0231.txt @@ -0,0 +1,2 @@ +W: 19:CCCC: Class has no __init__ method +W: 26:ZZZZ.__init__: __init__ method from base class 'BBBB' is not called diff --git a/test/messages/func_w0233.txt b/test/messages/func_w0233.txt new file mode 100644 index 0000000..edf0e45 --- /dev/null +++ b/test/messages/func_w0233.txt @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..58f65b3 --- /dev/null +++ b/test/messages/func_w0302.txt @@ -0,0 +1,2 @@ +C: 1: Too many lines in module (1016) + diff --git a/test/messages/func_w0312.txt b/test/messages/func_w0312.txt new file mode 100644 index 0000000..917e8d0 --- /dev/null +++ b/test/messages/func_w0312.txt @@ -0,0 +1,2 @@ +W: 10: Found indentation with tabs instead of spaces +W: 11: Found indentation with tabs instead of spaces diff --git a/test/messages/func_w0331.txt b/test/messages/func_w0331.txt new file mode 100644 index 0000000..8134a32 --- /dev/null +++ b/test/messages/func_w0331.txt @@ -0,0 +1 @@ +W: 6: Use of the <> operator diff --git a/test/messages/func_w0332.txt b/test/messages/func_w0332.txt new file mode 100644 index 0000000..b8984ad --- /dev/null +++ b/test/messages/func_w0332.txt @@ -0,0 +1 @@ +W: 4: Use l as long integer identifier diff --git a/test/messages/func_w0401.txt b/test/messages/func_w0401.txt new file mode 100644 index 0000000..74b14dc --- /dev/null +++ b/test/messages/func_w0401.txt @@ -0,0 +1,2 @@ +R: 1: Cyclic import (input.func_w0401 -> input.w0401_cycle) +W: 6: Redefining built-in 'input' diff --git a/test/messages/func_w0402.txt b/test/messages/func_w0402.txt new file mode 100644 index 0000000..cf06fc4 --- /dev/null +++ b/test/messages/func_w0402.txt @@ -0,0 +1,2 @@ +W: 5: Wildcard import input.func_fixme + diff --git a/test/messages/func_w0403.txt b/test/messages/func_w0403.txt new file mode 100644 index 0000000..ef511f7 --- /dev/null +++ b/test/messages/func_w0403.txt @@ -0,0 +1 @@ +W: 8: Uses of a deprecated module 'Bastion' diff --git a/test/messages/func_w0404.txt b/test/messages/func_w0404.txt new file mode 100644 index 0000000..311056d --- /dev/null +++ b/test/messages/func_w0404.txt @@ -0,0 +1 @@ +W: 6: Relative import 'func_w0302' diff --git a/test/messages/func_w0405.txt b/test/messages/func_w0405.txt new file mode 100644 index 0000000..9555dfd --- /dev/null +++ b/test/messages/func_w0405.txt @@ -0,0 +1,4 @@ +W: 9: Reimport 'os' (imported line 6) +W: 16: Reimport 'exists' (imported line 7) +W: 21:func: Reimport 'os' (imported line 6) +W: 23:func: Reimport 're' (imported line 10) diff --git a/test/messages/func_w0406.txt b/test/messages/func_w0406.txt new file mode 100644 index 0000000..a1440b9 --- /dev/null +++ b/test/messages/func_w0406.txt @@ -0,0 +1,2 @@ +W: 5: Module import itself +W: 5: Relative import 'func_w0406' diff --git a/test/messages/func_w0611.txt b/test/messages/func_w0611.txt new file mode 100644 index 0000000..10952da --- /dev/null +++ b/test/messages/func_w0611.txt @@ -0,0 +1 @@ +W: 4: Unused import os diff --git a/test/messages/func_w0612.txt b/test/messages/func_w0612.txt new file mode 100644 index 0000000..b2d9cbb --- /dev/null +++ b/test/messages/func_w0612.txt @@ -0,0 +1 @@ +W: 8:function: Unused variable 'aaaa' diff --git a/test/messages/func_w0613.txt b/test/messages/func_w0613.txt new file mode 100644 index 0000000..b658b54 --- /dev/null +++ b/test/messages/func_w0613.txt @@ -0,0 +1,2 @@ +W: 7:function: Unused argument 'arg' +W: 14:AAAA.method: Unused argument 'arg' diff --git a/test/messages/func_w0622.txt b/test/messages/func_w0622.txt new file mode 100644 index 0000000..7191347 --- /dev/null +++ b/test/messages/func_w0622.txt @@ -0,0 +1,2 @@ +W: 8:function: Redefining built-in 'type' +W: 11: Redefining built-in 'map' diff --git a/test/messages/func_w0701.txt b/test/messages/func_w0701.txt new file mode 100644 index 0000000..677443b --- /dev/null +++ b/test/messages/func_w0701.txt @@ -0,0 +1,3 @@ +W: 8:function1: Raising a string exception +W: 12:function2: Raising a string exception + diff --git a/test/messages/func_w0702.txt b/test/messages/func_w0702.txt new file mode 100644 index 0000000..cd4ba27 --- /dev/null +++ b/test/messages/func_w0702.txt @@ -0,0 +1 @@ +W: 10: No exception type(s) specified diff --git a/test/messages/func_w0703.txt b/test/messages/func_w0703.txt new file mode 100644 index 0000000..e37197a --- /dev/null +++ b/test/messages/func_w0703.txt @@ -0,0 +1 @@ +W: 8: Catch "Exception" diff --git a/test/messages/func_w0704.txt b/test/messages/func_w0704.txt new file mode 100644 index 0000000..1eca794 --- /dev/null +++ b/test/messages/func_w0704.txt @@ -0,0 +1 @@ +W: 8: Except doesn't do anything diff --git a/test/messages/func_w0705.txt b/test/messages/func_w0705.txt new file mode 100644 index 0000000..303e739 --- /dev/null +++ b/test/messages/func_w0705.txt @@ -0,0 +1,5 @@ +E: 10: Bad except clauses order (Exception is an ancestor class of TypeError) +E: 17: Bad except clauses order (LookupError is an ancestor class of IndexError) +E: 24: Bad except clauses order (LookupError is an ancestor class of IndexError) +E: 24: Bad except clauses order (NameError is an ancestor class of UnboundLocalError) +E: 27: Bad except clauses order (empty except clause should always appears last)
\ No newline at end of file diff --git a/test/messages/func_w0801.txt b/test/messages/func_w0801.txt new file mode 100644 index 0000000..203ce92 --- /dev/null +++ b/test/messages/func_w0801.txt @@ -0,0 +1,11 @@ +R: 1: Similar lines in 2 files +==input.func_w0801:3 +==input.w0801_same:3 +__revision__ = 'id' +A = 2 +B = 3 +C = A + B +# need more than X lines to trigger the message +C *= 2 +A -= B +# all this should be detected diff --git a/test/messages/func_wrong_encoding.txt b/test/messages/func_wrong_encoding.txt new file mode 100644 index 0000000..10123a1 --- /dev/null +++ b/test/messages/func_wrong_encoding.txt @@ -0,0 +1 @@ +E: 1: Wrong encoding specified (UTF-8) diff --git a/test/messages/func_yield_outside_func.txt b/test/messages/func_yield_outside_func.txt new file mode 100644 index 0000000..a5f6c12 --- /dev/null +++ b/test/messages/func_yield_outside_func.txt @@ -0,0 +1 @@ +E: 3: yield outside function diff --git a/test/messages/nonexistant.txt b/test/messages/nonexistant.txt new file mode 100644 index 0000000..1a14b6e --- /dev/null +++ b/test/messages/nonexistant.txt @@ -0,0 +1 @@ +F: 1: No module named nonexistant diff --git a/test/messages/nonexistant.txt2 b/test/messages/nonexistant.txt2 new file mode 100644 index 0000000..1ba6167 --- /dev/null +++ b/test/messages/nonexistant.txt2 @@ -0,0 +1 @@ +F: 1: No module named input/nonexistant diff --git a/test/regrtest.py b/test/regrtest.py new file mode 100644 index 0000000..1e660d2 --- /dev/null +++ b/test/regrtest.py @@ -0,0 +1,142 @@ +# Copyright (c) 2005 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""non regression tests for pylint, which requires a too specific configuration +to be incorporated in the automatic functionnal test framework +""" + +import sys +import os +from os.path import abspath, join + +from logilab.common.testlib import TestCase, unittest_main + +from utils import TestReporter + +from pylint.lint import PyLinter +from pylint import checkers + +test_reporter = TestReporter() +linter = PyLinter() +linter.set_reporter(test_reporter) +linter.disable_message_category('I') +linter.config.persistent = 0 +checkers.initialize(linter) + +sys.path.insert(1, abspath('regrtest_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.failUnlessEqual(got, '') + + def test_package___init___precedence(self): + linter.check('precedence_test') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_check_package___init__(self): + for variation in ('package.__init__', 'regrtest_data/package/__init__.py'): + linter.check(variation) + got = linter.reporter.finalize().strip() + checked = linter.stats['by_module'].keys() + self.failUnlessEqual(checked, ['package.__init__'], + '%s: %s' % (variation, checked)) + cwd = os.getcwd() + os.chdir('regrtest_data/package') + sys.path.insert(0, '') + try: + for variation in ('__init__', '__init__.py'): + linter.check(variation) + got = linter.reporter.finalize().strip() + checked = linter.stats['by_module'].keys() + self.failUnlessEqual(checked, ['__init__'], + '%s: %s' % (variation, checked)) + finally: + sys.path.pop(0) + os.chdir(cwd) + + def test_gtk_import(self): + try: + import gtk + except ImportError: + self.skip('test skipped: gtk is not available') + except RuntimeError: # RuntimeError when missing display + self.skip('no display, can\'t run this test') + linter.check('regrtest_data/pygtk_import.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_numarray_inference(self): + try: + from numarray import random_array + except ImportError: + self.skip('test skipped: numarray.random_array is not available') + linter.check('regrtest_data/numarray_inf.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(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.skip('test skipped: numarray is not available') + linter.check('regrtest_data/numarray_import.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_socketerror_import(self): + linter.check('regrtest_data/socketerror_import.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_class__doc__usage(self): + linter.check('regrtest_data/classdoc_usage.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_package_import_relative_subpackage_no_attribute_error(self): + linter.check('import_package_subpackage_module') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_module_global_crash(self): + linter.check('regrtest_data/module_global.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_decimal_inference(self): + linter.check('regrtest_data/decimal_inference.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, "") + + def test_descriptor_crash(self): + for fname in os.listdir('regrtest_data'): + if fname.endswith('_crash.py'): + linter.check(join('regrtest_data', fname)) + linter.reporter.finalize().strip() + + def test_try_finally_disable_msg_crash(self): + linter.check(join('regrtest_data', 'try_finally_disable_msg_crash')) + +if __name__ == '__main__': + unittest_main() diff --git a/test/regrtest_data/application_crash.py b/test/regrtest_data/application_crash.py new file mode 100644 index 0000000..6e6044a --- /dev/null +++ b/test/regrtest_data/application_crash.py @@ -0,0 +1,12 @@ +class ErudiPublisher: + def __init__(self, config): + self.url_resolver = self.select_component('urlpublisher') + + def select_component(self, cid, *args, **kwargs): + try: + return self.select(self.registry_objects('components', cid), *args, **kwargs) + except NoSelectableObject: + return + + def main_publish(self, path, req): + ctrlid = self.url_resolver.process(req, path) diff --git a/test/regrtest_data/classdoc_usage.py b/test/regrtest_data/classdoc_usage.py new file mode 100644 index 0000000..ae8b9fe --- /dev/null +++ b/test/regrtest_data/classdoc_usage.py @@ -0,0 +1,17 @@ +"""ds""" + +__revision__ = None + +class SomeClass: + """cds""" + doc = __doc__ + + def __init__(self): + """only to make pylint happier""" + + def please(self): + """public method 1/2""" + + def besilent(self): + """public method 2/2""" + diff --git a/test/regrtest_data/decimal_inference.py b/test/regrtest_data/decimal_inference.py new file mode 100644 index 0000000..1b97f60 --- /dev/null +++ b/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 astng :o) +""" +import decimal + +decimal.getcontext().prec = 200 +print decimal.getcontext().prec + diff --git a/test/regrtest_data/descriptor_crash.py b/test/regrtest_data/descriptor_crash.py new file mode 100644 index 0000000..4b3adcc --- /dev/null +++ b/test/regrtest_data/descriptor_crash.py @@ -0,0 +1,20 @@ +# -*- coding: iso-8859-1 -*- + +import urllib + +class Page(object): + _urlOpen = staticmethod(urllib.urlopen) + + def getPage(self, url): + handle = self._urlOpen(url) + data = handle.read() + handle.close() + return data + #_getPage + +#Page + +if __name__ == "__main__": + import sys + p = Page() + print p.getPage(sys.argv[1]) diff --git a/test/regrtest_data/import_package_subpackage_module.py b/test/regrtest_data/import_package_subpackage_module.py new file mode 100644 index 0000000..b5fb219 --- /dev/null +++ b/test/regrtest_data/import_package_subpackage_module.py @@ -0,0 +1,49 @@ +# pylint: disable-msg=I0011,C0301,W0611 +"""I found some of my scripts trigger off an AttributeError in pylint +0.8.1 (with common 0.12.0 and astng 0.13.1). + +Traceback (most recent call last): + File "/usr/bin/pylint", line 4, in ? + lint.Run(sys.argv[1:]) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 729, in __init__ + linter.check(args) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 412, in check + self.check_file(filepath, modname, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 426, in check_file + astng = self._check_file(filepath, modname, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 450, in _check_file + self.check_astng_module(astng, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 494, in check_astng_module + self.astng_events(astng, [checker for checker in checkers + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astng_events + self.astng_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astng_events + self.astng_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 508, in astng_events + checker.visit(astng) + File "/usr/lib/python2.4/site-packages/logilab/astng/utils.py", line 84, in visit + method(node) + File "/usr/lib/python2.4/site-packages/pylint/checkers/variables.py", line 295, in visit_import + self._check_module_attrs(node, module, name_parts[1:]) + File "/usr/lib/python2.4/site-packages/pylint/checkers/variables.py", line 357, in _check_module_attrs + self.add_message('E0611', args=(name, module.name), +AttributeError: Import instance has no attribute 'name' + + +You can reproduce it by: +(1) create package structure like the following: + +package/ + __init__.py + subpackage/ + __init__.py + module.py + +(2) in package/__init__.py write: + +import subpackage + +(3) run pylint with a script importing package.subpackage.module. +""" +__revision__ = '$Id: import_package_subpackage_module.py,v 1.1 2005-11-10 16:08:54 syt Exp $' +import package.subpackage.module diff --git a/test/regrtest_data/module_global.py b/test/regrtest_data/module_global.py new file mode 100644 index 0000000..54fd46d --- /dev/null +++ b/test/regrtest_data/module_global.py @@ -0,0 +1,7 @@ +# pylint: disable-msg=W0603,W0601,W0604,E0602,W0104 +"""was causing infinite recursion +""" +__revision__ = 1 + +global bar +bar.foo diff --git a/test/regrtest_data/numarray_import.py b/test/regrtest_data/numarray_import.py new file mode 100644 index 0000000..3f0be8b --- /dev/null +++ b/test/regrtest_data/numarray_import.py @@ -0,0 +1,7 @@ +"""#10077""" + +__revision__ = 1 + +from numarray import zeros + +zeros() diff --git a/test/regrtest_data/numarray_inf.py b/test/regrtest_data/numarray_inf.py new file mode 100644 index 0000000..4ea22a9 --- /dev/null +++ b/test/regrtest_data/numarray_inf.py @@ -0,0 +1,5 @@ +"""#3216""" + +import numarray as na +import numarray.random_array as nar +IM16 = nar.randint(0, 256, 300).astype(na.UInt8) diff --git a/test/regrtest_data/package/AudioTime.py b/test/regrtest_data/package/AudioTime.py new file mode 100644 index 0000000..a1fde96 --- /dev/null +++ b/test/regrtest_data/package/AudioTime.py @@ -0,0 +1,3 @@ +"""test preceeded by the AudioTime class in __init__.py""" + +__revision__ = 0 diff --git a/test/regrtest_data/package/__init__.py b/test/regrtest_data/package/__init__.py new file mode 100644 index 0000000..9e1ebfa --- /dev/null +++ b/test/regrtest_data/package/__init__.py @@ -0,0 +1,14 @@ +# pylint: disable-msg=R0903,W0403 +"""package's __init__ file""" + +__revision__ = 0 + +# E0602 - Undefined variable '__path__' +__path__ += "folder" + +class AudioTime(object): + """test precedence over the AudioTime submodule""" + + DECIMAL = 3 + +import subpackage diff --git a/test/regrtest_data/package/subpackage/__init__.py b/test/regrtest_data/package/subpackage/__init__.py new file mode 100644 index 0000000..dc4782e --- /dev/null +++ b/test/regrtest_data/package/subpackage/__init__.py @@ -0,0 +1 @@ +"""package.subpackage""" diff --git a/test/regrtest_data/package/subpackage/module.py b/test/regrtest_data/package/subpackage/module.py new file mode 100644 index 0000000..4b7244b --- /dev/null +++ b/test/regrtest_data/package/subpackage/module.py @@ -0,0 +1 @@ +"""package.subpackage.module""" diff --git a/test/regrtest_data/precedence_test.py b/test/regrtest_data/precedence_test.py new file mode 100644 index 0000000..087b5cf --- /dev/null +++ b/test/regrtest_data/precedence_test.py @@ -0,0 +1,21 @@ +""" + # package/__init__.py + class AudioTime(object): + DECIMAL = 3 + + # package/AudioTime.py + class AudioTime(object): + pass + + # test.py + from package import AudioTime + # E0611 - No name 'DECIMAL' in module 'AudioTime.AudioTime' + print AudioTime.DECIMAL + +""" + +__revision__ = 0 + +from package import AudioTime + +print AudioTime.DECIMAL diff --git a/test/regrtest_data/pygtk_import.py b/test/regrtest_data/pygtk_import.py new file mode 100644 index 0000000..4ac9560 --- /dev/null +++ b/test/regrtest_data/pygtk_import.py @@ -0,0 +1,14 @@ +#pylint: disable-msg=R0903,R0904 +"""#10026""" +__revision__ = 1 +from gtk import VBox +import gtk + +class FooButton(gtk.Button): + """extend gtk.Button""" + def extend(self): + """hop""" + print self + +print gtk.Button +print VBox diff --git a/test/regrtest_data/socketerror_import.py b/test/regrtest_data/socketerror_import.py new file mode 100644 index 0000000..37310cf --- /dev/null +++ b/test/regrtest_data/socketerror_import.py @@ -0,0 +1,6 @@ +"""ds""" + +__revision__ = '$Id: socketerror_import.py,v 1.2 2005-12-28 14:58:22 syt Exp $' + +from socket import error +print error diff --git a/test/regrtest_data/try_finally_disable_msg_crash.py b/test/regrtest_data/try_finally_disable_msg_crash.py new file mode 100644 index 0000000..9628191 --- /dev/null +++ b/test/regrtest_data/try_finally_disable_msg_crash.py @@ -0,0 +1,5 @@ +try: + pass +finally: + # pylint: disable-msg=W0201 + pass diff --git a/test/rpythoninput/__init__.py b/test/rpythoninput/__init__.py new file mode 100644 index 0000000..60e92b7 --- /dev/null +++ b/test/rpythoninput/__init__.py @@ -0,0 +1 @@ +"""test""" diff --git a/test/rpythoninput/func_genexpr.py b/test/rpythoninput/func_genexpr.py new file mode 100644 index 0000000..21b45b1 --- /dev/null +++ b/test/rpythoninput/func_genexpr.py @@ -0,0 +1,11 @@ +import os + +def function(l): + os.write(1, '%s\n' % ';'.join(str(v) for v in l)) + +def entry_point(argv): + function([1, 2, 3, 4, 5, 6]) + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_immutable_global.py b/test/rpythoninput/func_immutable_global.py new file mode 100644 index 0000000..33d3ba6 --- /dev/null +++ b/test/rpythoninput/func_immutable_global.py @@ -0,0 +1,15 @@ +GLOB = {} + +def function(): + v = GLOB + v['key'] = "value" + +def entry_point(argv): + function() + if GLOB['key'] == 'value': + return 0 + return 1 + +def target(*args): + return entry_point, None + diff --git a/test/rpythoninput/func_immutable_global1.py b/test/rpythoninput/func_immutable_global1.py new file mode 100644 index 0000000..4eace03 --- /dev/null +++ b/test/rpythoninput/func_immutable_global1.py @@ -0,0 +1,14 @@ +GLOB = [] + +def function(): + GLOB.append("value") + +def entry_point(argv): + function() + if GLOB['key'] == 'value': + return 0 + return 1 + +def target(*args): + return entry_point, None + diff --git a/test/rpythoninput/func_immutable_global2.py b/test/rpythoninput/func_immutable_global2.py new file mode 100644 index 0000000..acb5415 --- /dev/null +++ b/test/rpythoninput/func_immutable_global2.py @@ -0,0 +1,15 @@ +GLOB = 1 + +def function(): + global GLOB + GLOB = "value" + +def entry_point(argv): + function() + if GLOB == 'value': + return 0 + return 1 + +def target(*args): + return entry_point, None + diff --git a/test/rpythoninput/func_immutable_global3.py b/test/rpythoninput/func_immutable_global3.py new file mode 100644 index 0000000..fc2a51d --- /dev/null +++ b/test/rpythoninput/func_immutable_global3.py @@ -0,0 +1,15 @@ + +def function(): + from rpythoninput import immutable_global3 + immutable_global3.GLOB = 2 # was 1 + +def entry_point(argv): + function() + from rpythoninput import immutable_global3 + if immutable_global3.GLOB == 2: + return 0 + return 1 + +def target(*args): + return entry_point, None + diff --git a/test/rpythoninput/func_multiple_inheritance.py b/test/rpythoninput/func_multiple_inheritance.py new file mode 100644 index 0000000..be798bf --- /dev/null +++ b/test/rpythoninput/func_multiple_inheritance.py @@ -0,0 +1,20 @@ +class A: + pass + +class B: + pass + +class C(A, B): + def __init__(self, a): + self.attr = a + + +def function(l): + os.write(1, '%s\n' % C(l)) + +def entry_point(argv): + function(argv) + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_multiple_inheritance2.py b/test/rpythoninput/func_multiple_inheritance2.py new file mode 100644 index 0000000..4f62021 --- /dev/null +++ b/test/rpythoninput/func_multiple_inheritance2.py @@ -0,0 +1,22 @@ +class A: + pass + +class B: + _mixin_ = True + def __init__(self): + self.mixattr = None # not a pure mixin ! + +class C(A, B): + def __init__(self, a): + self.attr = a + + +def function(l): + os.write(1, '%s\n' % C(l)) + +def entry_point(argv): + function(argv) + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_multiple_types_assignment.py b/test/rpythoninput/func_multiple_types_assignment.py new file mode 100644 index 0000000..a294552 --- /dev/null +++ b/test/rpythoninput/func_multiple_types_assignment.py @@ -0,0 +1,30 @@ +import os + +def function1(i): + attr = None + if i == 1: + attr = 1 + else: + attr = "hello" + return attr + +def function2(i): + attr = None + if i == 1: + attr = 1 + return attr + +def function_ok(i): + attr = None + if i == 1: + attr = "hello" + return attr + +def entry_point(argv): + os.write(str(function1(len(argv)))) + os.write(str(function_ok(len(argv)))) + return 0 + + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_buffer.py b/test/rpythoninput/func_nobuiltin_buffer.py new file mode 100644 index 0000000..eda0846 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_buffer.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = buffer() + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_callable.py b/test/rpythoninput/func_nobuiltin_callable.py new file mode 100644 index 0000000..830277f --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_callable.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = callable(function) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_classmethod.py b/test/rpythoninput/func_nobuiltin_classmethod.py new file mode 100644 index 0000000..bf2827b --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_classmethod.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = classmethod(function) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_compile.py b/test/rpythoninput/func_nobuiltin_compile.py new file mode 100644 index 0000000..f170dca --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_compile.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = compile("a==b", "?", "eval") + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_complex.py b/test/rpythoninput/func_nobuiltin_complex.py new file mode 100644 index 0000000..186b584 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_complex.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = complex(3, 4) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_delattr.py b/test/rpythoninput/func_nobuiltin_delattr.py new file mode 100644 index 0000000..cbe7cde --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_delattr.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = delattr(function, "bar") + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_dict.py b/test/rpythoninput/func_nobuiltin_dict.py new file mode 100644 index 0000000..c47ed20 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_dict.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = dict() + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_dir.py b/test/rpythoninput/func_nobuiltin_dir.py new file mode 100644 index 0000000..fe048e1 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_dir.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = dir() + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_enumerate.py b/test/rpythoninput/func_nobuiltin_enumerate.py new file mode 100644 index 0000000..dd59d12 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_enumerate.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = enumerate(range(5)) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_eval.py b/test/rpythoninput/func_nobuiltin_eval.py new file mode 100644 index 0000000..a8a898c --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_eval.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = eval("a == b") + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_execfile.py b/test/rpythoninput/func_nobuiltin_execfile.py new file mode 100644 index 0000000..7974bcd --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_execfile.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = execfile("foo.py") + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_file.py b/test/rpythoninput/func_nobuiltin_file.py new file mode 100644 index 0000000..4484d90 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_file.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = file("foo.txt") + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_filter.py b/test/rpythoninput/func_nobuiltin_filter.py new file mode 100644 index 0000000..2e49672 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_filter.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = filter(function, []) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_frozenset.py b/test/rpythoninput/func_nobuiltin_frozenset.py new file mode 100644 index 0000000..9440dd4 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_frozenset.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = frozenset() + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_getattr.py b/test/rpythoninput/func_nobuiltin_getattr.py new file mode 100644 index 0000000..1a6da7b --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_getattr.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = getattr(function, "bar") + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_globals.py b/test/rpythoninput/func_nobuiltin_globals.py new file mode 100644 index 0000000..409b3cd --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_globals.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = globals() + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_help.py b/test/rpythoninput/func_nobuiltin_help.py new file mode 100644 index 0000000..48e54a1 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_help.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = help(function) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_id.py b/test/rpythoninput/func_nobuiltin_id.py new file mode 100644 index 0000000..a053fd5 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_id.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = id(function) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_input.py b/test/rpythoninput/func_nobuiltin_input.py new file mode 100644 index 0000000..42ab233 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_input.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = input() + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_intern.py b/test/rpythoninput/func_nobuiltin_intern.py new file mode 100644 index 0000000..3e5b51e --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_intern.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = intern("foo") + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_issubclass.py b/test/rpythoninput/func_nobuiltin_issubclass.py new file mode 100644 index 0000000..a0c5ca7 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_issubclass.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = issubclass(function, str) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_iter.py b/test/rpythoninput/func_nobuiltin_iter.py new file mode 100644 index 0000000..5a425b7 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_iter.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = iter([]) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_locals.py b/test/rpythoninput/func_nobuiltin_locals.py new file mode 100644 index 0000000..c87ad4f --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_locals.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = locals() + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_map.py b/test/rpythoninput/func_nobuiltin_map.py new file mode 100644 index 0000000..d2c42b7 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_map.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = map(function, []) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_object.py b/test/rpythoninput/func_nobuiltin_object.py new file mode 100644 index 0000000..3151700 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_object.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = object() + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_open.py b/test/rpythoninput/func_nobuiltin_open.py new file mode 100644 index 0000000..b597f12 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_open.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = open("foo.txt") + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_property.py b/test/rpythoninput/func_nobuiltin_property.py new file mode 100644 index 0000000..6724497 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_property.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = property(function) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_raw_input.py b/test/rpythoninput/func_nobuiltin_raw_input.py new file mode 100644 index 0000000..3b1f7e3 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_raw_input.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = raw_input() + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_reduce.py b/test/rpythoninput/func_nobuiltin_reduce.py new file mode 100644 index 0000000..c314fd1 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_reduce.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = reduce(function, []) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_reload.py b/test/rpythoninput/func_nobuiltin_reload.py new file mode 100644 index 0000000..544d5ab --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_reload.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = reload(module) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_reversed.py b/test/rpythoninput/func_nobuiltin_reversed.py new file mode 100644 index 0000000..4937c94 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_reversed.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = reversed(range(5)) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_round.py b/test/rpythoninput/func_nobuiltin_round.py new file mode 100644 index 0000000..530f34e --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_round.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = round(3.4) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_set.py b/test/rpythoninput/func_nobuiltin_set.py new file mode 100644 index 0000000..5de5c1d --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_set.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = set([1, 2, 3]) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_setattr.py b/test/rpythoninput/func_nobuiltin_setattr.py new file mode 100644 index 0000000..27eb472 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_setattr.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = setattr(function, "bar", "baz") + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_sorted.py b/test/rpythoninput/func_nobuiltin_sorted.py new file mode 100644 index 0000000..ab495c0 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_sorted.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = sorted(range(5)) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_staticmethod.py b/test/rpythoninput/func_nobuiltin_staticmethod.py new file mode 100644 index 0000000..888471c --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_staticmethod.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = staticmethod(function) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_sum.py b/test/rpythoninput/func_nobuiltin_sum.py new file mode 100644 index 0000000..8e33ed5 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_sum.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = sum(xrange(4)) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_super.py b/test/rpythoninput/func_nobuiltin_super.py new file mode 100644 index 0000000..cc9106a --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_super.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = super(str) + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_unicode.py b/test/rpythoninput/func_nobuiltin_unicode.py new file mode 100644 index 0000000..c3e1107 --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_unicode.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = unicode("f") + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_nobuiltin_vars.py b/test/rpythoninput/func_nobuiltin_vars.py new file mode 100644 index 0000000..19e3e9d --- /dev/null +++ b/test/rpythoninput/func_nobuiltin_vars.py @@ -0,0 +1,13 @@ + +import os + +def function(): + retval = vars() + os.write(2, str(retval) + '\n') + +def entry_point(argv): + function() + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_noerror_multiple_inheritance.py b/test/rpythoninput/func_noerror_multiple_inheritance.py new file mode 100644 index 0000000..050b2c3 --- /dev/null +++ b/test/rpythoninput/func_noerror_multiple_inheritance.py @@ -0,0 +1,25 @@ +class A: + def __init__(self): + self.parentattr = None + +class B: + _mixin_ = True + +class C(A, B): + def __init__(self, a): + A.__init__(self) + self.attr = a + + +def function(l): + os.write(1, '%s\n' % C(l)) + +def entry_point(argv): + function(argv) + return 0 + +def target(*args): + return entry_point, None + +import os + diff --git a/test/rpythoninput/func_noerror_mutable_global.py b/test/rpythoninput/func_noerror_mutable_global.py new file mode 100644 index 0000000..24ccb65 --- /dev/null +++ b/test/rpythoninput/func_noerror_mutable_global.py @@ -0,0 +1,18 @@ +class Foo: + def __init__(self): + self.bar = None + +X = Foo() + +def function(): + X.bar = 'quux' + + +def entry_point(argv): + function() + if X.bar == 'quux': + return 0 + return 1 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_noerror_notrpython.py b/test/rpythoninput/func_noerror_notrpython.py new file mode 100644 index 0000000..9b756c7 --- /dev/null +++ b/test/rpythoninput/func_noerror_notrpython.py @@ -0,0 +1,12 @@ +def function(attrname): + """NOT RPYTHON""" + # I'm free ... + yield getattr(unicode, attrname) + yield getattr(str, attrname) + +def entry_point(argv): + return 0 + + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_noerror_slice_index.py b/test/rpythoninput/func_noerror_slice_index.py new file mode 100644 index 0000000..2dfe988 --- /dev/null +++ b/test/rpythoninput/func_noerror_slice_index.py @@ -0,0 +1,17 @@ +import os + +def function(l): + os.write(1, '%s\n' % l[:-1]) + os.write(1, '%s\n' % l[0:-1]) + os.write(1, '%s\n' % l[:-1:]) + os.write(1, '%s\n' % l[0:2]) + step = 1 + os.write(1, '%s\n' % l[0:2:step]) + + +def entry_point(argv): + function([1, 2, 3, 4, 5, 6]) + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_non_homogeneous_list.py b/test/rpythoninput/func_non_homogeneous_list.py new file mode 100644 index 0000000..a0d09db --- /dev/null +++ b/test/rpythoninput/func_non_homogeneous_list.py @@ -0,0 +1,12 @@ +GLOB = 1 + +def function(i): + return [i, GLOB, "hop"] + +def entry_point(argv): + function(len(argv)) + return 0 + + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_noyield.py b/test/rpythoninput/func_noyield.py new file mode 100644 index 0000000..72f8537 --- /dev/null +++ b/test/rpythoninput/func_noyield.py @@ -0,0 +1,17 @@ +import os + +def function(): + yield "hello" + yield "hello" + yield "hello" + +def entry_point(argv): + count = 0 + for elt in function(): + count += 1 + if count == 3: + return 0 # OK + return 1 # ERROR + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_repr_format_string.py b/test/rpythoninput/func_repr_format_string.py new file mode 100644 index 0000000..4a8c062 --- /dev/null +++ b/test/rpythoninput/func_repr_format_string.py @@ -0,0 +1,12 @@ +import os + +def function(l): + os.write(1, '%r\n' % l[-1]) + os.write(1, '%(list)r\n' % {'list':l}) + +def entry_point(argv): + function([1, 2, 3, 4, 5, 6]) + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_slice_negative_index.py b/test/rpythoninput/func_slice_negative_index.py new file mode 100644 index 0000000..cd247ab --- /dev/null +++ b/test/rpythoninput/func_slice_negative_index.py @@ -0,0 +1,20 @@ +import os + +A = -1 +B = -2 + +def function(l, step=1): + os.write(1, '%s\n' % l[-1:]) + os.write(1, '%s\n' % l[1:-1]) + os.write(1, '%s\n' % l[A:B]) + os.write(1, '%s\n' % l[-1:B:A]) + os.write(1, '%s\n' % l[-1::]) + os.write(1, '%s\n' % l[::-1]) + + +def entry_point(argv): + function([1, 2, 3, 4, 5, 6]) + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_slice_non_constant_step.py b/test/rpythoninput/func_slice_non_constant_step.py new file mode 100644 index 0000000..b6eafac --- /dev/null +++ b/test/rpythoninput/func_slice_non_constant_step.py @@ -0,0 +1,12 @@ +import os + +def function(l, step=1): + os.write(1, '%s\n' % l[1:3:step]) + + +def entry_point(argv): + function([1, 2, 3, 4, 5, 6], len(argv)) + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/func_unsupported_protocol.py b/test/rpythoninput/func_unsupported_protocol.py new file mode 100644 index 0000000..427d5da --- /dev/null +++ b/test/rpythoninput/func_unsupported_protocol.py @@ -0,0 +1,23 @@ +# pylint: disable-msg=E1202 +import os + +class C(object): + def __new__(cls, *args): + os.write('new!') + return object.__new__(cls, *args) + + def __init__(self, a): + self.attr = a + + def __add__(self, other): + return self.attr + other + +def function(l): + os.write(1, '%s\n' % (C(len(l)) + 4)) + +def entry_point(argv): + function(argv) + return 0 + +def target(*args): + return entry_point, None diff --git a/test/rpythoninput/immutable_global3.py b/test/rpythoninput/immutable_global3.py new file mode 100644 index 0000000..311eec1 --- /dev/null +++ b/test/rpythoninput/immutable_global3.py @@ -0,0 +1 @@ +GLOB = 1 diff --git a/test/rpythonmessages/func_genexpr.txt b/test/rpythonmessages/func_genexpr.txt new file mode 100644 index 0000000..0b64c6f --- /dev/null +++ b/test/rpythonmessages/func_genexpr.txt @@ -0,0 +1 @@ +E: 1:function: generator expressions are not supported diff --git a/test/rpythonmessages/func_immutable_global.txt b/test/rpythonmessages/func_immutable_global.txt new file mode 100644 index 0000000..2cf59c6 --- /dev/null +++ b/test/rpythonmessages/func_immutable_global.txt @@ -0,0 +1 @@ +E: 5:function: Modifying global 'v' from module rpythoninput.func_immutable_global diff --git a/test/rpythonmessages/func_immutable_global1.txt b/test/rpythonmessages/func_immutable_global1.txt new file mode 100644 index 0000000..ed9c39e --- /dev/null +++ b/test/rpythonmessages/func_immutable_global1.txt @@ -0,0 +1 @@ +E: 4:function: Modifying global 'GLOB' from module rpythoninput.func_immutable_global1 diff --git a/test/rpythonmessages/func_immutable_global2.txt b/test/rpythonmessages/func_immutable_global2.txt new file mode 100644 index 0000000..3e9578a --- /dev/null +++ b/test/rpythonmessages/func_immutable_global2.txt @@ -0,0 +1 @@ +E: 5:function: Modifying global 'GLOB' from module rpythoninput.func_immutable_global2 diff --git a/test/rpythonmessages/func_immutable_global3.txt b/test/rpythonmessages/func_immutable_global3.txt new file mode 100644 index 0000000..1d43f23 --- /dev/null +++ b/test/rpythonmessages/func_immutable_global3.txt @@ -0,0 +1,2 @@ +E: 4:function: Modifying global 'GLOB' from module rpythoninput.immutable_global3 + diff --git a/test/rpythonmessages/func_multiple_inheritance.txt b/test/rpythonmessages/func_multiple_inheritance.txt new file mode 100644 index 0000000..4aba34f --- /dev/null +++ b/test/rpythonmessages/func_multiple_inheritance.txt @@ -0,0 +1 @@ +E: 7:C: multiple inheritance only supported under specific rules which doesn't seems to be satisfied diff --git a/test/rpythonmessages/func_multiple_inheritance2.txt b/test/rpythonmessages/func_multiple_inheritance2.txt new file mode 100644 index 0000000..41e8a8a --- /dev/null +++ b/test/rpythonmessages/func_multiple_inheritance2.txt @@ -0,0 +1 @@ +E: 9:C: multiple inheritance only supported under specific rules which doesn't seems to be satisfied diff --git a/test/rpythonmessages/func_multiple_types_assignment.txt b/test/rpythonmessages/func_multiple_types_assignment.txt new file mode 100644 index 0000000..a2e181d --- /dev/null +++ b/test/rpythonmessages/func_multiple_types_assignment.txt @@ -0,0 +1,2 @@ +E: 3:function1: Multiple types assigned to identifier 'attr' +E: 11:function2: Can't mix int and None on identifier 'attr' diff --git a/test/rpythonmessages/func_nobuiltin_buffer.txt b/test/rpythonmessages/func_nobuiltin_buffer.txt new file mode 100644 index 0000000..cad00a4 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_buffer.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'buffer'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_callable.txt b/test/rpythonmessages/func_nobuiltin_callable.txt new file mode 100644 index 0000000..b136693 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_callable.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'callable'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_classmethod.txt b/test/rpythonmessages/func_nobuiltin_classmethod.txt new file mode 100644 index 0000000..34dd1ab --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_classmethod.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'classmethod'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_compile.txt b/test/rpythonmessages/func_nobuiltin_compile.txt new file mode 100644 index 0000000..806ed01 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_compile.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'compile'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_complex.txt b/test/rpythonmessages/func_nobuiltin_complex.txt new file mode 100644 index 0000000..cfd44af --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_complex.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'complex'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_delattr.txt b/test/rpythonmessages/func_nobuiltin_delattr.txt new file mode 100644 index 0000000..d75a246 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_delattr.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'delattr'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_dict.txt b/test/rpythonmessages/func_nobuiltin_dict.txt new file mode 100644 index 0000000..8926654 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_dict.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'dict'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_dir.txt b/test/rpythonmessages/func_nobuiltin_dir.txt new file mode 100644 index 0000000..484c26c --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_dir.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'dir'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_enumerate.txt b/test/rpythonmessages/func_nobuiltin_enumerate.txt new file mode 100644 index 0000000..4e8eabb --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_enumerate.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'enumerate'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_eval.txt b/test/rpythonmessages/func_nobuiltin_eval.txt new file mode 100644 index 0000000..ce5b6a4 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_eval.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'eval'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_execfile.txt b/test/rpythonmessages/func_nobuiltin_execfile.txt new file mode 100644 index 0000000..9e4fe7c --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_execfile.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'execfile'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_file.txt b/test/rpythonmessages/func_nobuiltin_file.txt new file mode 100644 index 0000000..8de81b5 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_file.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'file'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_filter.txt b/test/rpythonmessages/func_nobuiltin_filter.txt new file mode 100644 index 0000000..454b8d4 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_filter.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'filter'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_frozenset.txt b/test/rpythonmessages/func_nobuiltin_frozenset.txt new file mode 100644 index 0000000..5853873 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_frozenset.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'frozenset'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_getattr.txt b/test/rpythonmessages/func_nobuiltin_getattr.txt new file mode 100644 index 0000000..853698a --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_getattr.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'getattr'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_globals.txt b/test/rpythonmessages/func_nobuiltin_globals.txt new file mode 100644 index 0000000..d92ef44 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_globals.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'globals'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_help.txt b/test/rpythonmessages/func_nobuiltin_help.txt new file mode 100644 index 0000000..50d5a8b --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_help.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'help'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_id.txt b/test/rpythonmessages/func_nobuiltin_id.txt new file mode 100644 index 0000000..44b1044 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_id.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'id'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_input.txt b/test/rpythonmessages/func_nobuiltin_input.txt new file mode 100644 index 0000000..8df0e0c --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_input.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'input'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_intern.txt b/test/rpythonmessages/func_nobuiltin_intern.txt new file mode 100644 index 0000000..381113d --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_intern.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'intern'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_issubclass.txt b/test/rpythonmessages/func_nobuiltin_issubclass.txt new file mode 100644 index 0000000..72b8851 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_issubclass.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'issubclass'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_iter.txt b/test/rpythonmessages/func_nobuiltin_iter.txt new file mode 100644 index 0000000..ec4501e --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_iter.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'iter'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_locals.txt b/test/rpythonmessages/func_nobuiltin_locals.txt new file mode 100644 index 0000000..ff8fb73 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_locals.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'locals'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_map.txt b/test/rpythonmessages/func_nobuiltin_map.txt new file mode 100644 index 0000000..838a820 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_map.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'map'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_object.txt b/test/rpythonmessages/func_nobuiltin_object.txt new file mode 100644 index 0000000..667bfe6 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_object.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'object'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_open.txt b/test/rpythonmessages/func_nobuiltin_open.txt new file mode 100644 index 0000000..09e0a8c --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_open.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'open'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_property.txt b/test/rpythonmessages/func_nobuiltin_property.txt new file mode 100644 index 0000000..2c31d4a --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_property.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'property'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_raw_input.txt b/test/rpythonmessages/func_nobuiltin_raw_input.txt new file mode 100644 index 0000000..9c725d9 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_raw_input.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'raw_input'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_reduce.txt b/test/rpythonmessages/func_nobuiltin_reduce.txt new file mode 100644 index 0000000..b1c61cd --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_reduce.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'reduce'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_reload.txt b/test/rpythonmessages/func_nobuiltin_reload.txt new file mode 100644 index 0000000..2b96613 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_reload.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'reload'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_reversed.txt b/test/rpythonmessages/func_nobuiltin_reversed.txt new file mode 100644 index 0000000..8ae4a20 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_reversed.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'reversed'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_round.txt b/test/rpythonmessages/func_nobuiltin_round.txt new file mode 100644 index 0000000..44dde14 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_round.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'round'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_set.txt b/test/rpythonmessages/func_nobuiltin_set.txt new file mode 100644 index 0000000..2be0dab --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_set.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'set'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_setattr.txt b/test/rpythonmessages/func_nobuiltin_setattr.txt new file mode 100644 index 0000000..465d8a1 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_setattr.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'setattr'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_sorted.txt b/test/rpythonmessages/func_nobuiltin_sorted.txt new file mode 100644 index 0000000..94fa187 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_sorted.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'sorted'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_staticmethod.txt b/test/rpythonmessages/func_nobuiltin_staticmethod.txt new file mode 100644 index 0000000..93c0297 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_staticmethod.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'staticmethod'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_sum.txt b/test/rpythonmessages/func_nobuiltin_sum.txt new file mode 100644 index 0000000..373910f --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_sum.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'sum'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_super.txt b/test/rpythonmessages/func_nobuiltin_super.txt new file mode 100644 index 0000000..9c497b4 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_super.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'super'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_unicode.txt b/test/rpythonmessages/func_nobuiltin_unicode.txt new file mode 100644 index 0000000..68caf5e --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_unicode.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'unicode'
\ No newline at end of file diff --git a/test/rpythonmessages/func_nobuiltin_vars.txt b/test/rpythonmessages/func_nobuiltin_vars.txt new file mode 100644 index 0000000..ae2f3c9 --- /dev/null +++ b/test/rpythonmessages/func_nobuiltin_vars.txt @@ -0,0 +1 @@ +E: 5:function: Using unavailable builtin 'vars'
\ No newline at end of file diff --git a/test/rpythonmessages/func_non_homogeneous_list.txt b/test/rpythonmessages/func_non_homogeneous_list.txt new file mode 100644 index 0000000..10404bc --- /dev/null +++ b/test/rpythonmessages/func_non_homogeneous_list.txt @@ -0,0 +1 @@ +E: 4:function: Non homogeneous values in list diff --git a/test/rpythonmessages/func_noyield.txt b/test/rpythonmessages/func_noyield.txt new file mode 100644 index 0000000..b0ac4b1 --- /dev/null +++ b/test/rpythonmessages/func_noyield.txt @@ -0,0 +1,3 @@ +E: 4:function: Using unavailable keyword 'yield' +E: 5:function: Using unavailable keyword 'yield' +E: 6:function: Using unavailable keyword 'yield' diff --git a/test/rpythonmessages/func_repr_format_string.txt b/test/rpythonmessages/func_repr_format_string.txt new file mode 100644 index 0000000..65ba565 --- /dev/null +++ b/test/rpythonmessages/func_repr_format_string.txt @@ -0,0 +1,2 @@ +E: 4:function: %r format is not supported +E: 5:function: %r format is not supported diff --git a/test/rpythonmessages/func_slice_negative_index.txt b/test/rpythonmessages/func_slice_negative_index.txt new file mode 100644 index 0000000..2074112 --- /dev/null +++ b/test/rpythonmessages/func_slice_negative_index.txt @@ -0,0 +1,9 @@ +E: 7:function: Using negative slice start index -1 (infered to -1) +E: 8:function: Using negative slice stop index -1 (infered to -1) +E: 9:function: Using negative slice start index A (infered to -1) +E: 9:function: Using negative slice stop index B (infered to -2) +E: 10:function: Using negative slice start index -1 (infered to -1) +E: 10:function: Using negative slice step A (infered to -1) +E: 10:function: Using negative slice stop index B (infered to -2) +E: 11:function: Using negative slice start index -1 (infered to -1) +E: 12:function: Using negative slice step -1 (infered to -1) diff --git a/test/rpythonmessages/func_slice_non_constant_step.txt b/test/rpythonmessages/func_slice_non_constant_step.txt new file mode 100644 index 0000000..e537176 --- /dev/null +++ b/test/rpythonmessages/func_slice_non_constant_step.txt @@ -0,0 +1 @@ +E: 4:function: Using non constant step diff --git a/test/rpythonmessages/func_unsupported_protocol.txt b/test/rpythonmessages/func_unsupported_protocol.txt new file mode 100644 index 0000000..330be4f --- /dev/null +++ b/test/rpythonmessages/func_unsupported_protocol.txt @@ -0,0 +1,2 @@ +E: 5:C.__new__: Using unavailable protocol '__new__' +W: 12:C.__add__: special method __add__ has to be called explicitly diff --git a/test/runtests.py b/test/runtests.py new file mode 100644 index 0000000..67d6636 --- /dev/null +++ b/test/runtests.py @@ -0,0 +1,5 @@ +from logilab.common.testlib import main + +if __name__ == '__main__': + import sys, os + main(os.path.dirname(sys.argv[0]) or '.') diff --git a/test/smoketest.py b/test/smoketest.py new file mode 100644 index 0000000..a2f0170 --- /dev/null +++ b/test/smoketest.py @@ -0,0 +1,71 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. + +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys +from cStringIO import StringIO + +from logilab.common.testlib import TestCase, unittest_main + +from pylint.lint import Run +from pylint.reporters.text import * +from pylint.reporters.html import HTMLReporter + + +class LintSmokeTest(TestCase): + + def test1(self): + """make pylint checking itself""" + Run(['--include-ids=y', 'pylint'], reporter=TextReporter(StringIO())) + + def test2(self): + """make pylint checking itself""" + Run(['pylint.lint'], reporter=ParseableTextReporter(StringIO())) + + def test3(self): + """make pylint checking itself""" + Run(['pylint.checkers'], reporter=HTMLReporter(StringIO())) + + def test4(self): + """make pylint checking itself""" + Run(['pylint.checkers'], reporter=ColorizedTextReporter(StringIO())) + + def test5(self): + """make pylint checking itself""" + Run(['pylint.checkers'], reporter=VSTextReporter(StringIO())) + + def test_generate_config_option(self): + """make pylint checking itself""" + sys.stdout = StringIO() + try: + self.assertRaises(SystemExit, Run, + ['--generate-rcfile'], + reporter=HTMLReporter(StringIO())) + finally: + sys.stdout = sys.__stdout__ + + def test_help_message_option(self): + """make pylint checking itself""" + sys.stdout = StringIO() + try: + self.assertRaises(SystemExit, Run, + ['--help-msg', 'W0101'], + reporter=HTMLReporter(StringIO())) + self.assertRaises(SystemExit, Run, + ['--help-msg', 'WX101'], + reporter=HTMLReporter(StringIO())) + finally: + sys.stdout = sys.__stdout__ + + +if __name__ == '__main__': + unittest_main() diff --git a/test/test_encoding.py b/test/test_encoding.py new file mode 100644 index 0000000..34003df --- /dev/null +++ b/test/test_encoding.py @@ -0,0 +1,62 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Check source code is ascii only or has an encoding declaration (PEP 263) +""" + +__revision__ = '$Id: test_encoding.py,v 1.6 2005-11-02 09:22:04 syt Exp $' + +from logilab.common.testlib import TestCase, unittest_main +import sys +from pylint.checkers.misc import guess_encoding + +class TestGuessEncoding(TestCase): + + def testEmacs(self): + e = guess_encoding('# -*- coding: UTF-8 -*-') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding('# -*- coding:UTF-8 -*-') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding(''' + ### -*- coding: ISO-8859-1 -*- + ''') + self.failUnlessEqual(e, 'ISO-8859-1') + e = guess_encoding(''' + + ### -*- coding: ISO-8859-1 -*- + ''') + self.failUnlessEqual(e, None) + + def testVim(self): + e = guess_encoding('# vim:fileencoding=UTF-8') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding(''' + ### vim:fileencoding=ISO-8859-1 + ''') + self.failUnlessEqual(e, 'ISO-8859-1') + e = guess_encoding(''' + + ### vim:fileencoding= ISO-8859-1 + ''') + self.failUnlessEqual(e, None) + + def testUTF8(self): + e = guess_encoding('\xef\xbb\xbf any UTF-8 data') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding(' any UTF-8 data \xef\xbb\xbf') + self.failUnlessEqual(e, None) + +if __name__ == '__main__': + unittest_main() diff --git a/test/test_format.py b/test/test_format.py new file mode 100644 index 0000000..94a499f --- /dev/null +++ b/test/test_format.py @@ -0,0 +1,168 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Check format checker helper functions +""" + +__revision__ = '$Id: test_format.py,v 1.13 2005-11-02 09:22:06 syt Exp $' + +import unittest +import sys +import re +from os import linesep + +from pylint.checkers.format import * +from utils import TestReporter + +REPORTER = TestReporter() + +class StringRgxTest(unittest.TestCase): + """test the STRING_RGX regular expression""" + + def test_known_values_1(self): + self.assertEqual(STRING_RGX.sub('', '"yo"'), '') + + def test_known_values_2(self): + self.assertEqual(STRING_RGX.sub('', "'yo'"), '') + + def test_known_values_tq_1(self): + self.assertEqual(STRING_RGX.sub('', '"""yo"""'), '') + + def test_known_values_tq_2(self): + self.assertEqual(STRING_RGX.sub('', '"""yo\n'), '') + + def test_known_values_ta_1(self): + self.assertEqual(STRING_RGX.sub('', "'''yo'''"), '') + + def test_known_values_ta_2(self): + self.assertEqual(STRING_RGX.sub('', "'''yo\n"), '') + + def test_known_values_5(self): + self.assertEqual(STRING_RGX.sub('', r'"yo\"yo"'), '') + + def test_known_values_6(self): + self.assertEqual(STRING_RGX.sub('', r"'yo\'yo'"), '') + + def test_known_values_7(self): + self.assertEqual(STRING_RGX.sub('', '"yo"upi"yo"upi'), 'upiupi') + + def test_known_values_8(self): + self.assertEqual(STRING_RGX.sub('', "'yo\\'yo\\"), '') + + def test_known_values_9(self): + self.assertEqual(STRING_RGX.sub('', '"yoyo\\'), '') + + def test_known_values_10(self): + self.assertEqual(STRING_RGX.sub('', 'self.filterFunc = eval(\'lambda %s: %s\'%(\',\'.join(variables),formula),{},{})'), + 'self.filterFunc = eval(%(.join(variables),formula),{},{})') + + def test_known_values_11(self): + self.assertEqual(STRING_RGX.sub('', 'cond_list[index] = OLD_PROG.sub(r\'getattr(__old__,"\1")\',cond)'), + 'cond_list[index] = OLD_PROG.sub(r,cond)') + + +## def test_no_crash(self): +## crash_str = """wizardBmp = ('eJzdXc2R5DxyPZBR0WPBd+4rywOZsQasCevDHHTVdQ5jx1w3QnJBF7mwISdUUyAeXv4CIFndPYpA\ndLBR+H14TCQyAfDHf/7vcvu+h7ef7TkKI2leEU7WW7K//3r8vf/jn78f3n9tf/+f3w9vPx8P+zMi\n3389kpWUj7/8a3lWkSUll/NDAYv2lwc3huPLw1XPF4JsCmxQPJEEaMAZCRg0HgU9IiY7gy+A/X8T\nAKnk2P1v/2ooPZ6B8CM9pWTiWdgthhbtPw9Yl6v2tZKQ/u7s3/7117/9twva+vZfO/Ge8LoEtrxV\nXLWRfxiwjJ78+8Cn4FmoWB5AUX6wHCsEBvKPv4/nndUmC1hdfuWUoYx1mcBksCKRH87Hx+E3bslP\nt++/iVeRVJACKyaeJbAFTbzg8Yi4kQD20bAS0No/KBSKtn+fHSyB/y3PJc0DWPzL6UtRKjGK5QdR\n/tvPUiB+3YE1YPK/zJkP+BvVruMLCeltLZElBgRGZMmFlFwOMxzlFOI9nguTS2I841euCA8A9tMp\ndz4wxxSjkpQq0s2r0nf/ZcZ+OiyzGIKr65MnJZ6nG6YTXlIbOGURvCoSNUIsc43lGZF4gLr16VgN\n4snPETntw7UNyCUwIiFjPx23PEAdUmyEcGMxuFZWd8u00tjNhZQQrYrSiET2PwXYSOi5M77i9mD5\ng2m4xqSELwWs2wyQihmrmFa09DXQClQAqVhmPp5zUcw66moYi2Qo8zewX1JxtfNsxEyXQonUdXWG\nKcYmCb6+jAV/mHjMMZa0KqXSYJWqwM8Rq22A/GSt2MX2d5n/+OeX1QosPSLVMS/Bpsz/TUqbyvjV\nGMvLKLUggpKZCEMWC0oalwQR1fMmqdcnJy0D++l4JiitwxRVedeA8LZklT4h5whpF2ndmu51bFtQ\nkZGFyranTO5LsGafClBrFf9R5m5rJQWYLQ9qkbVIQ5ZaZK2kjaDM0GzIpjnXFsrxbjJVQgxv+asM\ndMXKx8ZVkZ3El1va4y8MfevTlL13v5qvuUbXEdBs2lIitbaRnRzDBMxDn9dLzdSENtN1qQb5b//+\nH2Vi3Q37yoqrHiK3QrEBPg16rpcqisQQPJphf2W3ws5zeBAiYF1DffdX+zCJMBrGjo9Hwwq8v2Oi\nVnVrJPeW9RuWRYFDPE4pueqyGrKCIz/TNVNNyuw+fjyUzha6alSnCn8CCwyVwxpsdF3bEVxKxpah\n55S7p+ZjgPVcPPvMUvpVnaT7orXS9fH3d/OemwH6GJrgOlv3yGcb9hrzlMbx7Q5Tf1/BQIPbT/lf\nCezvYa3/YtJpbX4+lyYVSBuwg6ia1iovbakFD3t71MRXFVQFrHJt20kQwIIGrro1okodVsygBbGF\nudBgb+Fzc0VB9XdT5XBwsa7mJnSMqhuwCFX6Q6grkuZgtTWhYsn3sWT/9AVCa1hRzh+oPl2cRRUs\nNqKz5c+vL1yQo/jFWz58CrCJgl2wLTMXRMExHApFS4xyIB4YGoiUe91CkOf6AGBL+RBiPL6LWSFi\nKm9awRhjlbjgks9wdbYEJQpeZITBXAZdscynK/k4QAOGSKlb3V5gOVDECK+V8FKcIe0amHSShr2a\nsUXxKChh6HmzhOLDozGPX4UoGBh0aK0F1aKkrVdw9XAhr2Zs6WYBimdYFllIDIgEsFU7CiF9ZsFk\ntyvncheHqmK2I9bdM2g2fBWwT/qVN7qpT7H0KxDxykuld6tgkpeMyHUJY21rR4o9IwqUNUk9rCRj\nuddlblqlAVlhVUZhRCvYB6J6Q3a7jXT8RS0fD+yUAWP3sQuKermMrQYBy1urFayVgV11q+AJCcCj\nBpV4kBhDKed1jA+YvPb5tMKF19yn65Pk2gjjLrvMEMB+Kyyx80ZyN0CfwbL3k4Et1HoaRZm35aH8\nZNPnMhavgJitnlqBVYyvosdsma8GtlBot3w+5ZLimLJUuKJAmzJqGraHN7NqVTngXrmmF9JBuSvh\nsZphtYJZwZ6nb+vBqmo++gvLFa+tkEBPXsoJSCYatkirfb94uEThsatFVgJdeH3XjOvwcl0ksUUR\ngg4PZQlWDFY8lzVrdrW5hYZuT8vdRQrZCblGYcyMrJoqjQwFKMeDkHr9Oj4vi21uHUWos3NR0dkk\nCGGoZ3PZKiUKEPSIPDO6ptf9TZltuUcV66DZnZuqZHp+iQehTlULuTbge2Vyuig5wFb0xFjcvqN8\nB1iWP6e747hvAGwQXuWacU+uPW2tnGaROhjM2lB3W/OCWW/xnCn7FNOVWpPdYV+kesVeCyy6YHxz\n7LNQwC71MGa3JTCwNNrfGmm5ImxCuLBrjoy9ENjcvVWf0QZ1tCppzKA3VnC11gX1WIiC2wBXXX+Z\nl8vuQBCv3nlgRxoZnzW7piLZX9fft89cl1bkvxQjwLrvjtpw4oZ8cPnY9dXAlnHUbuhECCiNK1Kx\nboa3kSiI8AGwqa47skZo6g0AJFeRiLw16aqUBIWYeGABHpVv/62ehbWag/aF28zaga067gLBXS7l\nwEJDTgDHK1nmcUzcWPJzJOYpbewqfrGKalEkmgKAgasKA8phVQEVFa1g/7Xu/UOZC/zCqfubQ7Sk\ndZEpz4dtBUt1ES5Pc6u2MkkLSRSJiR5t0Cpr/bVwuyw0GFJeow014ykbeZX6onAMWDXc6F1pPGwj\nI93czCG+xawFdkDqpGDLnALWdiF6nRVpt+ETZGs9NXNydEAnyLfyzH+1UJVyVb0LEau1gK0xXLUj\nabEwOdrTRRmCXuyaYSha78qOrEqwXKtUhax1ZgmJx6XBzvOsJdJ/0LyIioPMWY1r5gMYq8ax9J2f\nxZueOwff9vtDYCjQb30ZMpqdudjlNYZuW4VbnQbWaAWd8oM0apMbRzJhwKJWYNH6pGkIVi9oF816\nUFG9zx/XOhYi93cC1yWigMdUU6hnBme9CKuVBuyt2Wq0EYZk6esgXc1LMRgsYxUUg0uG4nxRXE12\n9TA5oUE1yYwDCDQBWU24tOpeT37Z6o5JOUc1pRsSlt6OuKbHnt4nqf4dYRELUiE5pZdWKQ9aW6i8\njRpzVbA96lY0KwoiAi/m+F5YQtWXeEpi9Hjvlp3l1VzGRphXQFoC/JKoqKvKHl950fqlLZ8H6Fpw\nYHxAy7W6FMHJxThwF2kb/1G3KLxa0q5S2A4ytpkp5CJ6lRSN7AZF/qxmA7xumJSfanrigN2Y0FoZ\n2IV2MAodjPQ6tnFdAilPGcpQYCm31G2cC2xf1rYmjWyigzDRkDFtrYcduF5ec4GNTYp67zsrCaiu\nFFVmK7VcVXYz0XJqreqOk9IzjYqtvDjHEZnHI2Ddurzul594T+YiLbGahy4UEbBxGtjwHQOLJUmE\n83iQzkRYt/Jc7gQxF8hlAGuwILaEC6JA2A28IUj8Nfe6Qwlnl6LJ7ppgTtQmrPCBTTchRAN6V6f/\n2DNS3dx6tkqD1mNtgupML/Mg29PYB6THN/dtJV5dewg3cKD4wEaeC9MYTN8LzCy0P6TYUVUtP5Q7\nuzfc0ApssK49a0V0sB1H1fxqN2w0GRsU2xpvJLbSE8QY0aqfu7nW7Y6Kez+qeR8czvqolrQRsM+H\nuzl7K96L6MKEm5xBeu48vIZ362HnlFQyGi+0lBhbq26V+QsifbcGV6qUOcVFyVXGwBBfxtaWN9ce\nWSZZ3+DsbtdGWMSdvcsjaUrXsiUoW8FhNY/XCAoo0c2qs4VFWcbaJfNbdQsSqWCsEHPZpSaaqbWz\nBdaCvJgVAWfh5R5sa40k5kXOUW08lyRHGixGVnkhnhIjwg5mmANCul1Wv6JHS90utcZLWmS8ymwY\njSCE6ng5i1S3wi4wf0gPKaYGsbgzQB3r3ZT90AW22ww7oGDOMWPIIlUjPbmb9tzpLLbzgkgLD2tu\n/ZYEo3CXpx1dKPj5pIxVYzfivuwWuMiV1xoTxtp8gC3ztiwi7vViBNvs2W6OhPOiwI7j9ndxzTKP\neDdykc70osKtZM1WWaCNMIF31aiqFne6YSZsmzkRg4sobY2rfDcRyTdPXqIVHBtWl95lndtsrdKG\nFlWneXtrbnFlT4DWnRei3hEb6b4+nFgBaxWAbrAE2OpnBXJyupHMNJgUVnnNqaUKqNvKKT5xcycS\n+x04i92McTdnKGyN3N+S91rG5pI2Z7I+CU6rgx/VbWRJYqll22DnMj6tE7EuomJSo5v9vIxly49i\noNi4LkdcybrdxFr3XTBdN3mzgaW6uvsU5bS2kT1BXZUG0/Hq3Z2uWG0vP2cyYzcO1imX6LFq7BQP\nRziw1lUVb1NUhajDXPZdsHJ75+17O6fDvI1kqfuW5UJY8fa8KGjDRzO4KzlHIsuDqxW40/FGn3tQ\ndFJUX8ie8MAWhlzrtVcEhnqwmtdwNS+suEo6vRtqkKg5V5OMopC3fR9sMxu+/XRJstCJzsiTqEU9\n6bfgbZEMlqJWAViMq3RExgoDmjKmKeMSLGNQJOqQCeEWGBzsT9r6pFZetAWOG8+EwWY8VZ1rAGS7\nkJAJXIU0cPErs0jvnq0IkWfdGdIf1B6OeZd69tjVHL1k1x5o2Q9GB9O8kuHuGrp7Xchx6zZSjaOo\n9Ci8vKRVP0H0cTfvfLAr9xSYWrQdrCKvRmGRPosueS5wwLm3Jp4rM3Mmvu1HdNtSOj8pk7Zc6WBJ\n4tY1OKZ73Ah/dZuq3BA37aXFUv1VwN6O+ExzBELeco3VKbNf8ztQbANKerX8mGfilexIdzLCYNV5\nf1qeWzK3nGBmyfduLdXnxcrPSE9tUHtIxNpBcqn59UizgjdEgaOAnVWxxPxL5rhBRcsviuihjKht\nFNg1oxYaN+k9JP0NJS/yHAROT4CxRVVAvUIbfG+n9bt9ObbyEtZdug3+7m1wmgZWigLWalCjWtJq\nlbVeHM0vuHIrqEh2QVrD2kp3zq9jZudLlrSaOYd4y/pSWyDc/FUhqogq6p4Fs5H2hpm86hFguf2u\nb6K7HGvLRnMJZANN/gWSIrICWyStW+9Gl5dinSuqo/0MvDrOgb3X2+ybmuE5TGHTjrhxOWPV82Fh\nu6iVl1pSYYHJN1FbKwpd39FOgSkZiw2K9LEArPXcDvLyNsfhKg+CayE5Ir3V5BWUAEloRSKviztq\nmxvkC8UdtHaeJPy4wjssqDKzqyHCVqhbnk3vToeUMY+3BJWNTWASRVfPXtEFYfZNtIw9BjKbiDGs\nhxUDwTSzUcRON2oSV9pFM+2aI2aq6ryDdkdKB9jrGOsOKBuCBksQwEozeDKPcwkQs2zTPtnNXA9x\ngB1g7IhWYA+crvUKPlUOy0NVS0GyiQIoDwYoNemzClHKKXoF6126ruGeQlVm67ebEdkv0Qp4QO2/\ns5J2sdatun9PM9AcZ26+G57CsHfxsKLSc3bbcJVWUILycbhKZhdVLQpqDDa0sKsRhgXe1bxKw6wa\npmNdu9dNHevYPeqREn5mZFk3OLIEMycTFZ7CllVPMTeVtYpl1g0QSoIzHZySsVcBW6pmvR1Kwjiq\nSt2yq/tG4+oS4vWUVU3vtNdISVpRdTyJ3+m4LvYdjQAL3ViYW61Zj030xrq42h1N9ZMH69uw0+St\nnaV1r0ABD4Whm+Y1t1jf3Jp4c3hTN94LaJLwm6dutYlVSTfIL7jBrXDvnYqyrbLAqjR89kGcpwvq\n0uWMexzgs1BfqUvdFhcDe/NO7sAuOgMsXKhaQ6CAipRFxRcIJFI0bj1goWJ1TOtU1ClglfOuaqFq\ncJuwHcT2OVsJ33SQEhUV0LpZEv7rfkkmYBrl13AnTMyN48CqYSIvGMN7p2tOW7PTMpWMTYZA9VpP\ncKaz4O0cZ+SFjZoknph1Ji/r85IJtNBmK+utqcT36n3b8DU9aPtqIGTgc1sKQxcBJGNgXdGxkEBo\nFkLbfdv3cuSqXkcJo4GTuD6zUr2adVnyb/S3BDG41eDJKorKaIf7bk9/G0j3iuTV345iJkvGgkIV\nZXvK6tZChm61pHWhuHblxdqs+zyox7ohqYsDjANlXYZXCWsHQelhuxDXtVaLUJLlKrOhDe5WwBE3\n3CCeXFHhPHioZihRrPwyBaSTaIC3Upj1k16+8nLH+rD1Y7yW5dacLEqzYmD/st9RmrHmzS5pLxcF\nCO5W2POnlmwtaiusWi8wttCaGqs95ynz1lrvXW+pBfZFqEbhcgK3OZoOy7ACIIC9SZPvcGNYwHYT\nK63gFWGTHyk4YEsc7LXYpksbNlQHm+KhFmLpa7s9750oz+My9nWiIALBfT5fLNQ2x+Qlty7wKvgu\nNx4nbb5cxp5BXp0F4O36KPySkb2bczrWJqYWC67+EBXO/qYuVh8jY4X6Fw/9JfBiSaWukEI3d4X2\nrd2cNu61WYYN+B8mCtTEvZL/8aryMXHvMXKF66q1yjExUvj24iv4zoSp6fVYwD4iV2WFHFaKWdLg\n2WZ/MLDs2jiz4TMKUDl4v7cyHaxvjquxJYi9M2wxGAT2Y7BVAlaJ2TPsZTSwvUHptG29UP0ObEiH\nL1KVZhvMpLWmJAtsM53FNkbfqJgGW+Miz9ew2xGJHVuc/eslWGmtt8nLo37QxV9AMvH+8JBxmVA8\nRGuNFfR610warGWGtSNnKA8EOpiM1Sujx1wVuq613wIxa+i2tmXZ+I8D9m3f9lOmDHG6h28JUy/O\noVq2emECgF1od8Emv6USOjqZh/IDTO2Ql8fVjwaWGsx0Yhrg8ObZKmr3Q0/uoIMGDVYXjaoDdF8B\nWLwvEALye3kNW9c3NBMAAp/M0v3ND4jV2u/18NpGdxqEosNWlExV7t+o2DEo2IUEGuy8lf6mY6iW\njm/qY2rjBJD488vVjuy583sXWBWfwKseBmlGzpRNfbYSlDgjFiJgXZbSHKqO6zqCxRC4RVpgLSYW\nLiSw/XUVjwFY3B0ITGCn0kjvMtWBWg4+GFl58JkhVSYd6Abtplyp1bRyCrBcy3MgmqCjyPYX8RzQ\nRykhrQLPWjd+5epUgsVbOCyR68QeYyT2in8h6lWbbdcsCHLVYBu84OqSeplSGYhuKAMnYiijUEqN\nZ9wNd+/QzVpXwWpb+OASrPzL+0a2+AYSVq1h59TLDe92lI0Ona3VslQ0OnHEqfrjwoE7GibWp4YP\nHH8m2FfApVz4ZkmKcrzN+6N+6oVZh9XKeHApjfjo2paIvWxLYY6tkxYb5hge1EsBccqRK1ldVDOU\nnSeKbDI2ES/nglBND1kLx3NFLHJp5r4Obi2Wn1G9+JcZ60rL8peFaiSHE/msWD3COpZdTCcl5SxF\nXTox8fhZidOkYV3XDBe+4btjPKHXSXBX1M/J1fDXN3HLQU7CEWrlRHKla543eSOS9OggLOfQAy1R\ncU8g+MYcYIkKzRlX5zHblQzf6JIl2zy+VqvRr95JyFvsUIhqWOmgldgLmaoGXx8laRO/zKK0Aitj\nFYE5UvJtlRJshLFW65glScLAXK4mhXd5m4tZFd9WXnXN3rQCXP5Z1VHWYIEqrrBrbKx67FqV/53A\npEKwyB1xKbJW4E7Hrhy2WywidTTnLbIgZSRmWSsQjK0TN4+1+ms55ovQSfk8wpmEIYM0Vg22uaI3\nKBHXKr7J2EB9tStTsbaSOz+V4nqvlzbPLd964s7O4As50yNW23Igt62Oam+YUa9MQlcIuo2+Rhox\nJxlrN17JTy1O5WVHVhR3eWslpyVSLleTMpPXIeGq+6vL2H19DeFQDSz3pz1hI68K7BjuWkz/K2Uv\naKBflmog6vIWZbIagDW7K045ICXzdqMlv2LjGpxMjFZe5TLVe71qpvzLwY2cSnbH95KM6Q+ocuLN\nGk6VeZAMthuZ71iJ2iPrSN3plDHk/51chMgF5nBilRKjwLW3csgFPAJdNzzKdEtr9A5CyWhLE9i6\nFmA6J8gOKTbzsnULkVt1Fm/kF9tgb68vLA/QpjwIVE6Rq+y+ZGDd3p2EtAFrbdTSQFpaLkjufqZK\n2c/f6PQB2VvQaziFl7qL3uUYp4QkKS1vznHpRXr8uosCHJAkVsNliUuV7YupYiIJ0Eb/9t2mGXSm\nqIoavJHzheQ8BKYSAou5pxSALPVKcE5Qfv1WL0NmVuMZWVRKUB2MFa19ekyEzl+xVQ9tLpOGejsW\n2UtN4ZuU9uWvz1tLYJbbUuK1d7kyHGwUMpYOkCoBrsRLGcTSQjCcx07seXYbT5E81wNDN+VSD7rq\n+Wgs2KFs2aVvq80vyuf1vttC99eTxAVDhyH4q34OrMAViVPmP4QAAwtpA7Ph4Huquxm9oQSOYLXn\nCHOBZUg5OzIuZkOafYaGyXqXlcbKaKNSKl3OUTzo7IO6VR7CqjWMJRgj5i5dXRft+y+dZWBDvngR\nTF1dVX9kFbB45hqOt5FLuoJwy+R9BVl/2Wz4D/qIp3v6DAyn0yt3GBUjB6s9V1tlYMnrKvlJgXlQ\ntsS1nosBAznZ6i2iVXb3yx3LyLoPFBpI34wwYH6wFg7rYsaS0zmvdBxYS8LouVuIjVzI/d1/T+t8\nhOl1CNs6R8MkPtpxyNtC+2ozHy9hFuprw4/hK/gOEI9HcJx4IqMn2D8XsQlgR+hauYeQp1SRELbb\nmFlbFXWXzh1M3F85RMA6Me/CXzNVi6VcUpHOGJC22+CvCawbYDa0go7VyCQjpvgMTLcE6dCZkrSf\nCOxgYqXXTeVSknYq+1rfF74044uHcVGwVlk3OzWv5Ltp9of5EsD2P4K004ydnNlF3p6c/JhCPhLY\nQY2L5+V1fr5gsySb6IcgJfWAbQgjuT4N2N4uVqiOYo42Y9HJXs+ucphQvd7N7pErDAivBTY3apFN\nhk15i7Uxps4XCElrod1H7Ub+BXVtGn6qdtFmP1fNUMZDjkzsRd3Gcxqb1ws+sEEuAWyUReWlfx8v\nMnsc7s+zV/3uqCCxFX4xbsMIUG5Pj6E9CKybkfwmRxgrSxjy18TjKwqJWjKIbbf9OfhB5BRjmSqi\nO8MdUf6ajh82LkRhK5g/0pguLd33zs01zti4SR2yDQBbPBdnSfv0nlgP5oGBPtCFTt5awtSRT3ZP\n+33Jw1MN0J7xqUJqpZ0BGgTWZV1O5ngGUWEKWLyD/is8hu2oa3u4kH0DQILb1E8Xwesc+Yyn1FAr\nUGpMGjJ9abwcIr8eoEihsj/lapjRAHVe+0BpEutWy+J6FW/fc/1c4wMAlYb/jIxKWNluBnBUdunZ\n/PSlgVggkE7udK0CItyCdTiQxS1BD1N1tXzDTrnAIxC1RzG/BDid3fa7MVy+ikxGOXlWRf0Y/nIH\nKHpmLbnT2DBtpDtOY7icD7/kKg85Y1Ufuya7hCENEOmUjMwFSXsKb9UeciUHRkiYj6ZtQPScADtY\nnbZXHyPtRYaUL26NEdd8GazEGNEGDHXBiDu+/tDc2sU1TP5EMkeoiutM65mIrwPvA9jxxIs8QXAs\nCOafwIFt3V/Q4g17bHd+hFlpi09zd4EqqIoNosmNgimNxfmF4QsNPhRYvNRyW0VoW/ao4iSW/66u\ndI0CXo23n/zXrWj9qoxVG4wh+pS7mfVG3l3pBpUsKcdJYyJVdlVFxFgr/KO38rDmkDy0GzZScg4x\n1gssNGbLn23Mlwr2voKEfnaHvCWSmz3hs3qIgq2IG5NvEP0sYN1eqH+/mTM1KrGLjH2XN3O4Jsmb\no81HS+7YZJ5YAnMbS2L1covq/W1HPtlComwmNV7bYzmjNdHwgyxHHbfR5XAD7F9ZSDsA4rbEDYNm\nwK5tMDXBzdpjxUkWi143vO+HlTTT5gPztu/lGcTqWPAIP+tBQHdCunaB7TolDwF7uJwXhQOumWZY\nPtQX0DW0/E8C296g89hexeGjwJ4HpDH/dDnZPgf7kkbeATcyD1Fp88DyLSWHacBqRjt9M08YuHjK\nA+JdTb67NFAPI39XubKwCwQMkFXGFngQWAfAinVGbxSgHdXtYR9rhVB7Pl13VXqsHWVnFIgkMCwf\nqE4zjVw8E9iyyb0eqPmzgFWYnGeILuTYLlnm/NuRgzwfBmxuoMMzL1HVr24WW8JCV2rcyQk4Cmm1\nwglrDK4a/kqkHT/c0ehBRryp4Nt5puTAe3NbMGPXr2c5hCgY6aBi7IEAvQKMdfHPC+FTY7CT57k+\nnsyzH/E5SY9LbH2LNJufac+rgR0c0GatOnpihR3ohwthC+TEcHwsaQ+c8zqsFTgy9rTj+yOxOgDs\nYAeZb9mCIvhJ2cajlPnQKA320wHMgR3sFHhy4HjgUm76lXSdXhp8eY+MAnawj00FjV0h0ToaGuxW\nL7myWTqo1qsPeME1kou13HyZ7yY48LDUr9W7vWPolCV/irGFafjglJjNg616bglLdU22ctJNkoMj\nnuOc/5S0Odt4rAxouYE6N0xJt45jHFO2OCBGJbA3U/sLum6prqGP7YqqtOinKOb5NwTWA4rtJ45V\nOcZW+f46bhRvXNhZ0Dl0cPjviJE2GiYVM3XOS+IzAYs5hdRB1QzQZi5dPOwp+7BwwOd14JyX8neH\neAYlqHE543f4NGATUSk9zj4yXsbsFbbAmkIUpLMvy3g7h34dLn/oQ2k1CB/TiJB8/7WMu1Ndxpr7\nJ884dueQH0E4fsuO7Svwp3UPWLWtYo5vUg/59ryIdVY+d5LJ9Ju6zBC3cdZlY6k9k/Du5BXjj3rD\nSdnLiKtZ23AMkq22X+1u8nU8XgVE/6p4ilnpxviFL42sDW6Qyuum2cBuEWhfR5ILW1G76o7ckuTk\nks4XJ1RYwrVSxbatBVTVAysCf61hNkmKf+tKRKWJnpMqsI2Tlz/cTvViNv2cRlwsYSRdsRWBX59o\nWYRBZ66iUiZqtHSyf13Y3StMwVtck77Q/ZBsLN3kdfSLvLi+rNZ3YBWY7jb426gzUTCTH7ob6S2x\niaJlRE4aXlxCWr5FJF/Ixu6yGjFlf6y61E7jXGUIG2H8ZteU6lacUWtJ5SobBjdzhutAwFuprjC1\nMHJT7VWoeOZkMLlzSrGNUzmU337yACEIG8g7HQ1IwixXibRn8IxIeDjB4Puy1C/QgVd3u/taflyD\nTXbdjdw5vRVP7MbjIqyOcXUz3+/ovjjWDrnIrf6Kyav3YS/I6vZ1JLY/5/Rj3o4kjmWjklGqDecp\nKuRbXOBJua3KKV3grfJMy4i3ZabeTwWaLyLd5b53sCViXQFQlXPS1bjKLxmhDUpUIjF/88tWGjFW\naQIuY4WATeWkKzm7GcVoEhVRiBbamGHPTVhLMN278ZeQlqsAYy1LrQCxp7+XesA2lLR0ifRCS4Ol\nfjKDk935E41HNQE7gycJFlIVIlnqlsNZNnyZwtUKxshmJeexsNI17EswR1xC1KlkLCQTVkfvAgec\n8xIiLlA+mX4rLWAVaV1twZKTBfIlt8KiYYN6yCq/N7GZ6/oX+b0JK6LdlVdjbF0WRaKVh4bHFHqs\nEpJq6Eek93mKRnRKuMopbS79wqai2MazEWahjZptg/H7L0hFpmLBhFOym2+tS2B1UoBPzIEhZxZW\nW/0sFxOGLU6rmQQ38z1QFyj3u4rR5xSREmKzfPAX5FG2+mYCfYatumbu9StC1hXVbsOjBKyDtX95\nOhs0qLKNpVKITSLa0MqlkSsfZhYAtQRXYVjwRcm09sR4LdW6ZRml4FIHCcfj7U/NVDULqUmM+Q7A\najxtgRW0Mqz7ys4rHNzY8HVI22BOX1I+yy/zBb7nFeHTBTBK6f69xrFSDbYQShub9A2eTLO9DVWy\nZbXUz0ttfEoocJTsoqCu33dRcPq7fgld+e8FkBJDFramIt71ZUgG3uv3zkpMUhGMDNuYt52tu9d+\njJKZicESjqoc22GPFU+g43n3pYdieFpRm4CGh7v0GloBTAEuVph9IrGg+Ckw7LYqclGlxCsybeFv\nkOXeVfvF1Wd8y84tqbMkT/ROLdwA1PIESnlpI8We7SpJGL0a2ky+I+oWJl9o75s8vKMWwgCnBemB\njWpvc9yzIqvLqUr5LwZC6LG3+L4Cfk7+qsSSABeG7irg8ryzueyFZkOUS99oO6wjbBzJuytIM/f8\nI7AtcSrXbJb2Bbqxgzy8RLqcgXnVLYbW1EcKlNaVtmi9tDtNFJBfyZVRayxSVKsOtNAt2Q2Fpcds\n4FhoT1HdLmnH++ieQTgjwV4aPl60Hg4W2Fe/5meEyTF/TWKvHsl7YEQe78XUcaQ/N3wYXTEFTJ2l\nPVVjKpwHw2HGWlH5uhpLXbNnaT8lHODbuN17pKipQspKxIqCV+jz58NG23gOZD8mKktdxeI9m/dP\nYewZ0p6s+lhRfwSwWHAdE5KQsQdQ7eaK3m5XKzhgIXldgGPocHb1MJ6FGzCF6uqeTHQtda8L0i1l\nLQZs1FKGHZ3R/ksm8ea7qSlRPv4FXEvdQs/qhLWeOXbLGjl1auZzAhxPrruwF3jv1mgHCSIxmoP0\neIbPZ2yvj8WjGprxE19MZV2B1PEadHsnTeI6vWtXr/9mp2Y+HdUS6gaGzT10k3oNtnpWQmyQGK66\n5Gr+xxwWmSBjrG1z0qrxBgdDPASsymgdOrfvLEvbriH2FlmSq5j6dhSq71UbNFbeY2Aa0GesN7mI\nxFGW6G+PZioUu/FSPy4D0Yfn/YGoVeRqgRRu2fJQZnk44ttunLqvVbwd1QMu3LvgP6Vcq2Uy8nl9\nwcA9gs8UICy8E6bCxcj8dm4+c32rG3jWsmFDpcSWA0qpgHVrBLBoW8l+jSg4LI27BUoMMUc3xnIk\n8dCSijc1NWBl4dj+YYHdf72Rt7d+m6zIBMVt9nl9heWAUr93RfTNOTDoaPIycXswu14hbFGIFpKy\nFq56UXYV8u80VnhGmE/H1q29SNpOxpt2rIvN2z3j0mIOZnYt5Alu41+g+9zwkUvaS4IrChbrBfs8\n8I9Z7RCgHhwD9vAe/rZhw7i5xfxlFa3Lg9LH5Jvbb4MXuZKE5DMjTqVek/zax/rifhKlTKDtJ3ou\nWxFe9VwrKjGIV//mLYQa6ZY82KSSXmVXDdDtkTH/ByrXy2U=\n', (115, 260), None)""" +## re.sub(SQSTRING_RGX, '', crash_str) +## re.sub(TQSTRING_RGX, '', crash_str) +## re.sub(SASTRING_RGX, '', crash_str) +## re.sub(TASTRING_RGX, '', crash_str) + + +if linesep != '\n': + LINE_RGX = re.compile(linesep) + def ulines(strings): + return strings[0], LINE_RGX.sub('\n', strings[1]) +else: + def ulines(strings): + return strings + +class ChecklineFunctionTest(unittest.TestCase): + """test the check_line method""" + + def test_known_values_opspace_1(self): + self.assertEqual(ulines(check_line('a=1', REPORTER)), ('C0322', 'a=1\n ^')) + + def test_known_values_opspace_2(self): + self.assertEqual(ulines(check_line('a= 1', REPORTER)), ('C0322', 'a= 1\n ^') ) + + def test_known_values_opspace_3(self): + self.assertEqual(ulines(check_line('a =1', REPORTER)), ('C0323', 'a =1\n ^')) + + def test_known_values_opspace_4(self): + self.assertEqual(check_line('f(a=1)', REPORTER), None) + + def test_known_values_opspace_4(self): + self.assertEqual(check_line('f(a=1)', REPORTER), None) + + +## def test_known_values_colonnl_1(self): +## self.assertEqual(check_line('if a: a = 1', REPORTER), +## ('W0321', 'if a: a = 1\n ^^^^^^^')) + +## def test_known_values_colonnl_2(self): +## self.assertEqual(check_line('a[:1]', REPORTER), None) + +## def test_known_values_colonnl_3(self): +## self.assertEqual(check_line('a[1:]', REPORTER), None) + +## def test_known_values_colonnl_4(self): +## self.assertEqual(check_line('a[1:2]', REPORTER), None) + +## def test_known_values_colonnl_5(self): +## self.assertEqual(check_line('def intersection(list1, list2):', REPORTER), None) + +## def test_known_values_colonnl_6(self): +## self.assertEqual(check_line('def intersection(list1, list2):\n', REPORTER), None) + +## def test_known_values_colonnl_7(self): +## self.assertEqual(check_line('if file[:pfx_len] == path:\n', REPORTER), None) + +## def test_known_values_colonnl_8(self): +## self.assertEqual(check_line('def intersection(list1, list2): pass\n', REPORTER), +## ('W0321', +## 'def intersection(list1, list2): pass\n ^^^^^^') ) + +## def test_known_values_colonnl_9(self): +## self.assertEqual(check_line('if file[:pfx_len[1]] == path:\n', REPORTER), None) + +## def test_known_values_colonnl_10(self): +## self.assertEqual(check_line('if file[pfx_len[1]] == path:\n', REPORTER), None) + + + def test_known_values_commaspace_1(self): + self.assertEqual(ulines(check_line('a, b = 1,2', REPORTER)), + ('C0324', 'a, b = 1,2\n ^^')) + + + def test_known_values_instring_1(self): + self.assertEqual(check_line('f("a=1")', REPORTER), None) + + def test_known_values_instring_2(self): + self.assertEqual(ulines(check_line('print >>1, ("a:1")', REPORTER)), + ('C0323', 'print >>1, ("a:1")\n ^')) + + def test_known_values_all_1(self): + self.assertEqual(ulines(check_line("self.filterFunc = eval('lambda %s: %s'%(','.join(variables),formula),{},{})", REPORTER)), + ('C0324', "self.filterFunc = eval('lambda %s: %s'%(','.join(variables),formula),{},{})\n ^^")) + + def test_known_values_tqstring(self): + self.assertEqual(check_line('print """<a="=")', REPORTER), None) + + def test_known_values_tastring(self): + self.assertEqual(check_line("print '''<a='=')", REPORTER), None) + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_import_graph.py b/test/test_import_graph.py new file mode 100644 index 0000000..2d2bf02 --- /dev/null +++ b/test/test_import_graph.py @@ -0,0 +1,62 @@ +import sys +import os +import unittest +from os.path import exists +from cStringIO import StringIO + +from pylint.checkers import initialize, imports +from pylint.lint import PyLinter + +from utils import TestReporter + +class DependenciesGraphTC(unittest.TestCase): + """test the imports graph function""" + + dest = 'dependencies_graph.dot' + def tearDown(self): + os.remove(self.dest) + + def test_dependencies_graph(self): + imports.dependencies_graph(self.dest, {'labas': ['hoho', 'yep'], + 'hoho': ['yep']}) + self.assertEquals(open(self.dest).read().strip(), + ''' +digraph g { +rankdir="LR" URL="." concentrate=false +edge[fontsize="10" ] +node[width="0" height="0" fontsize="12" fontcolor="black"] +"hoho" [ label="hoho" ]; +"yep" [ label="yep" ]; +"labas" [ label="labas" ]; +"yep" -> "hoho" [ ] ; +"hoho" -> "labas" [ ] ; +"yep" -> "labas" [ ] ; +} +'''.strip()) + +class ImportCheckerTC(unittest.TestCase): + def setUp(self): + self.linter = l = PyLinter(reporter=TestReporter()) + initialize(l) + + def test_checker_dep_graphs(self): + l = self.linter + l.global_set_option('persistent', False) + l.global_set_option('enable-checker', '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') + try: + l.check('input') + self.assert_(exists('import.dot')) + self.assert_(exists('ext_import.dot')) + self.assert_(exists('int_import.dot')) + finally: + for fname in ('import.dot', 'ext_import.dot', 'int_import.dot'): + try: + os.remove(fname) + except: + pass + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_rpycompilation.py b/test/test_rpycompilation.py new file mode 100644 index 0000000..dd5929a --- /dev/null +++ b/test/test_rpycompilation.py @@ -0,0 +1,50 @@ +from os.path import isfile, splitext, basename +from os import system, remove +from glob import glob + +from logilab.common.testlib import TestCase, unittest_main + +class RPyCompilation(TestCase): + + def setUp(self): + self.trscript = self.find_pypy() + + def find_pypy(self): + #trscript = '/home/adim/local/svn/pypy-dist/pypy/translator/goal/translate.py' + trscript = '/home/syt/cvs_work/pypy-dist/pypy/translator/goal/translate.py' + if not isfile(trscript): + self.skip('translate.py not found') + return trscript + + def _compile_fail(self, filename): + status = system('%s --batch %s' % (self.trscript, filename)) + try: + self.assertNotEquals(status, 0, "%s translation succeed !!" % filename) + except AssertionError: + exefile = '%s-c' % splitext(basename(filename))[0] + status = system('./%s' % exefile) + remove(exefile) + self.assertNotEquals(status, 0, "%s run succeed !!" % exefile) + + def _compile_success(self, filename): + status = system('%s --batch %s' % (self.trscript, filename)) + self.assertEquals(status, 0, "%s translation failed !!" % filename) + exefile = '%s-c' % splitext(basename(filename))[0] + status = system('./%s' % exefile) + remove(exefile) + self.assertEquals(status, 0, "%s run failed !!" % exefile) + + + def test_translations(self): + for filename in glob('rpythoninput/func_*.py'): + if filename.startswith('rpythoninput/func_noerror'): + yield self._compile_success, filename + else: + yield self._compile_fail, filename + + +if __name__ == '__main__': + import sys + if not '-cc' in sys.argv: + sys.argv.append('-cc') + unittest_main() diff --git a/test/test_similar.py b/test/test_similar.py new file mode 100644 index 0000000..457fafa --- /dev/null +++ b/test/test_similar.py @@ -0,0 +1,56 @@ +import sys +import unittest +from cStringIO import StringIO + +from pylint.checkers import similar + + +class SimilarTC(unittest.TestCase): + """test the similar command line utility""" + def test(self): + sys.stdout = StringIO() + try: + similar.run(['--ignore-comments', 'input/similar1', 'input/similar2']) + output = sys.stdout.getvalue() + finally: + sys.stdout = sys.__stdout__ + self.assertEquals(output.strip(), """ +7 similar lines in 2 files +==input/similar1:5 +==input/similar2:5 + same file as this one. + more than 4 + identical lines should + be + detected + + +TOTAL lines=38 duplicates=7 percent=0.184210526316 +""".strip()) + + def test_help(self): + sys.stdout = StringIO() + try: + try: + similar.run(['--help']) + except SystemExit, ex: + self.assertEquals(ex.code, 0) + else: + self.fail() + finally: + sys.stdout = sys.__stdout__ + + def test_no_args(self): + sys.stdout = StringIO() + try: + try: + similar.run([]) + except SystemExit, ex: + self.assertEquals(ex.code, 1) + else: + self.fail() + finally: + sys.stdout = sys.__stdout__ + +if __name__ == '__main__': + unittest.main() diff --git a/test/unittest_checkers_utils.py b/test/unittest_checkers_utils.py new file mode 100644 index 0000000..380b4cd --- /dev/null +++ b/test/unittest_checkers_utils.py @@ -0,0 +1,49 @@ +# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""test the pylint.checkers.utils module +""" + +__revision__ = '$Id: unittest_checkers_utils.py,v 1.6 2005-11-02 09:22:07 syt Exp $' + +import unittest +import sys + +from pylint.checkers import utils +try: + __builtins__.mybuiltin = 2 +except AttributeError: + __builtins__['mybuiltin'] = 2 + +class UtilsTC(unittest.TestCase): + +## def test_is_native_builtin(self): +## self.assertEquals(utils.is_native_builtin('min'), True) +## self.assertEquals(utils.is_native_builtin('__path__'), True) +## self.assertEquals(utils.is_native_builtin('__file__'), True) +## self.assertEquals(utils.is_native_builtin('whatever'), False) +## self.assertEquals(utils.is_native_builtin('mybuiltin'), False) + + def test_is_builtin(self): + self.assertEquals(utils.is_builtin('min'), True) + self.assertEquals(utils.is_builtin('__builtins__'), True) + self.assertEquals(utils.is_builtin('__path__'), False) + self.assertEquals(utils.is_builtin('__file__'), False) + self.assertEquals(utils.is_builtin('whatever'), False) + self.assertEquals(utils.is_builtin('mybuiltin'), False) + +if __name__ == '__main__': + unittest.main() + diff --git a/test/unittest_lint.py b/test/unittest_lint.py new file mode 100644 index 0000000..ea111d8 --- /dev/null +++ b/test/unittest_lint.py @@ -0,0 +1,316 @@ +# Copyright (c) 2003-2007 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import shutil +import sys +import os +import tempfile +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 sorted + +from pylint.config import get_note_message +from pylint.lint import PyLinter, Run, sort_checkers, UnknownMessage +from pylint.utils import sort_msgs +from pylint import checkers + +class SortMessagesTC(TestCase): + + def test(self): + l = ['E0501', 'E0503', 'F0002', 'I0201', 'W0540', + 'R0202', 'F0203', 'R0220', 'W0321', 'I0001'] + self.assertEquals(sort_msgs(l), ['E0501', 'E0503', + 'W0321', 'W0540', + 'R0202', 'R0220', + 'I0001', 'I0201', + 'F0002', 'F0203',]) + +try: + optimized = True + raise AssertionError +except AssertionError: + optimized = False + +class GetNoteMessageTC(TestCase): + def test(self): + msg = None + for note in range(-1, 11): + note_msg = get_note_message(note) + self.assertNotEquals(msg, note_msg) + msg = note_msg + if optimized: + self.assertRaises(AssertionError, get_note_message, 11) + + +HERE = abspath(dirname(__file__)) +INPUTDIR = join(HERE, 'input') + +class RunTC(TestCase): + + def _test_run(self, args, exit_code=1, no_exit_fail=True): + sys.stdout = StringIO() + sys.sterr = StringIO() + try: + try: + Run(args) + except SystemExit, ex: + self.assertEquals(ex.code, exit_code) + else: + if no_exit_fail: + self.fail() + finally: + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + + def test_no_args(self): + self._test_run([], 1) + + def test_no_ext_file(self): + self._test_run([join(INPUTDIR, 'noext')], no_exit_fail=False) + + +class PyLinterTC(TestCase): + + def setUp(self): + self.linter = PyLinter() + self.linter.disable_message_category('I') + self.linter.config.persistent = 0 + # register checkers + checkers.initialize(self.linter) + + def test_message_help(self): + msg = self.linter.get_message_help('F0001', checkerref=True) + expected = ':F0001:\n Used when an error occured preventing the analysis of a module (unable to find\n it for instance). This message belongs to the master checker.' + self.assertTextEqual(msg, expected) + self.assertRaises(UnknownMessage, self.linter.get_message_help, 'YB12') + + def test_enable_message(self): + linter = self.linter + linter.open() + linter.set_current_module('toto') + self.assert_(linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('W0102')) + linter.disable_message('W0101', scope='package') + linter.disable_message('W0102', scope='module', line=1) + self.assert_(not linter.is_message_enabled('W0101')) + self.assert_(not linter.is_message_enabled('W0102', 1)) + linter.set_current_module('tutu') + self.assert_(not linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('W0102')) + linter.enable_message('W0101', scope='package') + linter.enable_message('W0102', scope='module', line=1) + self.assert_(linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('W0102', 1)) + + def test_enable_message_category(self): + linter = self.linter + linter.open() + linter.set_current_module('toto') + self.assert_(linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('R0102')) + linter.disable_message_category('W', scope='package') + linter.disable_message_category('REFACTOR', scope='module') + self.assert_(not linter.is_message_enabled('W0101')) + self.assert_(not linter.is_message_enabled('R0102')) + linter.set_current_module('tutu') + self.assert_(not linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('R0102')) + linter.enable_message_category('WARNING', scope='package') + linter.enable_message_category('R', scope='module') + self.assert_(linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('R0102')) + + def test_enable_message_block(self): + linter = self.linter + linter.open() + filepath = join(INPUTDIR, 'func_block_disable_msg.py') + linter.set_current_module('func_block_disable_msg') + linter.process_module(open(filepath)) + orig_state = linter._module_msgs_state.copy() + linter._module_msgs_state = {} + linter.collect_block_lines(linter.get_astng(filepath, 'func_block_disable_msg'), orig_state) + # global (module level) + self.assert_(linter.is_message_enabled('W0613')) + self.assert_(linter.is_message_enabled('E1101')) + # meth1 + self.assert_(linter.is_message_enabled('W0613', 13)) + # meth2 + self.assert_(not linter.is_message_enabled('W0613', 18)) + # meth3 + self.assert_(not linter.is_message_enabled('E1101', 24)) + self.assert_(linter.is_message_enabled('E1101', 26)) + # meth4 + self.assert_(not linter.is_message_enabled('E1101', 32)) + self.assert_(linter.is_message_enabled('E1101', 36)) + # meth5 + self.assert_(not linter.is_message_enabled('E1101', 42)) + self.assert_(not linter.is_message_enabled('E1101', 43)) + self.assert_(linter.is_message_enabled('E1101', 46)) + self.assert_(not linter.is_message_enabled('E1101', 49)) + self.assert_(not linter.is_message_enabled('E1101', 51)) + # meth6 + self.assert_(not linter.is_message_enabled('E1101', 57)) + self.assert_(linter.is_message_enabled('E1101', 61)) + self.assert_(not linter.is_message_enabled('E1101', 64)) + self.assert_(not linter.is_message_enabled('E1101', 66)) + + self.assert_(linter.is_message_enabled('E0602', 57)) + self.assert_(linter.is_message_enabled('E0602', 61)) + self.assert_(not linter.is_message_enabled('E0602', 62)) + self.assert_(linter.is_message_enabled('E0602', 64)) + self.assert_(linter.is_message_enabled('E0602', 66)) + # meth7 + self.assert_(not linter.is_message_enabled('E1101', 70)) + self.assert_(linter.is_message_enabled('E1101', 72)) + self.assert_(linter.is_message_enabled('E1101', 75)) + self.assert_(linter.is_message_enabled('E1101', 77)) + + def test_list_messages(self): + sys.stdout = StringIO() + try: + # just invoke it, don't check the output + self.linter.list_messages() + finally: + sys.stdout = sys.__stdout__ + + def test_lint_ext_module_with_file_output(self): + self.linter.config.files_output = True + try: + self.linter.check('StringIO') + self.assert_(os.path.exists('pylint_StringIO.txt')) + self.assert_(os.path.exists('pylint_global.txt')) + finally: + try: + os.remove('pylint_StringIO.txt') + os.remove('pylint_global.txt') + except: + pass + + def test_enable_report(self): + self.assertEquals(self.linter.is_report_enabled('R0001'), True) + self.linter.disable_report('R0001') + self.assertEquals(self.linter.is_report_enabled('R0001'), False) + self.linter.enable_report('R0001') + self.assertEquals(self.linter.is_report_enabled('R0001'), True) + + def test_set_option_1(self): + linter = self.linter + linter.set_option('disable-msg', 'C0111,W0142') + self.assert_(not linter.is_message_enabled('C0111')) + self.assert_(not linter.is_message_enabled('W0142')) + self.assert_(linter.is_message_enabled('W0113')) + + def test_set_option_2(self): + linter = self.linter + linter.set_option('disable-msg', ('C0111', 'W0142') ) + self.assert_(not linter.is_message_enabled('C0111')) + self.assert_(not linter.is_message_enabled('W0142')) + self.assert_(linter.is_message_enabled('W0113')) + + def test_enable_checkers1(self): + self.linter.enable_checkers(['design'], False) + self.assertEquals(sorted([c.name for c in self.linter._checkers.values() + if c.is_enabled()]), + ['basic', 'classes', 'exceptions', 'format', 'imports', + 'master', 'metrics', 'miscellaneous', 'newstyle', + 'similarities', 'typecheck', 'variables']) + + def test_enable_checkers2(self): + self.linter.enable_checkers(['design'], True) + self.assertEquals(sorted([c.name for c in self.linter._checkers.values() + if c.is_enabled()]), + ['design', 'master']) + +from pylint import config + +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.assertEquals(config.PYLINT_HOME, expected) + + try: + pylintd = join(tempfile.gettempdir(), '.pylint.d') + os.environ['PYLINTHOME'] = pylintd + try: + reload(config) + self.assertEquals(config.PYLINT_HOME, pylintd) + finally: + try: + os.remove(pylintd) + except: + pass + finally: + del os.environ['PYLINTHOME'] + + def test_pylintrc(self): + try: + self.assertEquals(config.find_pylintrc(), None) + os.environ['PYLINTRC'] = join(tempfile.gettempdir(), '.pylintrc') + self.assertEquals(config.find_pylintrc(), None) + os.environ['PYLINTRC'] = '.' + self.assertEquals(config.find_pylintrc(), None) + finally: + os.environ.pop('PYLINTRC', '') + reload(config) + + def test_pylintrc_parentdir(self): + chroot = tempfile.mkdtemp() + 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) + self.assertEquals(config.find_pylintrc(), None) + 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.assertEquals(config.find_pylintrc(), expected) + finally: + os.chdir(HERE) + shutil.rmtree(chroot) + + + def test_pylintrc_parentdir_no_package(self): + chroot = tempfile.mkdtemp() + try: + create_files(['a/pylintrc', 'a/b/pylintrc', 'a/b/c/d/__init__.py'], chroot) + os.chdir(chroot) + self.assertEquals(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.assertEquals(config.find_pylintrc(), expected) + finally: + os.chdir(HERE) + shutil.rmtree(chroot) + +if __name__ == '__main__': + unittest_main() diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..022807c --- /dev/null +++ b/utils.py @@ -0,0 +1,387 @@ +# Copyright (c) 2003-2008 Sylvain Thenault (thenault@gmail.com). +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""some various utilities and helper classes, most of them used in the +main pylint class +""" + +import sys +from os import linesep + +from logilab.common.textutils import normalize_text +from logilab.common.configuration import rest_format_section +from logilab.common.ureports import Section + +from logilab.astng import Module + +from pylint.checkers import EmptyReport + +class UnknownMessage(Exception): + """raised when a unregistered message id is encountered""" + + +MSG_TYPES = { + 'I' : 'info', + 'C' : 'convention', + 'R' : 'refactor', + 'W' : 'warning', + 'E' : 'error', + 'F' : 'fatal' + } +MSG_CATEGORIES = MSG_TYPES.keys() + + +def sort_checkers(checkers, enabled_only=True): + """return a list of enabled checker sorted by priority""" + if enabled_only: + checkers = [(-checker.priority, checker) for checker in checkers + if checker.is_enabled()] + else: + checkers = [(-checker.priority, checker) for checker in checkers] + checkers.sort() + return [item[1] for item in checkers] + +def sort_msgs(msg_ids): + """sort message identifiers according to their category first""" + msg_order = ['E', 'W', 'R', 'C', 'I', 'F'] + def cmp_func(msgid1, msgid2): + """comparison function for two message identifiers""" + if msgid1[0] != msgid2[0]: + return cmp(msg_order.index(msgid1[0]), msg_order.index(msgid2[0])) + else: + return cmp(msgid1, msgid2) + msg_ids.sort(cmp_func) + return msg_ids + +def get_module_and_frameid(node): + """return the module name and the frame id in the module""" + frame = node.frame() + module, obj = '', [] + while frame: + if isinstance(frame, Module): + module = frame.name + else: + obj.append(getattr(frame, 'name', '<lambda>')) + try: + frame = frame.parent.frame() + except AttributeError: + frame = None + obj.reverse() + return module, '.'.join(obj) + + +class Message: + def __init__(self, checker, msgid, msg, descr): + assert len(msgid) == 5, 'Invalid message id %s' % msgid + assert msgid[0] in MSG_CATEGORIES, \ + 'Bad message type %s in %r' % (msgid[0], msgid) + self.msgid = msgid + self.msg = msg + self.descr = descr + self.checker = checker + +class MessagesHandlerMixIn: + """a mix-in class containing all the messages related methods for the main + lint class + """ + + def __init__(self): + # dictionary of registered messages + self._messages = {} + self._msgs_state = {} + self._module_msgs_state = {} # None + self._msg_cats_state = {} + self._module_msg_cats_state = None + + def register_messages(self, checker): + """register a dictionary of messages + + Keys are message ids, values are a 2-uple with the message type and the + message itself + + message ids should be a string of len 4, where the to first characters + are the checker id and the two last the message id in this checker + """ + msgs_dict = checker.msgs + chkid = None + for msgid, (msg, msgdescr) in msgs_dict.items(): + # avoid duplicate / malformed ids + assert not self._messages.has_key(msgid), \ + 'Message id %r is already defined' % msgid + assert chkid is None or chkid == msgid[1:3], \ + 'Inconsistent checker part in message id %r' % msgid + chkid = msgid[1:3] + self._messages[msgid] = Message(checker, msgid, msg, msgdescr) + + def get_message_help(self, msg_id, checkerref=False): + """return the help string for the given message id""" + msg = self.check_message_id(msg_id) + desc = normalize_text(' '.join(msg.descr.split()), indent=' ') + if checkerref: + desc += ' This message belongs to the %s checker.' % \ + msg.checker.name + title = msg.msg + if title != '%s': + title = title.splitlines()[0] + return ':%s: *%s*\n%s' % (msg.msgid, title, desc) + return ':%s:\n%s' % (msg.msgid, desc) + + def disable_message(self, msg_id, scope='package', line=None): + """don't output message of the given id""" + assert scope in ('package', 'module') + msg = self.check_message_id(msg_id) + 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 msg_id != '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.items() + if not val] + + def enable_message(self, msg_id, scope='package', line=None): + """reenable message of the given id""" + assert scope in ('package', 'module') + msg = self.check_message_id(msg_id) + msg.checker.enabled = True # ensure the related checker is enabled + 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_msg = [mid for mid, val in msgs.items() if val] + + def disable_message_category(self, msg_cat_id, scope='package', line=None): + """don't output message in the given category""" + assert scope in ('package', 'module') + msg_cat_id = msg_cat_id[0].upper() + if scope == 'module': + self.add_message('I0011', line=line, args=msg_cat_id) + self._module_msg_cats_state[msg_cat_id] = False + else: + self._msg_cats_state[msg_cat_id] = False + + def enable_message_category(self, msg_cat_id, scope='package', line=None): + """reenable message of the given category""" + assert scope in ('package', 'module') + msg_cat_id = msg_cat_id[0].upper() + if scope == 'module': + self.add_message('I0012', line=line, args=msg_cat_id) + self._module_msg_cats_state[msg_cat_id] = True + else: + self._msg_cats_state[msg_cat_id] = True + + def check_message_id(self, msg_id): + """raise UnknownMessage if the message id is not defined""" + msg_id = msg_id.upper() + try: + return self._messages[msg_id] + except KeyError: + raise UnknownMessage('No such message id %s' % msg_id) + + def is_message_enabled(self, msg_id, line=None): + """return true if the message associated to the given message id is + enabled + """ + try: + if not self._module_msg_cats_state[msg_id[0]]: + return False + except (KeyError, TypeError): + if not self._msg_cats_state.get(msg_id[0], True): + return False + if line is None: + return self._msgs_state.get(msg_id, True) + try: + return self._module_msgs_state[msg_id][line] + except (KeyError, TypeError): + return self._msgs_state.get(msg_id, True) + + def add_message(self, msg_id, line=None, node=None, args=None): + """add the message corresponding to the given id. + + If provided, msg is expanded using args + + astng checkers should provide the node argument, raw checkers should + provide the line argument. + """ + if line is None and node is not None: + line = node.fromlineno#lineno or node.statement().lineno + #if not isinstance(node, Module): + # assert line > 0, node.__class__ + # should this message be displayed + if not self.is_message_enabled(msg_id, line): + return + # update stats + msg_cat = MSG_TYPES[msg_id[0]] + self.stats[msg_cat] += 1 + self.stats['by_module'][self.current_name][msg_cat] += 1 + try: + self.stats['by_msg'][msg_id] += 1 + except KeyError: + self.stats['by_msg'][msg_id] = 1 + msg = self._messages[msg_id].msg + # expand message ? + if args: + msg %= args + # get module and object + if node is None: + module, obj = self.current_name, '' + path = self.current_file + else: + module, obj = get_module_and_frameid(node) + path = node.root().file + # add the message + self.reporter.add_message(msg_id, (path, module, obj, line or 1), msg) + + def help_message(self, msgids): + """display help messages for the given message identifiers""" + for msg_id in msgids: + try: + print self.get_message_help(msg_id, True) + print + except UnknownMessage, ex: + print ex + print + continue + + def list_messages(self): + """output a full documentation in ReST format""" + for checker in sort_checkers(self._checkers.values()): + if checker.name == 'master': + prefix = 'Main ' + 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: + prefix = '' + title = '%s checker' % checker.name.capitalize() + print title + print '-' * len(title) + if checker.__doc__: # __doc__ is None with -OO + print linesep.join([l.strip() + for l in checker.__doc__.splitlines()]) + if checker.options: + title = 'Options' + print title + print '~' * len(title) + for section, options in checker.options_by_section(): + rest_format_section(sys.stdout, section, options) + print + if checker.msgs: + title = ('%smessages' % prefix).capitalize() + print title + print '~' * len(title) + for msg_id in sort_msgs(checker.msgs.keys()): + print self.get_message_help(msg_id, False) + print + if getattr(checker, 'reports', None): + title = ('%sreports' % prefix).capitalize() + print title + print '~' * len(title) + for report in checker.reports: + print ':%s: %s' % report[:2] + print + print + + +class ReportsHandlerMixIn: + """a mix-in class containing all the reports and stats manipulation + related methods for the main lint class + """ + def __init__(self): + self._reports = {} + self._reports_state = {} + + def register_report(self, r_id, r_title, r_cb, checker): + """register a report + + r_id is the unique identifier for the report + r_title the report's title + r_cb the method to call to make the report + checker is the checker defining the report + """ + r_id = r_id.upper() + self._reports.setdefault(checker, []).append( (r_id, r_title, r_cb) ) + + def enable_report(self, r_id): + """disable the report of the given id""" + r_id = r_id.upper() + self._reports_state[r_id] = True + + def disable_report(self, r_id): + """disable the report of the given id""" + r_id = r_id.upper() + self._reports_state[r_id] = False + + def is_report_enabled(self, r_id): + """return true if the report associated to the given identifier is + enabled + """ + return self._reports_state.get(r_id, True) + + def make_reports(self, stats, old_stats): + """render registered reports""" + if self.config.files_output: + filename = 'pylint_global.' + self.reporter.extension + self.reporter.set_output(open(filename, 'w')) + sect = Section('Report', + '%s statements analysed.'% (self.stats['statement'])) + checkers = sort_checkers(self._reports.keys()) + checkers.reverse() + for checker in checkers: + for r_id, r_title, r_cb in self._reports[checker]: + if not self.is_report_enabled(r_id): + continue + report_sect = Section(r_title) + try: + r_cb(report_sect, stats, old_stats) + except EmptyReport: + continue + report_sect.report_id = r_id + sect.append(report_sect) + self.reporter.display_results(sect) + + def add_stats(self, **kwargs): + """add some stats entries to the statistic dictionary + raise an AssertionError if there is a key conflict + """ + for key, value in kwargs.items(): + if key[-1] == '_': + key = key[:-1] + assert not self.stats.has_key(key) + self.stats[key] = value + return self.stats + |