summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorroot <none@none>2006-04-26 10:48:09 +0000
committerroot <none@none>2006-04-26 10:48:09 +0000
commit4becf6f9e596b45401680c4947e2d92c953d5e08 (patch)
tree3bb03a16daa8c780bf60c622dc288eb01cfca145
downloadpylint-git-4becf6f9e596b45401680c4947e2d92c953d5e08.tar.gz
forget the past.
forget the past.
-rw-r--r--.hgignore4
-rw-r--r--ChangeLog518
-rw-r--r--DEPENDS3
-rw-r--r--MANIFEST.in18
-rw-r--r--README164
-rw-r--r--TODO46
-rw-r--r--__init__.py18
-rw-r--r--__pkginfo__.py74
-rw-r--r--announce.txt42
-rwxr-xr-xbin/pylint4
-rwxr-xr-xbin/pylint-gui4
-rw-r--r--bin/pylint-gui.bat20
-rw-r--r--bin/pylint.bat19
-rwxr-xr-xbin/symilar3
-rw-r--r--bin/symilar.bat20
-rw-r--r--checkers/__init__.py166
-rw-r--r--checkers/base.py486
-rw-r--r--checkers/classes.py481
-rw-r--r--checkers/design_analysis.py322
-rw-r--r--checkers/exceptions.py167
-rw-r--r--checkers/format.py325
-rw-r--r--checkers/imports.py379
-rw-r--r--checkers/misc.py127
-rw-r--r--checkers/newstyle.py148
-rw-r--r--checkers/raw_metrics.py130
-rw-r--r--checkers/similar.py329
-rw-r--r--checkers/typecheck.py181
-rw-r--r--checkers/utils.py144
-rw-r--r--checkers/variables.py428
-rw-r--r--config.py137
-rw-r--r--debian/changelog205
-rw-r--r--debian/control30
-rw-r--r--debian/copyright29
-rw-r--r--debian/pylint.dirs7
-rw-r--r--debian/pylint.docs6
-rw-r--r--debian/pylint.emacsen-install45
-rw-r--r--debian/pylint.emacsen-remove14
-rw-r--r--debian/pylint.emacsen-startup17
-rw-r--r--debian/pylint.examples1
-rw-r--r--debian/pylint.manpages1
-rw-r--r--debian/pylint.postinst24
-rw-r--r--debian/pylint.prerm14
-rw-r--r--debian/rules84
-rw-r--r--debian/watch3
-rw-r--r--doc/FAQ.txt154
-rw-r--r--doc/features.txt678
-rw-r--r--doc/makefile36
-rw-r--r--doc/quickstart.txt216
-rw-r--r--elisp/pylint.el37
-rw-r--r--elisp/startup17
-rw-r--r--examples/custom.py38
-rw-r--r--examples/custom_raw.py31
-rw-r--r--examples/pylintrc352
-rw-r--r--examples/pylintrc_camelcase24
-rw-r--r--gui.py84
-rw-r--r--interfaces.py98
-rw-r--r--lint.py879
-rw-r--r--man/pylint.1371
-rw-r--r--reporters/__init__.py67
-rw-r--r--reporters/html.py64
-rw-r--r--reporters/text.py151
-rw-r--r--setup.cfg3
-rw-r--r--setup.py187
-rwxr-xr-xtest/fulltest.sh17
-rw-r--r--test/func_test.py193
-rw-r--r--test/func_test_sample_config.py30
-rw-r--r--test/input/__init__.py1
-rw-r--r--test/input/func___future___import_not_first_stmt.py5
-rw-r--r--test/input/func___name___access.py21
-rw-r--r--test/input/func_attrs_definition_order.py15
-rw-r--r--test/input/func_bad_assigment_to_exception_var.py31
-rw-r--r--test/input/func_base_stmt_without_effect.py15
-rw-r--r--test/input/func_block_disable_msg.py87
-rw-r--r--test/input/func_class_members.py31
-rw-r--r--test/input/func_continue_not_in_loop.py14
-rw-r--r--test/input/func_dangerous_default.py17
-rw-r--r--test/input/func_docstring.py48
-rw-r--r--test/input/func_dotted_ancestor.py11
-rw-r--r--test/input/func_e0011.py5
-rw-r--r--test/input/func_e0012.py5
-rw-r--r--test/input/func_e0101.py11
-rw-r--r--test/input/func_e0203.py19
-rw-r--r--test/input/func_e0204.py19
-rw-r--r--test/input/func_e0205.py17
-rw-r--r--test/input/func_e0206.py20
-rw-r--r--test/input/func_e0214.py18
-rw-r--r--test/input/func_e0601.py9
-rw-r--r--test/input/func_empty_module.py0
-rw-r--r--test/input/func_exceptions_raise_type_error.py14
-rw-r--r--test/input/func_f0001.py4
-rw-r--r--test/input/func_f0401.py9
-rw-r--r--test/input/func_fixme.py9
-rw-r--r--test/input/func_format.py61
-rw-r--r--test/input/func_globals.py40
-rw-r--r--test/input/func_i0010.py3
-rw-r--r--test/input/func_i0011.py5
-rw-r--r--test/input/func_i0012.py5
-rw-r--r--test/input/func_indent.py22
-rw-r--r--test/input/func_init_vars.py46
-rw-r--r--test/input/func_interfaces.py99
-rw-r--r--test/input/func_method_could_be_function.py52
-rw-r--r--test/input/func_method_missing_self.py25
-rw-r--r--test/input/func_method_without_self_but_self_assignment.py15
-rw-r--r--test/input/func_nameerror_on_string_substitution.py8
-rw-r--r--test/input/func_names_imported_from_module.py31
-rw-r--r--test/input/func_newstyle___slots__.py17
-rw-r--r--test/input/func_newstyle_exceptions.py35
-rw-r--r--test/input/func_newstyle_property.py19
-rw-r--r--test/input/func_newstyle_super.py22
-rw-r--r--test/input/func_noerror___future___import.py5
-rw-r--r--test/input/func_noerror___init___return_from_inner_function.py13
-rw-r--r--test/input/func_noerror_access_attr_before_def_false_positive.py100
-rw-r--r--test/input/func_noerror_base_init_vars.py36
-rw-r--r--test/input/func_noerror_builtin_module_test.py11
-rw-r--r--test/input/func_noerror_defined_and_used_on_same_line.py20
-rw-r--r--test/input/func_noerror_defined_and_used_on_same_line_py24.py7
-rw-r--r--test/input/func_noerror_e1101_13784.py15
-rw-r--r--test/input/func_noerror_e1101_but_getattr.py23
-rw-r--r--test/input/func_noerror_encoding.py6
-rw-r--r--test/input/func_noerror_exception.py7
-rw-r--r--test/input/func_noerror_indirect_interface.py16
-rw-r--r--test/input/func_noerror_inner_classes.py33
-rw-r--r--test/input/func_noerror_mcs_attr_access.py20
-rw-r--r--test/input/func_noerror_nested_classes.py18
-rw-r--r--test/input/func_noerror_new_style_class.py45
-rw-r--r--test/input/func_noerror_object_as_class_attribute.py19
-rw-r--r--test/input/func_noerror_socket_member.py25
-rw-r--r--test/input/func_noerror_static_method.py29
-rw-r--r--test/input/func_noerror_staticmethod_as_decorator.py35
-rw-r--r--test/input/func_noerror_w0232.py10
-rw-r--r--test/input/func_nonascii_noencoding.py5
-rw-r--r--test/input/func_r0901.py27
-rw-r--r--test/input/func_r0902.py28
-rw-r--r--test/input/func_r0903.py13
-rw-r--r--test/input/func_r0904.py73
-rw-r--r--test/input/func_r0921.py15
-rw-r--r--test/input/func_r0922.py21
-rw-r--r--test/input/func_r0923.py32
-rw-r--r--test/input/func_reqattrs.py1
-rw-r--r--test/input/func_scope_regrtest.py15
-rw-r--r--test/input/func_syntax_error.py1
-rw-r--r--test/input/func_toolonglines.py4
-rw-r--r--test/input/func_typecheck_callfunc_assigment.py56
-rw-r--r--test/input/func_typecheck_getattr.py60
-rw-r--r--test/input/func_typecheck_non_callable_call.py37
-rw-r--r--test/input/func_undefined_var.py26
-rw-r--r--test/input/func_unknown_encoding.py6
-rw-r--r--test/input/func_unreachable.py22
-rw-r--r--test/input/func_use_for_or_listcomp_var.py21
-rw-r--r--test/input/func_w0101.py28
-rw-r--r--test/input/func_w0102.py53
-rw-r--r--test/input/func_w0103.py8
-rw-r--r--test/input/func_w0104.py12
-rw-r--r--test/input/func_w0105.py62
-rw-r--r--test/input/func_w0109.py7
-rw-r--r--test/input/func_w0110.py10
-rw-r--r--test/input/func_w0111.py10
-rw-r--r--test/input/func_w0112.py37
-rw-r--r--test/input/func_w0122.py13
-rw-r--r--test/input/func_w0133.py54
-rw-r--r--test/input/func_w0151.py7
-rw-r--r--test/input/func_w0152.py14
-rw-r--r--test/input/func_w0202.py17
-rw-r--r--test/input/func_w0205.py24
-rw-r--r--test/input/func_w0223.py27
-rw-r--r--test/input/func_w0231.py38
-rw-r--r--test/input/func_w0233.py19
-rw-r--r--test/input/func_w0302.py1016
-rw-r--r--test/input/func_w0312.py11
-rw-r--r--test/input/func_w0331.py7
-rw-r--r--test/input/func_w0332.py4
-rw-r--r--test/input/func_w0401.py9
-rw-r--r--test/input/func_w0402.py9
-rw-r--r--test/input/func_w0403.py12
-rw-r--r--test/input/func_w0404.py10
-rw-r--r--test/input/func_w0405.py31
-rw-r--r--test/input/func_w0406.py9
-rw-r--r--test/input/func_w0611.py22
-rw-r--r--test/input/func_w0612.py8
-rw-r--r--test/input/func_w0613.py18
-rw-r--r--test/input/func_w0622.py11
-rw-r--r--test/input/func_w0701.py12
-rw-r--r--test/input/func_w0702.py17
-rw-r--r--test/input/func_w0703.py9
-rw-r--r--test/input/func_w0704.py16
-rw-r--r--test/input/func_w0705.py46
-rw-r--r--test/input/func_w0801.py11
-rw-r--r--test/input/func_wrong_encoding.py6
-rw-r--r--test/input/indirect1.py4
-rw-r--r--test/input/indirect2.py7
-rw-r--r--test/input/indirect3.py5
-rw-r--r--test/input/noext4
-rw-r--r--test/input/similar119
-rw-r--r--test/input/similar219
-rw-r--r--test/input/w0401_cycle.py9
-rw-r--r--test/input/w0801_same.py11
-rw-r--r--test/messages/builtin_module.txt1
-rw-r--r--test/messages/func___future___import_not_first_stmt.txt1
-rw-r--r--test/messages/func___name___access.txt3
-rw-r--r--test/messages/func_attrs_definition_order.txt1
-rw-r--r--test/messages/func_bad_assigment_to_exception_var.txt5
-rw-r--r--test/messages/func_base_stmt_without_effect.txt3
-rw-r--r--test/messages/func_block_disable_msg.txt9
-rw-r--r--test/messages/func_class_members.txt3
-rw-r--r--test/messages/func_continue_not_in_loop.txt2
-rw-r--r--test/messages/func_dangerous_default.txt2
-rw-r--r--test/messages/func_docstring.txt4
-rw-r--r--test/messages/func_dotted_ancestor.txt1
-rw-r--r--test/messages/func_e0011.txt1
-rw-r--r--test/messages/func_e0012.txt1
-rw-r--r--test/messages/func_e0101.txt1
-rw-r--r--test/messages/func_e0203.txt2
-rw-r--r--test/messages/func_e0204.txt3
-rw-r--r--test/messages/func_e0205.txt2
-rw-r--r--test/messages/func_e0206.txt3
-rw-r--r--test/messages/func_e0214.txt2
-rw-r--r--test/messages/func_e0601.txt1
-rw-r--r--test/messages/func_empty_module.txt2
-rw-r--r--test/messages/func_exceptions_raise_type_error.txt2
-rw-r--r--test/messages/func_f0001.txt1
-rw-r--r--test/messages/func_f0401.txt2
-rw-r--r--test/messages/func_fixme.txt2
-rw-r--r--test/messages/func_format.txt25
-rw-r--r--test/messages/func_globals.txt6
-rw-r--r--test/messages/func_i0010.txt1
-rw-r--r--test/messages/func_i0011.txt2
-rw-r--r--test/messages/func_i0012.txt2
-rw-r--r--test/messages/func_indent.txt3
-rw-r--r--test/messages/func_init_vars.txt1
-rw-r--r--test/messages/func_interfaces.txt5
-rw-r--r--test/messages/func_method_could_be_function.txt1
-rw-r--r--test/messages/func_method_missing_self.txt1
-rw-r--r--test/messages/func_method_without_self_but_self_assignment.txt2
-rw-r--r--test/messages/func_nameerror_on_string_substitution.txt2
-rw-r--r--test/messages/func_names_imported_from_module.txt8
-rw-r--r--test/messages/func_newstyle___slots__.txt1
-rw-r--r--test/messages/func_newstyle_exceptions.txt4
-rw-r--r--test/messages/func_newstyle_property.txt1
-rw-r--r--test/messages/func_newstyle_super.txt4
-rw-r--r--test/messages/func_nonascii_noencoding.txt1
-rw-r--r--test/messages/func_r0901.txt2
-rw-r--r--test/messages/func_r0902.txt1
-rw-r--r--test/messages/func_r0903.txt1
-rw-r--r--test/messages/func_r0904.txt1
-rw-r--r--test/messages/func_r0921.txt1
-rw-r--r--test/messages/func_r0922.txt1
-rw-r--r--test/messages/func_r0923.txt1
-rw-r--r--test/messages/func_reqattrs.txt1
-rw-r--r--test/messages/func_scope_regrtest.txt2
-rw-r--r--test/messages/func_syntax_error.txt2
-rw-r--r--test/messages/func_toolonglines.txt2
-rw-r--r--test/messages/func_typecheck_callfunc_assigment.txt2
-rw-r--r--test/messages/func_typecheck_getattr.txt9
-rw-r--r--test/messages/func_typecheck_non_callable_call.txt6
-rw-r--r--test/messages/func_undefined_var.txt5
-rw-r--r--test/messages/func_unknown_encoding.txt1
-rw-r--r--test/messages/func_unreachable.txt3
-rw-r--r--test/messages/func_use_for_or_listcomp_var.txt3
-rw-r--r--test/messages/func_w0101.txt1
-rw-r--r--test/messages/func_w0102.txt5
-rw-r--r--test/messages/func_w0103.txt1
-rw-r--r--test/messages/func_w0104.txt1
-rw-r--r--test/messages/func_w0105.txt1
-rw-r--r--test/messages/func_w0109.txt1
-rw-r--r--test/messages/func_w0110.txt1
-rw-r--r--test/messages/func_w0111.txt1
-rw-r--r--test/messages/func_w0112.txt1
-rw-r--r--test/messages/func_w0122.txt5
-rw-r--r--test/messages/func_w0133.txt9
-rw-r--r--test/messages/func_w0151.txt2
-rw-r--r--test/messages/func_w0152.txt3
-rw-r--r--test/messages/func_w0202.txt3
-rw-r--r--test/messages/func_w0205.txt2
-rw-r--r--test/messages/func_w0223.txt2
-rw-r--r--test/messages/func_w0231.txt2
-rw-r--r--test/messages/func_w0233.txt1
-rw-r--r--test/messages/func_w0302.txt2
-rw-r--r--test/messages/func_w0312.txt2
-rw-r--r--test/messages/func_w0331.txt1
-rw-r--r--test/messages/func_w0332.txt1
-rw-r--r--test/messages/func_w0401.txt2
-rw-r--r--test/messages/func_w0402.txt2
-rw-r--r--test/messages/func_w0403.txt1
-rw-r--r--test/messages/func_w0404.txt1
-rw-r--r--test/messages/func_w0405.txt4
-rw-r--r--test/messages/func_w0406.txt2
-rw-r--r--test/messages/func_w0611.txt1
-rw-r--r--test/messages/func_w0612.txt1
-rw-r--r--test/messages/func_w0613.txt2
-rw-r--r--test/messages/func_w0622.txt2
-rw-r--r--test/messages/func_w0701.txt3
-rw-r--r--test/messages/func_w0702.txt1
-rw-r--r--test/messages/func_w0703.txt1
-rw-r--r--test/messages/func_w0704.txt1
-rw-r--r--test/messages/func_w0705.txt5
-rw-r--r--test/messages/func_w0801.txt11
-rw-r--r--test/messages/func_wrong_encoding.txt1
-rw-r--r--test/messages/nonexistant.txt1
-rw-r--r--test/messages/nonexistant.txt21
-rw-r--r--test/regrtest.py131
-rw-r--r--test/regrtest_data/application_crash.py12
-rw-r--r--test/regrtest_data/classdoc_usage.py17
-rw-r--r--test/regrtest_data/descriptor_crash.py20
-rw-r--r--test/regrtest_data/import_package_subpackage_module.py49
-rw-r--r--test/regrtest_data/module_global.py7
-rw-r--r--test/regrtest_data/numarray_import.py7
-rw-r--r--test/regrtest_data/package/AudioTime.py3
-rw-r--r--test/regrtest_data/package/__init__.py14
-rw-r--r--test/regrtest_data/package/subpackage/__init__.py1
-rw-r--r--test/regrtest_data/package/subpackage/module.py1
-rw-r--r--test/regrtest_data/precedence_test.py21
-rw-r--r--test/regrtest_data/pygtk_import.py14
-rw-r--r--test/regrtest_data/socketerror_import.py6
-rw-r--r--test/runtests.py5
-rw-r--r--test/smoketest.py74
-rw-r--r--test/test_encoding.py62
-rw-r--r--test/test_format.py168
-rw-r--r--test/test_import_graph.py63
-rw-r--r--test/test_similar.py56
-rw-r--r--test/unittest_checkers_utils.py49
-rw-r--r--test/unittest_lint.py268
-rw-r--r--test/utils.py66
-rw-r--r--utils.py353
323 files changed, 14574 insertions, 0 deletions
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 000000000..2849616a4
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,4 @@
+(^|/)\.svn($|/)
+(^|/)\.hg($|/)
+(^|/)\.hgtags($|/)
+^log$
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 000000000..43305a9f6
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,518 @@
+ChangeLog for PyLint
+====================
+
+2006-04-19 -- 0.11.0
+ * fix crash caused by the exceptions checker in some case
+ * fix some E1101 false positive with abstract method or classes defining
+ __getattr__
+
+ * dirty fix to avoid "_socketobject" has not "connect" member. The actual
+ problem is that astng isn't able to understand the code used to create
+ socket.socket object with exec
+
+ * added an option in the similarity checker to ignore docstrings, enabled
+ by default
+
+ * included patch Benjamin Niemann to allow block level enabling/disabling
+ of messages
+
+
+2006-03-06 -- 0.10.0
+ * WARNING, this release include some configuration changes (see below),
+ so you may have to check and update your own configuration file(s) if
+ you use one
+
+ * this release require the 0.15 version of astng or superior (it will save
+ you a lot of pylint crashes...)
+
+ * W0705 has been reclassified to E0701, and is now detecting more
+ inheriting problem, and a false positive when empty except clause is
+ following an Exception catch has been fixed (close #10422)
+
+ * E0212 and E0214 (metaclass/class method should have mcs/cls as first
+ argument have been reclassified to C0202 and C0203 since this not as
+ well established as "self" for instance method (E0213)
+
+ * W0224 has been reclassified into F0220 (failed to resolve interfaces
+ implemented by a class)
+
+ * a new typecheck checker, introducing the following checks:
+ - E1101, access to unexistant member (implements #10430), remove
+ the need of E0201 and so some options has been moved from the
+ classes checker to this one
+ - E1102, calling a non callable object
+ - E1111 and W1111 when an assigment is done on a function call but the
+ infered function returns None (implements #10431)
+
+ * change in the base checker:
+ - checks module level and instance attribute names (new const-rgx
+ and attr-rgx configuration option) (implements #10209 and
+ #10440)
+ - list comprehension and generator expression variables have their
+ own regular expression (the inlinevar-rgx option) (implements
+ #9146)
+ - the C0101 check with its min-name-lentgh option has
+ been removed (this can be specified in the regxp after all...)
+ - W0103 and W0121 are now handled by the variables checker
+ (W0103 is now W0603 and W0604 has been splitted into different messages)
+ - W0131 and W0132 messages have been reclassified to C0111 and
+ C0112 respectivly
+ - new W0104 message on statement without effect
+
+ * regexp support for dummy-variables (dummy-variables-rgx option
+ replace dummy-variables) (implements #10027)
+
+ * better global statement handling, see W0602, W0603, W0604 messages
+ (implements #10344 and #10236)
+
+ * --debug-mode option, disabling all checkers without error message
+ and filtering others to only display error
+
+ * fixed some R0201 (method could be a function) false positive
+
+
+2006-01-10 -- 0.9.0
+ * a lot of updates to follow astng 0.14 API changes, so install
+ logilab-astng 0.14 or greater before using this version of pylint
+
+ * checker number 10 ! newstyle will search for problems regarding old
+ style / new style classes usage problems (rely on astng 0.14 new
+ style detection feature)
+
+ * new 'load-plugins' options to load additional pylint plugins (usable
+ from the command line or from a configuration file) (implements
+ #10031)
+
+ * check if a "pylintrc" file exists in the current working directory
+ before using the one specified in the PYLINTRC environment variable
+ or the default ~/.pylintrc or /etc/pylintrc
+
+ * fixed W0706 (Identifier used to raise an exception is assigned...)
+ false positive and reraising a catched exception instance
+
+ * fixed E0611 (No name get in module blabla) false positive when accessing
+ to a class'__dict__
+
+ * fixed some E0203 ("access to member before its definition") false
+ positive
+
+ * fixed E0214 ("metaclass method frist argument should be mcs) false
+ positive with staticmethod used on a metaclass
+
+ * fixed packaging which was missing the test/regrtest_data directory
+
+ * W0212 (method could be a function) has been reclassified in the
+ REFACTOR category as R0201, and is no more considerer when a method
+ overrides an abstract method from an ancestor class
+
+ * include module name in W0401 (wildcard import), as suggested by
+ Amaury
+
+ * when using the '--parseable', path are written relative to the
+ current working directory if in a sub-directory of it (#9789)
+
+ * 'pylint --version' shows logilab-astng and logilab-common versions
+
+ * fixed pylint.el to handle space in file names
+
+ * misc lint style fixes
+
+
+
+2005-11-07 -- 0.8.1
+ * fix "deprecated module" false positive when the code imports a
+ module whose name starts with a deprecated module's name (close
+ #10061)
+
+ * fix "module has no name __dict__" false positive (close #10039)
+
+ * fix "access to undefined variable __path__" false positive (close
+ #10065)
+
+ * fix "explicit return in __init__" false positive when return is
+ actually in an inner function (close #10075)
+
+
+2005-10-21 -- 0.8.0
+ * check names imported from a module exists in the module (E0611),
+ patch contributed by Amaury Forgeot d'Arc
+
+ * print a warning (W0212) for methods that could be a function
+ (implements #9100)
+
+ * new --defining-attr-methods option on classes checker
+
+ * new --acquired-members option on the classes checker, used when
+ --zope=yes to avoid false positive on acquired attributes (listed
+ using this new option) (close #8616)
+
+ * generate one E0602 for each use of an undefined variable
+ (previously, only one for the first use but not for the following)
+ (implements #1000)
+
+ * make profile option saveable
+
+ * fix Windows .bat file, patch contributed by Amaury Forgeot d'Arc
+
+ * fix one more false positive for E0601 (access before definition)
+ with for loop such as "for i in range(10): print i" (test
+ func_noerror_defined_and_used_on_same_line)
+
+ * fix false positive for E0201 (undefined member) when accessing to
+ __name__ on a class object
+
+ * fix astng checkers traversal order
+
+ * fix bug in format checker when parsing a file from a platform
+ using different new line characters (close #9239)
+
+ * fix encoding detection regexp
+
+ * fix --rcfile handling (support for --rcfile=file, close #9590)
+
+
+
+2005-05-27 -- 0.7.0
+ * WARNING: pylint is no longer a logilab subpackage. Users may have to
+ manually remove the old logilab/pylint directory.
+
+ * introduce a new --additional-builtins option to handle user defined
+ builtins
+
+ * --reports option has now -r as short alias, and -i for --include-ids
+
+ * fix a bug in the variables checker which may causing some false
+ positives when variables are defined and used within the same
+ statement (test func_noerror_defined_and_used_on_same_line)
+
+ * this time, real fix of the "disable-msg in the config file" problem,
+ test added to unittest_lint
+
+ * fix bug with --list-messages and python -OO
+
+ * fix possible false positive for W0201
+
+
+
+2005-04-14 -- 0.6.4
+ * allow to parse files without extension when a path is given on the
+ command line (test noext)
+
+ * don't fail if we are unable to read an inline option (e.g. inside a
+ module), just produce an information message (test func_i0010)
+
+ * new message E0103 for break or continue outside loop (close #8883,
+ test func_continue_not_in_loop)
+
+ * fix bug in the variables checker, causing non detection of some
+ actual name error (close #8884, test
+ func_nameerror_on_string_substitution)
+
+ * fix bug in the classes checker which was making pylint crash if
+ "object" is assigned in a class inheriting from it (test
+ func_noerror_object_as_class_attribute)
+
+ * fix problem with the similar checker when related options are
+ defined in a configuration file
+
+ * new --generate-man option to generate pylint's man page (require the
+ latest logilab.common (>= 0.9.3)
+
+ * packaged (generated...) man page
+
+
+
+2005-02-24 -- 0.6.3
+ * fix scope problem which may cause false positive and true negative
+ on E0602
+
+ * fix problem with some options such as disable-msg causing error when
+ they are coming from the configuration file
+
+
+
+2005-02-16 -- 0.6.2
+ * fix false positive on E0201 ("access to undefined member") with
+ metaclasses
+
+ * fix false positive on E0203 ("access to member before its
+ definition") when attributes are defined in a parent class
+
+ * fix false positive on W0706 ("identifier used to raise an exception
+ assigned to...")
+
+ * fix interpretation of "\t" as value for the indent-string
+ configuration variable
+
+ * fix --rcfile so that --rcfile=pylintrc (only --rcfile pylintrc was
+ working in earlier release)
+
+ * new raw checker example in the examples/ directory
+
+
+
+2005-02-04 -- 0.6.1
+ * new --rcfile option to specify the configuration file without the
+ PYLINTRC environment variable
+
+ * added an example module for a custom pylint checker (see the
+ example/ directory)
+
+ * some fixes to handle fixes in common 0.9.1 (should however still working
+ with common 0.9.0, even if upgrade is recommended)
+
+
+
+2005-01-20 -- 0.6.0
+ * refix pylint emacs mode
+
+ * no more traceback when just typing "pylint"
+
+ * fix a bug which may cause crashes on resolving parent classes
+
+ * fix problems with the format checker: don't chock on files
+ containing multiple CR, avoid C0322, C0323, C0324 false positives
+ with triple quoted string with quote inside
+
+ * correctly detect access to member defined latter in __init__ method
+
+ * now depends on common 0.8.1 to fix problem with interface resolution
+ (close #8606)
+
+ * new --list-msgs option describing available checkers and their
+ messages
+
+ * added windows specific documentation to the README file, contributed
+ by Brian van den Broek
+
+ * updated doc/features.txt (actually this file is now generated using
+ the --list-msgs option), more entries into the FAQ
+
+ * improved tests coverage
+
+
+
+2004-10-19 -- 0.5.0
+ * avoid to import analyzed modules !
+
+ * new Refactor and Convention message categories. Some Warnings have been
+ remaped into those new categories
+
+ * added "similar", a tool to find copied and pasted lines of code,
+ both using a specific command line tool and integrated as a
+ pylint's checker
+
+ * imports checker may report import dependancies as a dot graph
+
+ * new checker regrouping most Refactor detection (with some new metrics)
+
+ * more command line options storable in the configuration file
+
+ * fix bug with total / undocumented number of methods
+
+
+
+2004-07-08 -- 0.4.2
+ * fix pylint emacs mode
+
+ * fix classes checkers to handler twisted interfaces
+
+
+
+2004-05-14 -- 0.4.1
+ * fix the setup.py script to allow bdist_winst (well, the generated
+ installer has not been tested...) with the necessary
+ logilab/__init__.py file
+
+ * fix file naming convention as suggested by Andreas Amoroso
+
+ * fix stupid crash bug with bad method names
+
+
+
+2004-05-10 -- 0.4.0
+ * fix file path with --parsable
+
+ * --parsable option has been renamed to --parseable
+
+ * added patch from Andreas Amoroso to output message to files instead
+ of standard output
+
+ * added Run to the list of correct variable names
+
+ * fix variable names regexp and checking of local classes names
+
+ * some basic handling of metaclasses
+
+ * no-docstring-rgx apply now on classes too
+
+ * new option to specify a different regexp for methods than for
+ functions
+
+ * do not display the evaluation report when no statements has been
+ analysed
+
+ * fixed crash with a class nested in a method
+
+ * fixed format checker to deals with triple quoted string and
+ lines with code and comment mixed
+
+ * use logilab.common.ureports to layout reports
+
+
+
+2004-02-17 -- 0.3.3
+ * added a parsable text output, used when the --parsable option is
+ provided
+
+ * added an emacs mode using this output, availabe in the distrib's
+ elisp directory
+
+ * fixed some typos in messages
+
+ * change include-ids options to yn, and allow it to be in the
+ configuration file
+
+ * do not chock on corrupted stats files
+
+ * fixed bug in the format checker which may stop pylint execution
+
+ * provide scripts for unix and windows to wrap the minimal pylint tk
+ gui
+
+
+
+2003-12-23 -- 0.3.2
+ * html-escape messages in the HTML reporter (bug reported by Juergen
+ Hermann)
+
+ * added "TODO" to the list of default note tags
+
+ * added "rexec" to the list of default deprecated modules
+
+ * fixed typos in some messages
+
+
+
+2003-12-05 -- 0.3.1
+ * bug fix in format and classes checkers
+
+ * remove print statement from imports checkers
+
+ * provide a simple tk gui, essentially usefull for windows users
+
+
+
+2003-11-20 -- 0.3.0
+ * new exceptions checker, checking for string exception and empty
+ except clauses.
+
+ * imports checker checks for reimport of modules
+
+ * classes checker checks for calls to ancestor's __init__ and abstract
+ method not overriden. It doesn't complain anymore for unused import in
+ __init__ files, and provides a new option ignore-interface-methods,
+ usefull when you're using zope Interface implementation in your project
+
+ * base checker checks for black listed builtins call (controled by the
+ bad-functions option) and for use of * and **
+
+ * format checker checks for use of <> and "l" as long int marker
+
+ * major internal API changes
+
+ * use the rewrite of astng, based on compiler.ast
+
+ * added unique id for messages, as suggested by Wolfgang Grafen
+
+ * added unique id for reports
+
+ * can take multiple modules or files as argument
+
+ * new options command line options : --disable-msg, --enable-msg,
+ --help-msg, --include-ids, --reports, --disable-report, --cache-size
+
+ * --version shows the version of the python interpreter
+
+ * removed some options which are now replaced by [en|dis]able-msg, or
+ disable-report
+
+ * read disable-msg and enable-msg options in source files (should be
+ in comments on the top of the file, in the form
+ "# pylint: disable-msg=W0402"
+
+ * new message for modules importing themselves instead of the "cyclic
+ import" message
+
+ * fix bug with relative and cyclic imports
+
+ * fix bug in imports checker (cycle was not always detected)
+
+ * still fixes in format checker : don't check comment and docstring,
+ check first line after an indent
+
+ * black and white list now apply to all identifiers, not only
+ variables, so changed the configuration option from
+ (good|bad)-variable-names to (good|bad)-names
+
+ * added string, rexec and Bastion to the default list of deprecated
+ modules
+
+ * do not print redefinition warning for function/class/method defined
+ in mutually exclusive branchs
+
+
+
+2003-10-10 -- 0.2.1
+ * added some documentation, fixed some typos
+
+ * set environment variable PYLINT_IMPORT to 1 during pylint execution.
+
+ * check that variables "imported" using the global statement exist
+
+ * indentation problems are now warning instead of errors
+
+ * fix checkers.initialize to try to load all files with a known python
+ extension (patch from wrobell)
+
+ * fix a bunch of messages
+
+ * fix sample configuration file
+
+ * fix the bad-construction option
+
+ * fix encoding checker
+
+ * fix format checker
+
+
+
+2003-09-12 -- 0.2.0
+ * new source encoding / FIXME checker (pep 263)
+
+ * new --zope option which trigger Zope import. Usefull to check Zope
+ products code.
+
+ * new --comment option which enable the evaluation note comment
+ (disabled by default).
+
+ * a ton of bug fixes
+
+ * easy functionnal test infrastructure
+
+
+
+2003-06-18 -- 0.1.2
+ * bug fix release
+
+ * remove dependency to pyreverse
+
+
+
+2003-06-01 -- 0.1.1
+ * much more functionnalities !
+
+
+
+2003-05-19 -- 0.1
+ * initial release
diff --git a/DEPENDS b/DEPENDS
new file mode 100644
index 000000000..e9ed30b45
--- /dev/null
+++ b/DEPENDS
@@ -0,0 +1,3 @@
+python-logilab-common (>= 0.13.0)
+python-logilab-astng (>= 0.15.0)
+python-tk
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 000000000..feed5c68b
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,18 @@
+include COPYING
+include DEPENDS
+include ChangeLog
+include TODO
+include bin/symilar
+include bin/pylint
+include bin/*.bat
+include bin/pylint-gui
+include examples/pylintrc*
+include examples/*.py
+include elisp/*.el
+include elisp/startup
+include man/pylint.1
+recursive-include doc *.txt *.html
+recursive-include test/input *.py similar* noext
+recursive-include test/messages *.txt *.txt2
+recursive-include test/regrtest_data *.py
+include test/fulltest.sh
diff --git a/README b/README
new file mode 100644
index 000000000..ea4565fde
--- /dev/null
+++ b/README
@@ -0,0 +1,164 @@
+README for PyLint
+=================
+
+Dependencies
+------------
+Pylint requires the logilab-astng (version >= 0.14), logilab-common
+(version >= 0.13) and the optik (only for python < 2.3) packages.
+
+* http://www.logilab.org/projects/astng
+* http://www.logilab.org/projects/common
+* http://optik.sourceforge.net/
+
+
+Distributions
+-------------
+The source tarball is available at ftp://ftp.logilab.fr/pub/pylint.
+
+You may apt-get a debian package by adding ::
+
+ deb ftp://ftp.logilab.org/pub/debian unstable/
+
+to your /etc/apt/sources.list files. Pylint is also available in the standard Debian distribution
+
+Contributed RPM packages for pylint and logilab-common are available at
+ftp://ftp.nest.pld-linux.org/test .
+
+Pylint is also available in Gentoo, Fedora 4, Ubuntu, FreeBSD, Darwin.
+
+
+Install
+-------
+From the source distribution, extract the tarball and run ::
+
+ python setup.py install
+
+For debian and rpm packages, use your usual tools according to your Linux
+distribution.
+
+Note for Windows users:
+On Windows, once you have installed pylint, the command line usage is
+pylint.bat [options] module_or_package
+
+But this will only work if pylint.bat is either in the current
+directory, or on your system path. (setup.py install install python.bat
+to the Scripts subdirectory of your Python installation -- e.g.
+C:\Python24\Scripts.) You can do any of the following to solve this:
+
+1. change to the appropriate directory before running pylint.bat
+
+2. add the Scripts directory to your path statement in your autoexec.bat
+ file (this file is found in the root directory of your boot-drive)
+
+3. create a 'redirect' batch file in a directory actually on your
+ systems path
+
+To effect (2), simply append the appropriate directory name to the PATH=
+statement in autoexec.bat. Be sure to use the Windows directory
+separator of ';' between entries. Then, once you have rebooted (this is
+necessary so that the new path statement will take effect when
+autoexec.bat is run), you will be able to invoke PyLint with
+pylint.bat on the command line.
+
+(3) is the best solution. Once done, you can call pylint at the command
+line without the .bat, just as do non-Windows users by typing: ::
+
+ pylint [options] module_or_package
+
+To effect option (3), simply create a plain text file pylint.bat with
+the single line: ::
+
+ C:\PythonDirectory\Scripts\pylint.bat
+
+(where PythonDirectory is replaced by the actual Python installation
+directory on your system -- e.g. C:\Python24\Scripts\pylint.bat).
+
+
+IDE integration
+---------------
+
+Pylint is integrated in the following editors/IDEs:
+
+ * emacs (of course)
+
+ * eric3
+
+ * eclipse (using the pydev_ plugin, see also http://msdl.cs.mcgill.ca/MSDL/people/denis/meetings/pythonDev)
+
+To use pylint from within vim, see http://www.gonzo.kiev.ua/projects/pylint.vim
+
+_pydev: http://pydev.sourceforge.net
+
+Some projects using Pylint
+--------------------------
+
+ * The CheeseCake kwalitee reporting tool uses pylint to analyze the source code.
+
+The following projects use pylint to help develop better code:
+
+ * http://browsershots.org
+
+ * OSAF Chandler (http://www.osafoundation.org/)
+
+ * CPS (http://www.nuxeo.org)
+
+ * Xen (http://www.xensource.com/)
+
+ * pyxmpp (http://pyxmpp.jabberstudio.org/)
+
+ * eXe (http://exelearning.org/)
+
+ * PrimaGIS (http://www.primagis.org)
+
+ * python-cdd (http://projetos.ossystems.com.br/python-cdd/)
+
+ * CDSWare (http://cdsware.cern.ch/)
+
+ * ASE (http://dcwww.camp.dtu.dk/campos/ASE/intro.html)
+
+ * RunJob (http://projects.fnal.gov/runjob/)
+
+ * Slugathon (http://slugathon.python-hosting.com/)
+
+ * mercurial
+
+ * Topographica (http://topographica.org/Home/index.html) (at least they intend to do so)
+
+ * ERP5 (http://www.erp5.org/)
+
+ * many more...
+
+Documentation
+-------------
+Look in the doc/ subdirectory.
+
+
+Comments, support, bug reports
+------------------------------
+Use the python-projects@logilab.org mailing list. Since we do not have
+publicly available bug tracker yet, bug reports should be emailed
+there too.
+
+You can subscribe to this mailing list at
+http://www.logilab.org/mailinglists/python_projects/mailinglist_register_form
+
+Archives are available at
+http://lists.logilab.org/pipermail/python-projects/
+
+If you prefer speaking french instead of english, you can use the
+generic forum-fr@logilab.org mailing list.
+
+Contributors
+------------
+* Sylvain Thenault: main author / maintainer
+* Alexandre Fayolle: TkInter gui, documentation, debian support
+* Brian van den Broek: windows installation documentation
+* Amaury Forgeot d'Arc: patch to check names imported from a module
+ exists in the module
+* Benjamin Niemann: patch to allow block level enabling/disabling of messages
+* Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau,
+ Maarten ter Huurne, Mirko Friedenhagen (among others):
+ bug reports, feedback, feature requests...
+* All the Logilab's team: daily use, bug reports, feature requests
+* Other people have contributed by their feedback, if I've forgotten
+ you, send me a note !
diff --git a/TODO b/TODO
new file mode 100644
index 000000000..aaf847298
--- /dev/null
+++ b/TODO
@@ -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 000000000..abbb3e949
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,18 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2002-2003 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__revision__ = '$Id: __init__.py,v 1.5 2003-06-11 13:22:50 syt Exp $'
+
diff --git a/__pkginfo__.py b/__pkginfo__.py
new file mode 100644
index 000000000..169e1bc25
--- /dev/null
+++ b/__pkginfo__.py
@@ -0,0 +1,74 @@
+# pylint: disable-msg=W0622,C0103
+# Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""pylint packaging information"""
+
+__revision__ = '$Id: __pkginfo__.py,v 1.50 2006-04-19 09:17:40 syt Exp $'
+
+
+modname = 'pylint'
+
+numversion = (0, 11, 0)
+version = '.'.join([str(num) for num in numversion])
+
+license = 'GPL'
+copyright = '''Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com).
+Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr'''
+
+short_desc = "python code static checker"
+long_desc = """\
+ Pylint is a Python source code analyzer which looks for programming
+ errors, helps enforcing a coding standard and sniffs for some code
+ smells (as defined in Martin Fowler's Refactoring book)
+ .
+ Pylint can be seen as another PyChecker since nearly all tests you
+ can do with PyChecker can also be done with Pylint. However, Pylint
+ offers some more features, like checking length of lines of code,
+ checking if variable names are well-formed according to your coding
+ standard, or checking if declared interfaces are truly implemented,
+ and much more.
+ .
+ Additionally, it is possible to write plugins to add your own checks."""
+
+author = "Sylvain Thenault"
+author_email = "sylvain.thenault@logilab.fr"
+
+web = "http://www.logilab.org/projects/%s" % modname
+ftp = "ftp://ftp.logilab.org/pub/%s" % modname
+mailinglist = "mailto://python-projects@logilab.org"
+
+from os.path import join
+scripts = [join('bin', filename)
+ for filename in ('pylint', 'pylint-gui', "symilar")]
+
+include_dirs = [join('test', 'input'), join('test', 'messages'),
+ join('test', 'regrtest_data')]
+
+pyversions = ["2.2", "2.3", "2.4"]
+
+debian_uploader = 'Alexandre Fayolle <afayolle@debian.org>'
+
+classifiers = ['Development Status :: 4 - Beta',
+ 'Environment :: Console',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: GNU General Public License (GPL)',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development :: Debuggers',
+ 'Topic :: Software Development :: Quality Assurance',
+ 'Topic :: Software Development :: Testing',
+ ]
diff --git a/announce.txt b/announce.txt
new file mode 100644
index 000000000..4f49abe0c
--- /dev/null
+++ b/announce.txt
@@ -0,0 +1,42 @@
+What's new ?
+------------
+%CHANGELOG%
+
+
+What is pylint ?
+----------------
+
+Pylint is a python tool that checks if a module satisfy a coding
+standard. Pylint can be seen as another pychecker since nearly all
+tests you can do with pychecker can also be done with Pylint. But
+Pylint offers some more features, like checking line-code's length,
+checking if variable names are well-formed according to your coding
+standard, or checking if declared interfaces are truly implemented,
+and much more (see http://www.logilab.org/projects/pylint/ for the
+complete check list). The big advantage with Pylint is that it is
+highly configurable, customizable, and you can easily write a small
+plugin to add a personal feature.
+
+The usage it quite simple :
+
+$ pylint mypackage.mymodule
+
+
+This command will output all the errors and warnings related to the
+tested code (here : mypackage.mymodule), will dump a little summary at
+the end, and will give a mark to the tested code.
+
+Pylint is free software distributed under the GNU Public Licence.
+
+
+Home page
+---------
+%WEB%
+
+Download
+--------
+%FTP%
+
+Mailing list
+------------
+%MAILINGLIST%
diff --git a/bin/pylint b/bin/pylint
new file mode 100755
index 000000000..e20e03135
--- /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 000000000..847ef3a36
--- /dev/null
+++ b/bin/pylint-gui
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+import sys
+from pylint import gui
+gui.Run(sys.argv[1:])
diff --git a/bin/pylint-gui.bat b/bin/pylint-gui.bat
new file mode 100644
index 000000000..68d552ea5
--- /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 000000000..772735ac7
--- /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 000000000..7ca139fe2
--- /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 000000000..5c9bd0ead
--- /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 000000000..fdf015aad
--- /dev/null
+++ b/checkers/__init__.py
@@ -0,0 +1,166 @@
+# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""utilities methods and classes for checkers
+
+Base id of standard checkers (used in msg and report ids):
+01: base
+02: classes
+03: format
+04: import
+05: misc
+06: variables
+07: exceptions
+08: similar
+09: design_analysis
+10: newstyle
+"""
+
+__revision__ = "$Id: __init__.py,v 1.21 2005-11-21 23:08:11 syt Exp $"
+
+import tokenize
+from os import listdir
+from os.path import dirname, join, isdir, splitext
+
+from logilab.astng.utils import ASTWalker
+from logilab.common.configuration import OptionsProviderMixIn
+
+from pylint.reporters import diff_string, EmptyReport
+
+def table_lines_from_stats(stats, old_stats, columns):
+ """get values listed in <columns> from <stats> and <old_stats>,
+ and return a formated list of values, designed to be given to a
+ ureport.Table object
+ """
+ lines = []
+ for m_type in columns:
+ new = stats[m_type]
+ format = str
+ if isinstance(new, float):
+ format = lambda num: '%.3f' % num
+ old = old_stats.get(m_type)
+ if old is not None:
+ diff_str = diff_string(old, new)
+ old = format(old)
+ else:
+ old, diff_str = 'NC', 'NC'
+ lines += (m_type.replace('_', ' '), format(new), old, diff_str)
+ return lines
+
+
+class BaseChecker(OptionsProviderMixIn, ASTWalker):
+ """base class for checkers"""
+
+ options = ()
+ priority = -9
+ may_be_disabled = True
+ name = None
+
+ def __init__(self, linter=None):
+ """checker instances should have the linter as argument
+
+ linter is an object implementing ILinter
+ """
+ ASTWalker.__init__(self, self)
+ self.name = self.name.lower()
+ if self.may_be_disabled:
+ opt_name = 'enable-' + self.name
+ self.options = (
+ (opt_name,
+ {'type' : 'yn', 'default' : 1, 'metavar': '<y_or_n>',
+ 'help' : "Enable / disable this checker"})
+ ,) + self.options
+ OptionsProviderMixIn.__init__(self)
+ self.linter = linter
+
+ def add_message(self, msg_id, line=None, node=None, args=None):
+ """add a message of a given type"""
+ self.linter.add_message(msg_id, line, node, args)
+
+ def is_enabled(self):
+ """return true if the checker is enabled"""
+ opt = 'enable_' + self.name
+ return getattr(self.config, opt, 1)
+
+ def enable(self, enable):
+ """enable / disable this checker if true / false is given
+
+ it false values has no effect if the checker can't be disabled
+ """
+ if self.may_be_disabled:
+ setattr(self.config, 'enable_' + self.name, enable)
+
+ def package_dir(self):
+ """return the base directory for the analysed package"""
+ return dirname(self.linter.base_file)
+
+
+ # dummy methods implementing the IChecker interface
+
+ def open(self):
+ """called before visiting project (i.e set of modules)"""
+
+ def close(self):
+ """called after visiting project (i.e set of modules)"""
+
+class BaseRawChecker(BaseChecker):
+ """base class for raw checkers"""
+
+ def process_module(self, stream):
+ """process a module
+
+ the module's content is accessible via the stream object
+
+ stream must implements the readline method
+ """
+ self.process_tokens(tokenize.generate_tokens(stream.readline))
+
+ def process_tokens(self, tokens):
+ """should be overiden by subclasses"""
+ raise NotImplementedError()
+
+
+PY_EXTS = ('.py', '.pyc', '.pyo', '.pyw', '.so', '.dll')
+
+def initialize(linter):
+ """initialize linter with checkers in this package """
+ package_load(linter, __path__[0])
+
+def package_load(linter, directory):
+ """load all module and package in the given directory, looking for a
+ 'register' function in each one, used to register pylint checkers
+ """
+ globs = globals()
+ imported = {}
+ for filename in listdir(directory):
+ basename, extension = splitext(filename)
+ if not imported.has_key(basename) and (
+ (extension in PY_EXTS and basename != '__init__') or (
+ not extension and not basename == 'CVS' and
+ isdir(join(directory, basename)))):
+ try:
+ module = __import__(basename, globs, globs, None)
+ except ValueError:
+ # empty module name (usually emacs auto-save files)
+ continue
+ except ImportError:
+ import sys
+ print >> sys.stderr, "Problem importing module: %s" % filename
+ else:
+ if hasattr(module, 'register'):
+ module.register(linter)
+ imported[basename] = 1
+
+__all__ = ('CheckerHandler', 'BaseChecker', 'initialize', 'package_load')
diff --git a/checkers/base.py b/checkers/base.py
new file mode 100644
index 000000000..7d852ea68
--- /dev/null
+++ b/checkers/base.py
@@ -0,0 +1,486 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+ basic checker for Python code
+"""
+
+__revision__ = "$Id: base.py,v 1.65 2006-01-26 00:26:23 syt Exp $"
+
+from logilab import astng
+from logilab.common.ureports import Table
+
+from pylint.interfaces import IASTNGChecker
+from pylint.reporters import diff_string
+from pylint.checkers import BaseChecker
+from pylint.checkers.utils import are_exclusive
+
+import re
+
+# regex for class/function/variable/constant nane
+CLASS_NAME_RGX = re.compile('[A-Z_][a-zA-Z0-9]+$')
+MOD_NAME_RGX = re.compile('(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$')
+CONST_NAME_RGX = re.compile('(([A-Z_][A-Z1-9_]*)|(__.*__))$')
+COMP_VAR_RGX = re.compile('[A-Za-z_][A-Za-z0-9_]*$')
+DEFAULT_NAME_RGX = re.compile('[a-z_][a-z0-9_]{2,30}$')
+# do not require a doc string on system methods
+NO_REQUIRED_DOC_RGX = re.compile('__.*__')
+
+del re
+
+def in_loop(node):
+ parent = node.parent
+ while parent is not None:
+ if isinstance(parent, (astng.For, astng.ListComp, astng.GenExpr)):
+ return True
+ parent = parent.parent
+ return False
+
+def in_nested_list(nested_list, obj):
+ """return true if the object is an element of <nested_list> or of a nested
+ list
+ """
+ for elmt in nested_list:
+ if isinstance(elmt, (list, tuple)):
+ if in_nested_list(elmt, obj):
+ return True
+ elif elmt == obj:
+ return True
+ return False
+
+def report_by_type_stats(sect, stats, old_stats):
+ """make a report of
+
+ * percentage of different types documented
+ * percentage of different types with a bad name
+ """
+ # percentage of different types documented and/or with a bad name
+ nice_stats = {}
+ for node_type in ('module', 'class', 'method', 'function'):
+ nice_stats[node_type] = {}
+ total = stats[node_type]
+ if total == 0:
+ doc_percent = 0
+ badname_percent = 0
+ else:
+ documented = total - stats['undocumented_'+node_type]
+ doc_percent = float((documented)*100) / total
+ badname_percent = (float((stats['badname_'+node_type])*100)
+ / total)
+ nice_stats[node_type]['percent_documented'] = doc_percent
+ nice_stats[node_type]['percent_badname'] = badname_percent
+ lines = ('type', 'number', 'old number', 'difference',
+ '%documented', '%badname')
+ for node_type in ('module', 'class', 'method', 'function'):
+ new = stats[node_type]
+ old = old_stats.get(node_type, None)
+ if old is not None:
+ diff_str = diff_string(old, new)
+ else:
+ old, diff_str = 'NC', 'NC'
+ lines += (node_type, str(new), str(old), diff_str,
+ '%.2f' % nice_stats[node_type]['percent_documented'],
+ '%.2f' % nice_stats[node_type]['percent_badname'])
+ sect.append(Table(children=lines, cols=6, rheaders=1))
+
+
+MSGS = {
+ 'E0101': ('Explicit return in __init__',
+ 'Used when the special class method __ini__ has an explicit \
+ return value.'),
+ 'E0102': ('%s already defined line %s',
+ 'Used when a function / class / method is redefined.'),
+ 'E0103': ('%r not properly in loop',
+ 'Used when break or continue keywords are used outside a loop.'),
+
+ 'W0101': ('Unreachable code',
+ 'Used when there is some code behind a "return" or "raise" \
+ statement, which will never be accessed.'),
+ 'W0102': ('Dangerous default value %s as argument',
+ 'Used when a mutable value as list or dictionary is detected in \
+ a default value for an argument.'),
+ 'W0104': ('Statement seems to have no effect',
+ 'Used when a statement doesn\'t have (or at least seems to) \
+ any effect.'),
+
+ 'W0122': ('Use of the exec statement',
+ 'Used when you use the "exec" statement, to discourage its \
+ usage. That doesn\'t mean you can not use it !'),
+
+ 'W0141': ('Used builtin function %r',
+ 'Used when a black listed builtin function is used (see the \
+ bad-function option). Usual black listed functions are the ones \
+ like map, or filter , where Python offers now some cleaner \
+ alternative like list comprehension.'),
+ 'W0142': ('Used * or ** magic',
+ 'Used when a function or method is called using `*args` or \
+ `**kwargs` to dispatch arguments. This doesn\'t improve readility\
+ and should be used with care.'),
+
+ 'C0102': ('Black listed name "%s"',
+ 'Used when the name is listed in the black list (unauthorized \
+ names).'),
+ 'C0103': ('Invalid name "%s" (should match %s)',
+ 'Used when the name doesn\'t match the regular expression \
+ associated to its type (constant, variable, class...).'),
+
+ 'C0111': ('Missing docstring', # W0131
+ 'Used when a module, function, class or method has no docstring.\
+ Some special methods like __init__ doesn\'t necessary require a \
+ docstring.'),
+ 'C0112': ('Empty docstring', # W0132
+ 'Used when a module, function, class or method has an empty \
+ docstring (it would be to easy ;).'),
+
+ 'C0121': ('Missing required attribute "%s"', # W0103
+ 'Used when an attribute required for modules is missing.'),
+
+ }
+
+class BasicChecker(BaseChecker):
+ """checks for :
+ * doc strings
+ * modules / classes / functions / methods / arguments / variables name
+ * number of arguments, local variables, branchs, returns and statements in
+functions, methods
+ * required module attributes
+ * dangerous default values as arguments
+ * redefinition of function / method / class
+ * uses of the global statement
+ """
+
+ __implements__ = IASTNGChecker
+
+ name = 'basic'
+ msgs = MSGS
+ priority = -1
+ options = (('required-attributes',
+ {'default' : ('__revision__',), 'type' : 'csv',
+ 'metavar' : '<attributes>',
+ 'help' : 'Required attributes for module, separated by a '
+ 'comma'}
+ ),
+ ('no-docstring-rgx',
+ {'default' : NO_REQUIRED_DOC_RGX,
+ 'type' : 'regexp', 'metavar' : '<regexp>',
+ 'help' : 'Regular expression which should only match '
+ 'functions or classes name which do not require a '
+ 'docstring'}
+ ),
+## ('min-name-length',
+## {'default' : 3, 'type' : 'int', 'metavar' : '<int>',
+## 'help': 'Minimal length for module / class / function / '
+## 'method / argument / variable names'}
+## ),
+ ('module-rgx',
+ {'default' : MOD_NAME_RGX,
+ 'type' :'regexp', 'metavar' : '<regexp>',
+ 'help' : 'Regular expression which should only match correct '
+ 'module names'}
+ ),
+ ('const-rgx',
+ {'default' : CONST_NAME_RGX,
+ 'type' :'regexp', 'metavar' : '<regexp>',
+ 'help' : 'Regular expression which should only match correct '
+ 'module level names'}
+ ),
+ ('class-rgx',
+ {'default' : CLASS_NAME_RGX,
+ 'type' :'regexp', 'metavar' : '<regexp>',
+ 'help' : 'Regular expression which should only match correct '
+ 'class names'}
+ ),
+ ('function-rgx',
+ {'default' : DEFAULT_NAME_RGX,
+ 'type' :'regexp', 'metavar' : '<regexp>',
+ 'help' : 'Regular expression which should only match correct '
+ 'function names'}
+ ),
+ ('method-rgx',
+ {'default' : DEFAULT_NAME_RGX,
+ 'type' :'regexp', 'metavar' : '<regexp>',
+ 'help' : 'Regular expression which should only match correct '
+ 'method names'}
+ ),
+ ('attr-rgx',
+ {'default' : DEFAULT_NAME_RGX,
+ 'type' :'regexp', 'metavar' : '<regexp>',
+ 'help' : 'Regular expression which should only match correct '
+ 'instance attribute names'}
+ ),
+ ('argument-rgx',
+ {'default' : DEFAULT_NAME_RGX,
+ 'type' :'regexp', 'metavar' : '<regexp>',
+ 'help' : 'Regular expression which should only match correct '
+ 'argument names'}),
+ ('variable-rgx',
+ {'default' : DEFAULT_NAME_RGX,
+ 'type' :'regexp', 'metavar' : '<regexp>',
+ 'help' : 'Regular expression which should only match correct '
+ 'variable names'}
+ ),
+ ('inlinevar-rgx',
+ {'default' : COMP_VAR_RGX,
+ 'type' :'regexp', 'metavar' : '<regexp>',
+ 'help' : 'Regular expression which should only match correct '
+ 'list comprehension / generator expression variable \
+ names'}
+ ),
+ ('good-names',
+ {'default' : ('i', 'j', 'k', 'ex', 'Run', '_'),
+ 'type' :'csv', 'metavar' : '<names>',
+ 'help' : 'Good variable names which should always be accepted,'
+ ' separated by a comma'}
+ ),
+ ('bad-names',
+ {'default' : ('foo', 'bar', 'baz', 'toto', 'tutu', 'tata'),
+ 'type' :'csv', 'metavar' : '<names>',
+ 'help' : 'Bad variable names which should always be refused, '
+ 'separated by a comma'}
+ ),
+
+ ('bad-functions',
+ {'default' : ('map', 'filter', 'apply', 'input'),
+ 'type' :'csv', 'metavar' : '<builtin function names>',
+ 'help' : 'List of builtins function names that should not be '
+ 'used, separated by a comma'}
+ ),
+ )
+ reports = ( ('R0101', 'Statistics by type', report_by_type_stats), )
+
+ def __init__(self, linter):
+ BaseChecker.__init__(self, linter)
+ self.stats = None
+ self._returns = None
+
+ def open(self):
+ """initialize visit variables and statistics
+ """
+ self._returns = []
+ self.stats = self.linter.add_stats(module=0, function=0,
+ method=0, class_=0,
+ badname_module=0,
+ badname_class=0, badname_function=0,
+ badname_method=0, badname_attr=0,
+ badname_const=0,
+ badname_variable=0,
+ badname_inlinevar=0,
+ badname_argument=0,
+ undocumented_module=0,
+ undocumented_function=0,
+ undocumented_method=0,
+ undocumented_class=0)
+
+ def visit_module(self, node):
+ """check module name, docstring and required arguments
+ """
+ self.stats['module'] += 1
+ self._check_name('module', node.name.split('.')[-1], node)
+ self._check_docstring('module', node)
+ self._check_required_attributes(node, self.config.required_attributes)
+
+ def visit_class(self, node):
+ """check module name, docstring and redefinition
+ increment branch counter
+ """
+ self.stats['class'] += 1
+ self._check_name('class', node.name, node)
+ if self.config.no_docstring_rgx.match(node.name) is None:
+ self._check_docstring('class', node)
+ self._check_redefinition('class', node)
+ for attr, anodes in node.instance_attrs.items():
+ self._check_name('attr', attr, anodes[0])
+
+ def visit_discard(self, node):
+ """check for statement without effect"""
+ if not isinstance(node.expr, astng.CallFunc):
+ self.add_message('W0104', node=node)
+
+ def visit_function(self, node):
+ """check function name, docstring, arguments, redefinition,
+ variable names, max locals
+ """
+ is_method = node.is_method()
+ self._returns.append(0)
+ f_type = is_method and 'method' or 'function'
+ self.stats[f_type] += 1
+ # function name
+ self._check_name(f_type, node.name, node)
+ # docstring
+ if self.config.no_docstring_rgx.match(node.name) is None:
+ self._check_docstring(f_type, node)
+ # check default arguments'value
+ self._check_defaults(node)
+ # check arguments name
+ args = node.argnames
+ if args is not None:
+ self._recursive_check_names(args, node)
+ # check for redefinition
+ self._check_redefinition(is_method and 'method' or 'function', node)
+
+ def leave_function(self, node):
+ """most of the work is done here on close:
+ checks for max returns, branch, return in __init__
+ """
+ nb_returns = self._returns.pop()
+ if node.is_method() and node.name == '__init__' and nb_returns:
+ self.add_message('E0101', node=node)
+
+ def visit_assname(self, node):
+ """check module level assigned names"""
+ frame = node.frame()
+ ass_type = node.ass_type()
+ if isinstance(ass_type, (astng.ListCompFor, astng.GenExprFor)):
+ self._check_name('inlinevar', node.name, node)
+ elif isinstance(frame, astng.Module):
+ if isinstance(ass_type, astng.Assign) and not in_loop(ass_type):
+ self._check_name('const', node.name, node)
+ elif isinstance(frame, astng.Function):
+ # global introduced variable aren't in the function locals
+ if node.name in frame:
+ self._check_name('variable', node.name, node)
+
+ def visit_return(self, node):
+ """check is the node has a right sibling (if so, that's some unreachable
+ code)
+ """
+ self._returns[-1] += 1
+ self._check_unreachable(node)
+
+ def visit_yield(self, _):
+ """check is the node has a right sibling (if so, that's some unreachable
+ code)
+ """
+ self._returns[-1] += 1
+
+ def visit_continue(self, node):
+ """check is the node has a right sibling (if so, that's some unreachable
+ code)
+ """
+ self._check_unreachable(node)
+ self._check_in_loop(node, 'continue')
+
+ def visit_break(self, node):
+ """check is the node has a right sibling (if so, that's some unreachable
+ code)
+ """
+ self._check_unreachable(node)
+ self._check_in_loop(node, 'break')
+
+ def visit_raise(self, node):
+ """check is the node has a right sibling (if so, that's some unreachable
+ code)
+ """
+ self._check_unreachable(node)
+
+ def visit_exec(self, node):
+ """just pring a warning on exec statements"""
+ self.add_message('W0122', node=node)
+
+ def visit_callfunc(self, node):
+ """visit a CallFunc node -> check if this is not a blacklisted builtin
+ call and check for * or ** use
+ """
+ if isinstance(node.node, astng.Name):
+ name = node.node.name
+ # ignore the name if it's not a builtin (ie not defined in the
+ # locals nor globals scope)
+ if not (node.frame().has_key(name) or
+ node.root().has_key(name)):
+ if name in self.config.bad_functions:
+ self.add_message('W0141', node=node, args=name)
+ if node.star_args or node.dstar_args:
+ self.add_message('W0142', node=node.node)
+
+
+ def _check_unreachable(self, node):
+ """check unreachable code"""
+ unreach_stmt = node.next_sibling()
+ if unreach_stmt is not None:
+ self.add_message('W0101', node=unreach_stmt)
+
+ def _check_in_loop(self, node, node_name):
+ """check that a node is inside a for or while loop"""
+ _node = node.parent
+ while _node:
+ if isinstance(_node, (astng.For, astng.While)):
+ break
+ _node = _node.parent
+ else:
+ self.add_message('E0103', node=node, args=node_name)
+
+ def _check_redefinition(self, redef_type, node):
+ """check for redefinition of a function / method / class name"""
+ defined_self = node.parent.frame()[node.name]
+ if defined_self is not node and not are_exclusive(node, defined_self):
+ self.add_message('E0102', node=node,
+ args=(redef_type, defined_self.lineno))
+
+ def _check_docstring(self, node_type, node):
+ """check the node has a non empty docstring"""
+ docstring = node.doc
+ if docstring is None:
+ self.stats['undocumented_'+node_type] += 1
+ self.add_message('C0111', node=node)
+ elif not docstring.strip():
+ self.stats['undocumented_'+node_type] += 1
+ self.add_message('C0112', node=node)
+
+ def _recursive_check_names(self, args, node):
+ """check names in a possibly recursive list <arg>"""
+ for arg in args:
+ if type(arg) is type(''):
+ self._check_name('argument', arg, node)
+ else:
+ self._recursive_check_names(arg, node)
+
+ def _check_name(self, node_type, name, node):
+ """check for a name using the type's regexp"""
+ if name in self.config.good_names:
+ return
+ if name in self.config.bad_names:
+ self.stats['badname_' + node_type] += 1
+ self.add_message('C0102', node=node, args=name)
+ return
+ regexp = getattr(self.config, node_type + '_rgx')
+ if regexp.match(name) is None:
+ self.add_message('C0103', node=node, args=(name, regexp.pattern))
+ self.stats['badname_' + node_type] += 1
+
+
+ def _check_defaults(self, node):
+ """check for dangerous default values as arguments"""
+ for default in node.defaults:
+ try:
+ value = default.infer().next()
+ except astng.InferenceError:
+ continue
+ if isinstance(value, (astng.Dict, astng.List)):
+ if value is default:
+ msg = default.as_string()
+ else:
+ msg = '%s (%s)' % (default.as_string(), value.as_string())
+ self.add_message('W0102', node=node, args=(msg,))
+
+ def _check_required_attributes(self, node, attributes):
+ """check for required attributes"""
+ for attr in attributes:
+ if not node.has_key(attr):
+ self.add_message('C0121', node=node, args=attr)
+
+
+def register(linter):
+ """required method to auto register this checker"""
+ linter.register_checker(BasicChecker(linter))
+
diff --git a/checkers/classes.py b/checkers/classes.py
new file mode 100644
index 000000000..7c0e55178
--- /dev/null
+++ b/checkers/classes.py
@@ -0,0 +1,481 @@
+# Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""classes checker for Python code
+"""
+from __future__ import generators
+
+__revision__ = "$Id: classes.py,v 1.77 2006-03-05 14:39:37 syt Exp $"
+
+from logilab import astng
+
+from pylint.interfaces import IASTNGChecker
+from pylint.checkers import BaseChecker
+from pylint.checkers.utils import overrides_a_method
+
+MSGS = {
+## 'F0201': ('Unable to check method %r of interface %s',
+## 'Used when PyLint has been unable to fetch a
+ ##method declared in \
+## an interface (either in the class or in the
+ ##interface) and so to\
+## check its implementation.'),
+ 'F0202': ('Unable to check methods signature (%s / %s)',
+ 'Used when PyLint has been unable to check methods signature \
+ compatibility for an unexpected raison. Please report this kind \
+ if you don\'t make sense of it.'),
+## 'F0203': ('Unable to resolve %s',
+## 'Used when PyLint has been unable to resolve a name.'),
+## 'F0204': ('Name %s has not been resolved to a class as expected',
+## 'Used when PyLint try to resolve an ancestor class name but \
+## gets something else than a Class node.'),
+
+## 'E0201': ('Access to undefined member %r',
+## 'Used when an instance member not defined in the instance, its\
+## class or its ancestors is accessed.'),
+ 'E0202': ('An attribute inherited from %s hide this method',
+ 'Used when a class defines a method which is hiden by an \
+ instance attribute from an ancestor class.'),
+ 'E0203': ('Access to member %r before its definition line %s',
+ 'Used when an instance member is accessed before it\'s actually\
+ assigned.'),
+ 'W0201': ('Attribute %r defined outside __init__',
+ 'Used when an instance attribute is defined outside the __init__\
+ method.'),
+
+ 'E0211': ('Method has no argument',
+ 'Used when a method which should have the bound instance as \
+ first argument has no argument defined.'),
+ 'E0213': ('Method should have "self" as first argument',
+ 'Used when a method has an attribute different the "self" as\
+ first argument.'),
+ 'C0202': ('Class method should have "cls" as first argument', # E0212
+ 'Used when a class method has an attribute different than "cls"\
+ as first argument, to easily differentiate them from regular \
+ instance methods.'),
+ 'C0203': ('Metaclass method should have "mcs" as first argument', # E0214
+ 'Used when a metaclass method has an attribute different the \
+ "mcs" as first argument.'),
+ 'W0211': ('Static method with %r as first argument',
+ 'Used when a static method has "self" or "cls" as first argument.'
+ ),
+ 'R0201': ('Method could be a function',
+ 'Used when a method doesn\'t use its bound instance, and so could\
+ be written as a function.'
+ ),
+
+ 'E0221': ('Interface resolved to %s is not a class',
+ 'Used when a class claims to implement an interface which is not \
+ a class.'),
+ 'E0222': ('Missing method %r from %s interface' ,
+ 'Used when a method declared in an interface is missing from a \
+ class implementing this interface'),
+ 'W0221': ('Arguments number differs from %s method',
+ 'Used when a method has a different number of arguments than in \
+ the implemented interface or in an overriden method.'),
+ 'W0222': ('Signature differs from %s method',
+ 'Used when a method signature is different than in the \
+ implemented interface or in an overriden method.'),
+ 'W0223': ('Method %r is abstract in class %r but is not overriden',
+ 'Used when an abstract method (ie raise NotImplementedError) is \
+ not overriden in concrete class.'
+ ),
+ 'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224
+ 'Used when a PyLint as failed to find interfaces implemented by \
+ a class'),
+
+
+ 'W0231': ('__init__ method from base class %r is not called',
+ 'Used when an ancestor class method has an __init__ method \
+ which is not called by a derived class.'),
+ 'W0232': ('Class has no __init__ method',
+ 'Used when a class has no __init__ method, neither its parent \
+ classes.'),
+ 'W0233': ('__init__ method from a non direct base class %r is called',
+ 'Used when an __init__ method is called on a class which is not \
+ in the direct ancestors for the analysed class.'),
+
+ }
+
+
+class ClassChecker(BaseChecker):
+ """checks for :
+ * methods without self as first argument
+ * overriden methods signature
+ * access only to existant members via self
+ * attributes not defined in the __init__ method
+ * supported interfaces implementation
+ * unreachable code
+ """
+
+ __implements__ = (IASTNGChecker,)
+
+ # configuration section name
+ name = 'classes'
+ # messages
+ msgs = MSGS
+ priority = -2
+ # configuration options
+ options = (('ignore-iface-methods',
+ {'default' : (#zope interface
+ 'isImplementedBy', 'deferred', 'extends', 'names',
+ 'namesAndDescriptions', 'queryDescriptionFor', 'getBases',
+ 'getDescriptionFor', 'getDoc', 'getName', 'getTaggedValue',
+ 'getTaggedValueTags', 'isEqualOrExtendedBy', 'setTaggedValue',
+ 'isImplementedByInstancesOf',
+ # twisted
+ 'adaptWith',
+ # logilab.common interface
+ 'is_implemented_by'),
+ 'type' : 'csv',
+ 'metavar' : '<method names>',
+ 'help' : 'List of interface methods to ignore, \
+separated by a comma. This is used for instance to not check methods defines \
+in Zope\'s Interface base class.'}
+ ),
+
+ ('defining-attr-methods',
+ {'default' : ('__init__', '__new__', 'setUp'),
+ 'type' : 'csv',
+ 'metavar' : '<method names>',
+ 'help' : 'List of method names used to declare (i.e. assign) \
+instance attributes.'}
+ ),
+
+ )
+
+ def __init__(self, linter=None):
+ BaseChecker.__init__(self, linter)
+ self._accessed = []
+ self._first_attrs = []
+ self._meth_could_be_func = None
+
+ def visit_class(self, node):
+ """init visit variable _accessed and check interfaces
+ """
+ self._accessed.append({})
+ self._check_bases_classes(node)
+ self._check_interfaces(node)
+ # if not an interface, exception, metaclass
+ if node.type == 'class':
+ try:
+ node.local_attr('__init__')
+ except astng.NotFoundError:
+ self.add_message('W0232', args=node, node=node)
+
+ def leave_class(self, cnode):
+ """close a class node:
+ check that instance attributes are defined in __init__ and check
+ access to existant members
+ """
+ # checks attributes are defined in an allowed method such as __init__
+ defining_methods = self.config.defining_attr_methods
+ for attr, nodes in cnode.instance_attrs.items():
+ node = nodes[0] # XXX
+ frame = node.frame()
+ if frame.name not in defining_methods:
+ # check attribute is defined in a parent's __init__
+ for parent in cnode.instance_attr_ancestors(attr):
+ frame = parent.instance_attrs[attr][0].frame() # XXX
+ if frame.name in defining_methods:
+ # we're done :)
+ break
+ else:
+ # check attribute is defined as a class attribute
+ try:
+ cnode.local_attr(attr)
+ except astng.NotFoundError:
+ self.add_message('W0201', args=attr, node=node)
+ # check access to existant members on non metaclass classes
+ accessed = self._accessed.pop()
+ if cnode.type != 'metaclass':
+ self._check_accessed_members(cnode, accessed)
+
+ def visit_function(self, node):
+ """check method arguments, overriding"""
+ # ignore actual functions
+ if not node.is_method():
+ return
+ self._meth_could_be_func = True
+ # check first argument is self if this is actually a method
+ klass = node.parent.frame()
+ self._check_first_arg_for_type(node, klass.type == 'metaclass')
+ if node.name == '__init__':
+ self._check_init(node)
+ return
+ # check signature if the method overrload an herited method
+ for overriden in klass.local_attr_ancestors(node.name):
+ # get astng for the searched method
+ try:
+ meth_node = overriden[node.name]
+ except KeyError:
+ # we have found the method but it's not in the local
+ # dictionnary.
+ # This may happen with astng build from living objects
+ continue
+ if not isinstance(meth_node, astng.Function):
+ continue
+ self._check_signature(node, meth_node, 'overriden')
+ break
+ # check if the method overload an attribute
+ try:
+ overriden = klass.instance_attr(node.name)[0] # XXX
+ while not isinstance(overriden, astng.Class):
+ overriden = overriden.parent.frame()
+ self.add_message('E0202', args=overriden.name, node=node)
+ except astng.NotFoundError:
+ pass
+
+ def leave_function(self, node):
+ """on method node, check if this method couldn't be a function
+
+ ignore class, static and abstract methods, initializer,
+ methods overriden from a parent class and any
+ kind of method defined in an interface for this warning
+ """
+ if node.is_method():
+ if node.argnames is not None:
+ self._first_attrs.pop()
+ class_node = node.parent.frame()
+ if (self._meth_could_be_func and node.type == 'method'
+ and node.name != '__init__'
+ and not (node.is_abstract() or
+ overrides_a_method(class_node, node.name))
+ and class_node.type != 'interface'):
+ self.add_message('R0201', node=node)
+
+ def visit_getattr(self, node):
+ """check if the name handle an access to a class member
+ if so, register it
+ """
+ if self._first_attrs and isinstance(node.expr, astng.Name) and \
+ node.expr.name == self._first_attrs[-1]:
+ self._accessed[-1].setdefault(node.attrname, []).append(node)
+
+ def visit_name(self, node):
+ """check if the name handle an access to a class member
+ if so, register it
+ """
+ if self._first_attrs and (node.name == self._first_attrs[-1] or
+ not self._first_attrs[-1]):
+ self._meth_could_be_func = False
+
+ def _check_accessed_members(self, node, accessed):
+ """check that accessed members are defined"""
+ # XXX refactor, probably much simpler now that E0201 is in type checker
+ for attr, nodes in accessed.items():
+## # is it a builtin attribute ?
+## if attr in ('__dict__', '__class__', '__doc__'):
+## # FIXME: old class object doesn't have __class__
+## continue
+ # is it a class attribute ?
+ try:
+ node.local_attr(attr)
+ # yes, stop here
+ continue
+ except astng.NotFoundError:
+ pass
+ # is it an instance attribute of a parent class ?
+ try:
+ node.instance_attr_ancestors(attr).next()
+ # yes, stop here
+ continue
+ except StopIteration:
+ pass
+ # is it an instance attribute ?
+ try:
+ def_nodes = node.instance_attr(attr) # XXX
+ instance_attribute = True
+ except astng.NotFoundError:
+ instance_attribute = False
+ else:
+ if len(def_nodes) == 1:
+ def_node = def_nodes[0]
+ # check that if the node is accessed in the same method as
+ # it's defined, it's accessed after the initial assigment
+ frame = def_node.frame()
+ lno = def_node.source_line()
+ for _node in nodes:
+ if _node.frame() is frame and _node.lineno < lno:
+ self.add_message('E0203', node=_node,
+ args=(attr, lno))
+
+ def _check_first_arg_for_type(self, node, metaclass=0):
+ """check the name of first argument, expect:
+
+ * 'self' for a regular method
+ * 'cls' for a class method
+ * 'mcs' for a metaclass
+ * not one of the above for a static method
+ """
+ # don't care about functions with unknown argument (builtins)
+ if node.argnames is None:
+ return
+ self._first_attrs.append(node.argnames and node.argnames[0])
+ # static method
+ if node.type == 'staticmethod':
+ if node.argnames and node.argnames[0] in ('self', 'cls', 'mcs'):
+ self.add_message('W0211', args=node.argnames[0], node=node)
+ self._first_attrs[-1] = None
+ # class / regular method with no args
+ elif not node.argnames:
+ self.add_message('E0211', node=node)
+ # metaclass method
+ elif metaclass:
+ if self._first_attrs[-1] != 'mcs':
+ self.add_message('C0203', node=node)
+ # class method
+ elif node.type == 'classmethod':
+ if self._first_attrs[-1] != 'cls':
+ self.add_message('C0202', node=node)
+ # regular method without self as argument
+ elif self._first_attrs[-1] != 'self':
+ self.add_message('E0213', node=node)
+
+ def _check_bases_classes(self, node):
+ """check that the given class node implements abstract methods from
+ base classes
+ """
+ for method in node.methods():
+ owner = method.parent.frame()
+ if owner is node:
+ continue
+ # owner is not this class, it must be a parent class
+ # check that the ancestor's method is not abstract
+ if method.is_abstract(pass_is_abstract=False):
+ self.add_message('W0223', node=node,
+ args=(method.name, owner.name))
+
+ def _check_interfaces(self, node):
+ """check that the given class node really implements declared
+ interfaces
+ """
+ e0221_hack = [False]
+ def iface_handler(obj):
+ """filter interface objects, it should be classes"""
+ if not isinstance(obj, astng.Class):
+ e0221_hack[0] = True
+ self.add_message('E0221', node=node,
+ args=(obj.as_string(),))
+ return False
+ return True
+ ignore_iface_methods = self.config.ignore_iface_methods
+ try:
+ for iface in node.interfaces(handler_func=iface_handler):
+ for imethod in iface.methods():
+ name = imethod.name
+ if name.startswith('_') or name in ignore_iface_methods:
+ # don't check method begining with an underscore,
+ # usually belonging to the interface implementation
+ continue
+ # get class method astng
+ try:
+ method = node_method(node, name)
+ except astng.NotFoundError:
+ self.add_message('E0222', args=(name, iface.name),
+ node=node)
+ continue
+ # ignore inherited methods
+ if method.parent.frame() is not node:
+ continue
+ # check signature
+ self._check_signature(method, imethod,
+ '%s interface' % iface.name)
+ except astng.InferenceError:
+ if e0221_hack[0]:
+ return
+ implements = astng.Instance(node).getattr('__implements__')[0]
+ assignment = implements.parent
+ assert isinstance(assignment, astng.Assign)
+ # assignment.expr can be a Name or a Tupe or whatever.
+ # Use as_string() for the message
+ # FIXME: in case of multiple interfaces, find which one could not
+ # be resolved
+ self.add_message('F0220', node=implements,
+ args=(node.name, assignment.expr.as_string()))
+
+ def _check_init(self, node):
+ """check that the __init__ method call super or ancestors'__init__
+ method
+ """
+ klass_node = node.parent.frame()
+ to_call = _ancestors_to_call(klass_node)
+ for stmt in node.nodes_of_class(astng.CallFunc):
+ expr = stmt.node
+ if not isinstance(expr, astng.Getattr) \
+ or expr.attrname != '__init__':
+ continue
+ # skip the test if using super
+ if isinstance(expr.expr, astng.CallFunc) and \
+ isinstance(expr.expr.node, astng.Name) and \
+ expr.expr.node.name == 'super':
+ return
+ try:
+ klass = expr.expr.infer().next()
+ try:
+ del to_call[klass]
+ except KeyError:
+ self.add_message('W0233', node=expr, args=klass.name)
+ except astng.InferenceError, ex:
+ continue
+ for klass in to_call.keys():
+ if klass.name == 'object':
+ continue
+ self.add_message('W0231', args=klass.name, node=node)
+
+ def _check_signature(self, method1, method2, class_type):
+ """check that the signature of the two given methods match
+
+ class_type is in 'class', 'interface'
+ """
+ if not (isinstance(method1, astng.Function)
+ and isinstance(method2, astng.Function)):
+ self.add_message('F0202', args=(method1, method2), node=method1)
+ return
+ # don't care about functions with unknown argument (builtins)
+ if method1.argnames is None or method2.argnames is None:
+ return
+ if len(method1.argnames) != len(method2.argnames):
+ self.add_message('W0221', args=class_type, node=method1)
+ elif len(method1.defaults) != len(method2.defaults):
+ self.add_message('W0222', args=class_type, node=method1)
+
+
+def _ancestors_to_call(klass_node, method='__init__'):
+ """return a dictionary where keys are the list of base classes providing
+ the queried method, and so that should/may be called from the method node
+ """
+ to_call = {}
+ for base_node in klass_node.ancestors(recurs=False):
+ try:
+ base_node.local_attr(method)
+ to_call[base_node] = 1
+ except astng.NotFoundError:
+ continue
+ return to_call
+
+
+def node_method(node, method_name):
+ """get astng for <method_name> on the given class node, ensuring it
+ is a Function node
+ """
+ stmt = node.local_attr(method_name)
+ if not isinstance(stmt, astng.Function):
+ raise astng.NotFoundError(method_name)
+ return stmt
+
+def register(linter):
+ """required method to auto register this checker """
+ linter.register_checker(ClassChecker(linter))
diff --git a/checkers/design_analysis.py b/checkers/design_analysis.py
new file mode 100644
index 000000000..1d51e7438
--- /dev/null
+++ b/checkers/design_analysis.py
@@ -0,0 +1,322 @@
+# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""check for signs of poor design
+
+
+ see http://intranet.logilab.fr/jpl/view?rql=Any%20X%20where%20X%20eid%201243
+ FIXME: missing 13, 15, 16
+"""
+
+__revision__ = "$Id: design_analysis.py,v 1.11 2005-12-30 15:41:29 adim Exp $"
+
+from logilab.astng import Function, InferenceError
+
+from pylint.interfaces import IASTNGChecker
+from pylint.checkers import BaseChecker
+
+def class_is_abstract(klass):
+ """return true if the given class node should be considered as an abstract
+ class
+ """
+ for attr in klass.values():
+ if isinstance(attr, Function):
+ if attr.is_abstract(pass_is_abstract=False):
+ return True
+ return False
+
+
+MSGS = {
+ 'R0901': ('Too many ancestors (%s/%s)',
+ 'Used when class has too many parent classes.'),
+ 'R0902': ('Too many instance attributes (%s/%s)',
+ 'Used when class has too many instance attributes.'),
+ 'R0903': ('Not enough public methods (%s/%s)',
+ 'Used when class has not enough public methods.'),
+ 'R0904': ('Too many public methods (%s/%s)',
+ 'Used when class has too many public methods.'),
+
+ 'R0911': ('Too many return statements (%s/%s)',
+ 'Used when a function or method has too many return statement.'),
+ 'R0912': ('Too many branches (%s/%s)',
+ 'Used when a function or method has too many branches.'),
+ 'R0913': ('Too many arguments (%s/%s)',
+ 'Used when a function or method takes too many arguments.'),
+ 'R0914': ('Too many local variables (%s/%s)',
+ 'Used when a function or method has too many local variables.'),
+ 'R0915': ('Too many statements (%s/%s)',
+ 'Used when a function or method has too many statements. You \
+ should then split it in smaller functions / methods.'),
+
+ 'R0921': ('Abstract class not referenced',
+ 'Used when an abstract class is not used as ancestor anywhere.'),
+ 'R0922': ('Abstract class is only referenced %s times',
+ 'Used when an abstract class is used less than X times as \
+ ancestor.'),
+ 'R0923': ('Interface not implemented',
+ 'Used when an interface class is not implemented anywhere.'),
+ }
+
+
+class MisdesignChecker(BaseChecker):
+ """checks for sign of poor/misdesign:
+ * number of methods, attributes, local variables...
+ * size, complexity of functions, methods
+ """
+
+ __implements__ = (IASTNGChecker,)
+
+ # configuration section name
+ name = 'design'
+ # messages
+ msgs = MSGS
+ priority = -2
+ # configuration options
+ options = (('max-args',
+ {'default' : 5, 'type' : 'int', 'metavar' : '<int>',
+ 'help': 'Maximum number of arguments for function / method'}
+ ),
+ ('max-locals',
+ {'default' : 15, 'type' : 'int', 'metavar' : '<int>',
+ 'help': 'Maximum number of locals for function / method body'}
+ ),
+ ('max-returns',
+ {'default' : 6, 'type' : 'int', 'metavar' : '<int>',
+ 'help': 'Maximum number of return / yield for function / '
+ 'method body'}
+ ),
+ ('max-branchs',
+ {'default' : 12, 'type' : 'int', 'metavar' : '<int>',
+ 'help': 'Maximum number of branch for function / method body'}
+ ),
+ ('max-statements',
+ {'default' : 50, 'type' : 'int', 'metavar' : '<int>',
+ 'help': 'Maximum number of statements in function / method '
+ 'body'}
+ ),
+ ('max-parents',
+ {'default' : 7,
+ 'type' : 'int',
+ 'metavar' : '<num>',
+ 'help' : 'Maximum number of parents for a class (see R0901).'}
+ ),
+ ('max-attributes',
+ {'default' : 7,
+ 'type' : 'int',
+ 'metavar' : '<num>',
+ 'help' : 'Maximum number of attributes for a class \
+(see R0902).'}
+ ),
+ ('min-public-methods',
+ {'default' : 2,
+ 'type' : 'int',
+ 'metavar' : '<num>',
+ 'help' : 'Minimum number of public methods for a class \
+(see R0903).'}
+ ),
+ ('max-public-methods',
+ {'default' : 20,
+ 'type' : 'int',
+ 'metavar' : '<num>',
+ 'help' : 'Maximum number of public methods for a class \
+(see R0904).'}
+ ),
+ )
+
+ def __init__(self, linter=None):
+ BaseChecker.__init__(self, linter)
+ self.stats = None
+ self._returns = None
+ self._branchs = None
+ self._used_abstracts = None
+ self._used_ifaces = None
+ self._abstracts = None
+ self._ifaces = None
+ self._stmts = 0
+
+ def open(self):
+ """initialize visit variables"""
+ self.stats = self.linter.add_stats()
+ self._returns = []
+ self._branchs = []
+ self._used_abstracts = {}
+ self._used_ifaces = {}
+ self._abstracts = []
+ self._ifaces = []
+
+ def close(self):
+ """check that abstract/interface classes are used"""
+ for abstract in self._abstracts:
+ if not abstract in self._used_abstracts:
+ self.add_message('R0921', node=abstract)
+ elif self._used_abstracts[abstract] < 2:
+ self.add_message('R0922', node=abstract,
+ args=self._used_abstracts[abstract])
+ for iface in self._ifaces:
+ if not iface in self._used_ifaces:
+ self.add_message('R0923', node=iface)
+
+ def visit_class(self, node):
+ """check size of inheritance hierarchy and number of instance attributes
+ """
+ self._inc_branch()
+ # Is the total inheritance hierarchy is 7 or less?
+ nb_parents = len(list(node.ancestors()))
+ if nb_parents > self.config.max_parents:
+ self.add_message('R0901', node=node,
+ args=(nb_parents, self.config.max_parents))
+ # Does the class contain less than 20 attributes for
+ # non-GUI classes (40 for GUI)?
+ # FIXME detect gui classes
+ if len(node.instance_attrs) > self.config.max_attributes:
+ self.add_message('R0902', node=node,
+ args=(len(node.instance_attrs),
+ self.config.max_attributes))
+ # update abstract / interface classes structures
+ if class_is_abstract(node):
+ self._abstracts.append(node)
+ elif node.type == 'interface' and node.name != 'Interface':
+ self._ifaces.append(node)
+ for parent in node.ancestors(False):
+ if parent.name == 'Interface':
+ continue
+ self._used_ifaces[parent] = 1
+ try:
+ for iface in node.interfaces():
+ self._used_ifaces[iface] = 1
+ except InferenceError:
+ # XXX log ?
+ pass
+ for parent in node.ancestors():
+ try:
+ self._used_abstracts[parent] += 1
+ except KeyError:
+ self._used_abstracts[parent] = 1
+
+ def leave_class(self, node):
+ """check number of public methods"""
+ nb_public_methods = 0
+ for method in node.methods():
+ if not method.name.startswith('_'):
+ nb_public_methods += 1
+ # Does the class contain less than 20 public methods ?
+ if nb_public_methods > self.config.max_public_methods:
+ self.add_message('R0904', node=node,
+ args=(nb_public_methods,
+ self.config.max_public_methods))
+ # stop here for exception, metaclass and interface classes
+ if node.type != 'class':
+ return
+ # Does the class contain more than 5 public methods ?
+ if nb_public_methods < self.config.min_public_methods:
+ self.add_message('R0903', node=node,
+ args=(nb_public_methods,
+ self.config.min_public_methods))
+
+
+ def visit_function(self, node):
+ """check function name, docstring, arguments, redefinition,
+ variable names, max locals
+ """
+ self._inc_branch()
+ # init branch and returns counters
+ self._returns.append(0)
+ self._branchs.append(0)
+ # check number of arguments
+ args = node.argnames
+ if args is not None and len(args) > self.config.max_args:
+ self.add_message('R0913', node=node,
+ args=(len(args), self.config.max_args))
+ # check number of local variables
+ locnum = len(node.locals)
+ if locnum > self.config.max_locals:
+ self.add_message('R0914', node=node,
+ args=(locnum, self.config.max_locals))
+ # init statements counter
+ self._stmts = 1
+
+ def leave_function(self, node):
+ """most of the work is done here on close:
+ checks for max returns, branch, return in __init__
+ """
+ returns = self._returns.pop()
+ if returns > self.config.max_returns:
+ self.add_message('R0911', node=node,
+ args=(returns, self.config.max_returns))
+ branchs = self._branchs.pop()
+ if branchs > self.config.max_branchs:
+ self.add_message('R0912', node=node,
+ args=(branchs, self.config.max_branchs))
+ # check number of statements
+ if self._stmts > self.config.max_statements:
+ self.add_message('R0915', node=node,
+ args=(self._stmts, self.config.max_statements))
+
+ def visit_return(self, _):
+ """count number of returns/yields"""
+ self._returns[-1] += 1
+
+ def visit_yield(self, _):
+ """count number of returns/yields"""
+ self._returns[-1] += 1
+
+ def visit_default(self, node):
+ """default visit method -> increments the statements counter if
+ necessary
+ """
+ if node.is_statement():
+ self._stmts += 1
+
+ def visit_tryexcept(self, node):
+ """increments the branchs counter"""
+ branchs = len(node.handlers)
+ if node.else_:
+ branchs += 1
+ self._inc_branch(branchs)
+ self._stmts += branchs
+
+ def visit_tryfinally(self, _):
+ """increments the branchs counter"""
+ self._inc_branch(2)
+ self._stmts += 2
+
+ def visit_if(self, node):
+ """increments the branchs counter"""
+ branchs = len(node.tests)
+ if node.else_:
+ branchs += 1
+ self._inc_branch(branchs)
+ self._stmts += branchs
+
+ def visit_while(self, node):
+ """increments the branchs counter"""
+ branchs = 1
+ if node.else_:
+ branchs += 1
+ self._inc_branch(branchs)
+
+ visit_for = visit_while
+
+ def _inc_branch(self, branchsnum=1):
+ """increments the branchs counter"""
+ branchs = self._branchs
+ for i in xrange(len(branchs)):
+ branchs[i] += branchsnum
+
+ # FIXME: make a nice report...
+
+def register(linter):
+ """required method to auto register this checker """
+ linter.register_checker(MisdesignChecker(linter))
diff --git a/checkers/exceptions.py b/checkers/exceptions.py
new file mode 100644
index 000000000..0f5fddce4
--- /dev/null
+++ b/checkers/exceptions.py
@@ -0,0 +1,167 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+exceptions checkers for Python code
+"""
+
+__revision__ = '$Id: exceptions.py,v 1.27 2006-03-08 15:53:42 syt Exp $'
+
+from logilab.common.compat import enumerate
+from logilab import astng
+from logilab.astng.inference import unpack_infer
+
+from pylint.checkers import BaseChecker
+from pylint.checkers.utils import is_empty
+from pylint.interfaces import IASTNGChecker
+
+MSGS = {
+ 'E0701': (
+ 'Bad except clauses order (%s)',
+ 'Used when except clauses are not in the correct order (from the \
+ more specific to the more generic). If you don\'t fix the order, \
+ some exceptions may not be catched by the most specific handler.'),
+ 'E0702': ('Raising %s while only classes, instances or string are allowed',
+ 'Used when something which is neither a class, an instance or a \
+ string is raised (i.e. a `TypeError` will be raised).'),
+
+ 'W0701': ('Raising a string exception',
+ 'Used when a string exception is raised.'),
+ 'W0702': ('No exception\'s type specified',
+ 'Used when an except clause doesn\'t specify exceptions type to \
+ catch.'),
+ 'W0703': ('Catch "Exception"',
+ 'Used when an except catch Exception instances.'),
+ 'W0704': ('Except doesn\'t do anything',
+ 'Used when an except clause does nothing but "pass" and there is\
+ no "else" clause.'),
+ 'W0706': (
+ 'Identifier %s used to raise an exception is assigned to %s',
+ 'Used when a variable used to raise an exception is initially \
+ assigned to a value which can\'t be used as an exception.'),
+ }
+
+def is_raising(stmt):
+ """return true if the given statement node raise an exception
+ """
+ for node in stmt.nodes:
+ if isinstance(node, astng.Raise):
+ return 1
+ return 0
+
+class ExceptionsChecker(BaseChecker):
+ """checks for
+ * excepts without exception filter
+ * string exceptions
+ """
+
+ __implements__ = IASTNGChecker
+
+ name = 'exceptions'
+ msgs = MSGS
+ priority = -4
+ options = ()
+
+ def visit_raise(self, node):
+ """check for string exception
+ """
+ # ignore empty raise
+ if node.expr1 is None:
+ return
+ expr = node.expr1
+ if isinstance(expr, astng.Const):
+ value = expr.value
+ if isinstance(value, str):
+ self.add_message('W0701', node=node)
+ else:
+ self.add_message('E0702', node=node,
+ args=value.__class__.__name__)
+ elif isinstance(expr, astng.Name) and \
+ expr.name in ('None', 'True', 'False'):
+ self.add_message('E0702', node=node, args=expr.name)
+ elif isinstance(expr, astng.Mod):
+ self.add_message('W0701', node=node)
+ else:
+ try:
+ value = unpack_infer(expr).next()
+ except astng.InferenceError:
+ return
+ if value is astng.YES:
+ return
+ # must to be carefull since Const, Dict, .. inherit from
+ # Instance now
+ if isinstance(value, (astng.Class, astng.Module)):
+ return
+ if isinstance(value, astng.Instance) and \
+ isinstance(value._proxied, astng.Class):
+ return
+ if isinstance(value, astng.Const) and \
+ (value.value is None or
+ value.value is True or value.value is False):
+ # this Const has been generated by resolve
+ # since None, True and False are represented by Name
+ # nodes in the ast, and so this const node doesn't
+ # have the necessary parent, lineno and so on attributes
+ assinfo = value.as_string()
+ else:
+ assinfo = '%s line %s' % (value.as_string(),
+ value.source_line())
+ self.add_message('W0706', node=node,
+ args=(expr.as_string(), assinfo))
+
+ def visit_tryexcept(self, node):
+ """check for empty except
+ """
+ exceptions_classes = []
+ nb_handlers = len(node.handlers)
+ for index, handler in enumerate(node.handlers):
+ exc_type = handler[0]
+ stmt = handler[2]
+ # single except doing nothing but "pass" without else clause
+ if nb_handlers == 1 and is_empty(stmt) and not node.else_:
+ self.add_message('W0704', node=exc_type)
+ if exc_type is None:
+ if nb_handlers == 1 and not is_raising(stmt):
+ self.add_message('W0702', node=stmt.nodes[0])
+ # check if a "except:" is followed by some other
+ # except
+ elif index < (nb_handlers - 1):
+ msg = 'empty except clause should always appears last'
+ self.add_message('E0701', node=node, args=msg)
+ else:
+ try:
+ excs = list(unpack_infer(exc_type))
+ except astng.InferenceError:
+ continue
+ for exc in excs:
+ if exc is astng.YES:
+ continue
+ if not isinstance(exc, astng.Class):
+ continue # XXX
+ exc_ancestors = [anc for anc in exc.ancestors()
+ if isinstance(anc, astng.Class)]
+ for previous_exc in exceptions_classes:
+ if previous_exc in exc_ancestors:
+ msg = '%s is an ancestor class of %s' % (
+ previous_exc.name, exc.name)
+ self.add_message('E0701', node=exc_type, args=msg)
+ if (exc.name == 'Exception'
+ and exc.root().name == 'exceptions'
+ and nb_handlers == 1 and not is_raising(stmt)):
+ self.add_message('W0703', node=exc_type)
+ exceptions_classes += excs
+
+def register(linter):
+ """required method to auto register this checker"""
+ linter.register_checker(ExceptionsChecker(linter))
diff --git a/checkers/format.py b/checkers/format.py
new file mode 100644
index 000000000..9cfd63df4
--- /dev/null
+++ b/checkers/format.py
@@ -0,0 +1,325 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+Python code format's checker.
+
+By default try to follow Guido's style guide :
+
+http://www.python.org/doc/essays/styleguide.html
+
+Some parts of the process_token method is based from The Tab Nanny std module.
+"""
+
+__revision__ = "$Id: format.py,v 1.51 2006-03-14 15:08:10 syt Exp $"
+
+import re
+import tokenize
+if not hasattr(tokenize, 'NL'):
+ raise ValueError("tokenize.NL doesn't exist -- tokenize module too old")
+
+from logilab.common.textutils import pretty_match
+
+from pylint.interfaces import IRawChecker, IASTNGChecker
+from pylint.checkers import BaseRawChecker
+
+MSGS = {
+ 'C0301': ('Line too long (%s/%s)',
+ 'Used when a line is longer than a given number of characters.'),
+ 'W0302': ('Too many lines in module (%s)',
+ 'Used when a module has too much lines, reducing its readibility.'
+ ),
+
+ 'W0311': ('Bad indentation. Found %s %s, expected %s',
+ 'Used when an unexpected number of indentation\'s tabulations or\
+ spaces has been found.'),
+ 'W0312': ('Found indentation with %ss instead of %ss',
+ 'Used when there are some mixed tabs and spaces in a module.'),
+
+ 'F0321': ('Format detection error in %r',
+ 'Used when an unexpected error occured in bad format detection.\
+ Please report the error if it occurs.'),
+ 'C0321': ('More than one statement on a single line',
+ 'Used when more than on statement are found on the same line.'),
+ 'C0322': ('Operator not preceded by a space\n%s',
+ 'Used when one of the following operator (!= | <= | == | >= | < \
+ | > | = | \+= | -= | \*= | /= | %) is not preceded by a space.'),
+ 'C0323': ('Operator not followed by a space\n%s',
+ 'Used when one of the following operator (!= | <= | == | >= | < \
+ | > | = | \+= | -= | \*= | /= | %) is not followed by a space.'),
+ 'C0324': ('Comma not followed by a space\n%s',
+ 'Used when a comma (",") is not followed by a space.'),
+
+ 'W0331': ('Use of the <> operator',
+ 'Used when the deprecated "<>" operator is used instead \
+ of "!=".'),
+ 'W0332': ('Use l as long integer identifier',
+ 'Used when a lower case "l" is used to mark a long integer. You \
+should use a upper case "L" since the letter "l" looks too much like the digit \
+"1"'),
+ }
+
+# simple quoted string rgx
+SQSTRING_RGX = r'"([^"\\]|\\.)*("|\\$)'
+# triple quoted string rgx
+TQSTRING_RGX = r'"""([^"]|"(?!""))*("""|$)'
+# simple apostrophed rgx
+SASTRING_RGX = r"'([^'\\]|\\.)*('|\\$)"
+# triple apostrophed string rgx # FIXMY english please
+TASTRING_RGX = r"'''([^']|'(?!''))*('''|$)"
+
+# finally, the string regular expression
+STRING_RGX = re.compile('%s|%s|%s|%s' % (TQSTRING_RGX, TASTRING_RGX,
+ SQSTRING_RGX, SASTRING_RGX))
+
+COMMENT_RGX = re.compile("#.*$")
+
+OPERATORS = r'!=|<=|==|>=|<|>|=|\+=|-=|\*=|/=|%'
+
+OP_RGX_MATCH_1 = r'[^(]*(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s).*' % OPERATORS
+OP_RGX_SEARCH_1 = r'(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s)' % OPERATORS
+
+OP_RGX_MATCH_2 = r'[^(]*(%s)(?!\s|=|>|<).*' % OPERATORS
+OP_RGX_SEARCH_2 = r'(%s)(?!\s|=|>)' % OPERATORS
+
+BAD_CONSTRUCT_RGXS = (
+## (re.compile(
+## r'\s*(class|def|if|for|while)\s+([^:\[\]]|\[.*:?.*\])*?:\s*\w+(\s|\w)*'),
+## re.compile(r':\s*[^\s]+.*'),
+## 'C0321'),
+
+ (re.compile(OP_RGX_MATCH_1),
+ re.compile(OP_RGX_SEARCH_1),
+ 'C0322'),
+
+ (re.compile(OP_RGX_MATCH_2),
+ re.compile(OP_RGX_SEARCH_2),
+ 'C0323'),
+
+ (re.compile(r'.*,[^\s)].*'),
+ re.compile(r',[^\s)]'),
+ 'C0324'),
+ )
+
+
+def get_string_coords(line):
+ """return a list of string positions (tuple (start, end)) in the line
+ """
+ result = []
+ for match in re.finditer(STRING_RGX, line):
+ result.append( (match.start(), match.end()) )
+ return result
+
+def in_coords(match, string_coords):
+ """return true if the match in in the string coord
+ """
+ mstart = match.start()
+ for start, end in string_coords:
+ if mstart >= start and mstart < end:
+ return 1
+ return 0
+
+def check_line(line, writer):
+ """check a line for a bad construction
+ if it founds one, return a message describing the problem
+ else return None
+ """
+ clean_str = STRING_RGX.sub('', line)
+ clean_str = COMMENT_RGX.sub('', clean_str)
+ for rgx_match, rgx_search, msg_id in BAD_CONSTRUCT_RGXS:
+ if rgx_match.match(clean_str):
+ string_positions = get_string_coords(line)
+ for match in re.finditer(rgx_search, line):
+ if not in_coords(match, string_positions):
+ return msg_id, pretty_match(match, line.rstrip())
+ writer.add_message('F0321', line=line, args=line)
+
+
+
+class FormatChecker(BaseRawChecker):
+ """checks for :
+ * unauthorized constructions
+ * strict indentation
+ * line length
+ * use of <> instead of !=
+ """
+
+ __implements__ = (IRawChecker, IASTNGChecker)
+
+ # configuration section name
+ name = 'format'
+ # messages
+ msgs = MSGS
+ # configuration options
+ # for available dict keys/values see the optik parser 'add_option' method
+ options = (('max-line-length',
+ {'default' : 80, 'type' : "int", 'metavar' : '<int>',
+ 'help' : 'Maximum number of characters on a single line.'}),
+ ('max-module-lines',
+ {'default' : 1000, 'type' : 'int', 'metavar' : '<int>',
+ 'help': 'Maximum number of lines in a module'}
+ ),
+ ('indent-string',
+ {'default' : ' ', 'type' : "string", 'metavar' : '<string>',
+ 'help' : 'String used as indentation unit. This is usually \
+" " (4 spaces) or "\\t" (1 tab).'}),
+ )
+ def __init__(self, linter=None):
+ BaseRawChecker.__init__(self, linter)
+ self._lines = None
+ self._visited_lines = None
+
+ def new_line(self, tok_type, line, line_num, junk):
+ """a new line has been encountered, process it if necessary"""
+ if not tok_type in junk:
+ self._lines[line_num] = line
+ self.check_lines(line, line_num)
+
+ def process_tokens(self, tokens):
+ """process tokens and search for :
+
+ _ non strict indentation (i.e. not always using the <indent> parameter as
+ indent unit)
+ _ too long lines (i.e. longer than <max_chars>)
+ _ optionally bad construct (if given, bad_construct must be a compiled
+ regular expression).
+ """
+ indent = tokenize.INDENT
+ dedent = tokenize.DEDENT
+ newline = tokenize.NEWLINE
+ junk = (tokenize.COMMENT, tokenize.NL)
+ indents = [0]
+ check_equal = 0
+ line_num = 0
+ self._lines = {}
+ self._visited_lines = {}
+ for (tok_type, token, start, _, line) in tokens:
+ if start[0] != line_num:
+ line_num = start[0]
+ self.new_line(tok_type, line, line_num, junk)
+ if tok_type == tokenize.OP:
+ if token == '<>':
+ self.add_message('W0331', line=line_num)
+ elif tok_type == tokenize.NUMBER:
+ if token.endswith('l'):
+ self.add_message('W0332', line=line_num)
+
+ elif tok_type == newline:
+ # a program statement, or ENDMARKER, will eventually follow,
+ # after some (possibly empty) run of tokens of the form
+ # (NL | COMMENT)* (INDENT | DEDENT+)?
+ # If an INDENT appears, setting check_equal is wrong, and will
+ # be undone when we see the INDENT.
+ check_equal = 1
+
+ elif tok_type == indent:
+ check_equal = 0
+ self.check_indent_level(token, indents[-1]+1, line_num)
+ indents.append(indents[-1]+1)
+
+ elif tok_type == dedent:
+ # there's nothing we need to check here! what's important is
+ # that when the run of DEDENTs ends, the indentation of the
+ # program statement (or ENDMARKER) that triggered the run is
+ # equal to what's left at the top of the indents stack
+ check_equal = 1
+ if len(indents) > 1:
+ del indents[-1]
+
+ elif check_equal and tok_type not in junk:
+ # this is the first "real token" following a NEWLINE, so it
+ # must be the first token of the next program statement, or an
+ # ENDMARKER; the "line" argument exposes the leading whitespace
+ # for this statement; in the case of ENDMARKER, line is an empty
+ # string, so will properly match the empty string with which the
+ # "indents" stack was seeded
+ check_equal = 0
+ self.check_indent_level(line, indents[-1], line_num)
+
+ if line_num > self.config.max_module_lines:
+ self.add_message('W0302', args=line_num)
+
+ def visit_default(self, node):
+ """check the node line number and check it if not yet done
+ """
+ if not node.is_statement():
+ return
+ prev_sibl = node.previous_sibling()
+ if prev_sibl is not None:
+ # don't use .source_line since it causes C0321 false positive !
+ prev_line = prev_sibl.lineno
+ else:
+ # itou ?
+ prev_line = node.parent.statement().lineno
+ line = node.source_line()
+ if prev_line == line and self._visited_lines.get(line) != 2:
+ self.add_message('C0321', node=node)
+ self._visited_lines[line] = 2
+ return
+ if self._visited_lines.has_key(line):
+ return
+ self._visited_lines[line] = 1
+ #print 'checking line', self._lines[line]
+ #print node
+ try:
+ msg_def = check_line(self._lines[line], self)
+ if msg_def:
+ self.add_message(msg_def[0], node = node, args=msg_def[1])
+ except KeyError:
+ # FIXME: internal error !
+ pass
+
+ def check_lines(self, lines, i):
+ """check lines have less than a maximum number of characters
+ """
+ max_chars = self.config.max_line_length
+ for line in lines.splitlines():
+ if len(line) > max_chars:
+ self.add_message('C0301', line=i, args=(len(line), max_chars))
+ i += 1
+
+ def check_indent_level(self, string, expected, line_num):
+ """return the indent level of the string
+ """
+ indent = self.config.indent_string
+ if indent == '\\t': # \t is not interpreted in the configuration file
+ indent = '\t'
+ level = 0
+ unit_size = len(indent)
+ while string[:unit_size] == indent:
+ string = string[unit_size:]
+ level += 1
+ suppl = ''
+ while string and string[0] in ' \t':
+ if string[0] != indent[0]:
+ if string[0] == '\t':
+ args = ('tab', 'space')
+ else:
+ args = ('space', 'tab')
+ self.add_message('W0312', args=args, line=line_num)
+ return level
+ suppl += string[0]
+ string = string [1:]
+ if level != expected or suppl:
+ i_type = 'spaces'
+ if indent[0] == '\t':
+ i_type = 'tabs'
+ self.add_message('W0311', line=line_num,
+ args=(level * unit_size + len(suppl), i_type,
+ expected * unit_size))
+
+
+def register(linter):
+ """required method to auto register this checker """
+ linter.register_checker(FormatChecker(linter))
diff --git a/checkers/imports.py b/checkers/imports.py
new file mode 100644
index 000000000..6ca248ac8
--- /dev/null
+++ b/checkers/imports.py
@@ -0,0 +1,379 @@
+# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""imports checkers for Python code
+"""
+
+__revision__ = "$Id: imports.py,v 1.46 2005-11-10 17:26:27 syt Exp $"
+
+from logilab.common import get_cycles
+from logilab.common.modutils import is_standard_module, is_relative, \
+ get_module_part
+from logilab.common.ureports import VerbatimText, Paragraph
+
+from logilab import astng
+
+from pylint.interfaces import IASTNGChecker
+from pylint.checkers import BaseChecker, EmptyReport
+from pylint.checkers.utils import are_exclusive
+
+def get_first_import(context, name, base):
+ """return the node where [base.]<name> is imported or None if not found
+ """
+ for node in context.values():
+ if isinstance(node, astng.Import):
+ if name in [iname[0] for iname in node.names]:
+ return node
+ if isinstance(node, astng.From):
+ if base == node.modname and \
+ name in [iname[0] for iname in node.names]:
+ return node
+
+
+# utilities to represents import dependencies as tree and dot graph ###########
+
+def filter_dependencies_info(dep_info, package_dir, mode='external'):
+ """filter external or internal dependencies from dep_info (return a
+ new dictionary containing the filtered modules only)
+ """
+ if mode == 'external':
+ filter_func = lambda x: not is_standard_module(x, (package_dir,))
+ else:
+ assert mode == 'internal'
+ filter_func = lambda x: is_standard_module(x, (package_dir,))
+ result = {}
+ for importee, importers in dep_info.items():
+ if filter_func(importee):
+ result[importee] = importers
+ return result
+
+def make_tree_defs(mod_files_list):
+ """get a list of 2-uple (module, list_of_files_which_import_this_module),
+ it will return a dictionnary to represent this as a tree
+ """
+ tree_defs = {}
+ for mod, files in mod_files_list:
+ node = (tree_defs, ())
+ for prefix in mod.split('.'):
+ node = node[0].setdefault(prefix, [{}, []])
+ node[1] += files
+ return tree_defs
+
+def repr_tree_defs(data, indent_str=None):
+ """return a string which represents imports as a tree"""
+ lines = []
+ nodes = data.items()
+ for i in range(len(nodes)):
+ mod, (sub, files) = nodes[i]
+ if not files:
+ files = ''
+ else:
+ files = '(%s)' % ','.join(files)
+ if indent_str is None:
+ lines.append('%s %s' % (mod, files))
+ sub_indent_str = ' '
+ else:
+ lines.append('%s\-%s %s' % (indent_str, mod, files))
+ if i == len(nodes)-1:
+ sub_indent_str = '%s ' % indent_str
+ else:
+ sub_indent_str = '%s| ' % indent_str
+ if sub:
+ lines.append(repr_tree_defs(sub, sub_indent_str))
+ return '\n'.join(lines)
+
+def dot_node(modname):
+ """return the string representation for a dot node"""
+ return '"%s" [ label="%s" ];' % (modname, modname)
+
+def dot_edge(from_, to_):
+ """return the string representation for a dot edge between two nodes"""
+ return'"%s" -> "%s" [ ] ;' % (from_, to_)
+
+DOT_HEADERS = '''rankdir="LR" URL="." concentrate=false
+edge[fontsize="10" ]
+node[width="0" height="0" fontsize="12" fontcolor="black"]'''
+
+def dependencies_graph(filename, dep_info):
+ """write dependencies as defined in the dep_info dictionary as a dot
+ (graphviz) file
+ """
+ done = {}
+ stream = open(filename, 'w')
+ print >> stream, "digraph g {"
+ print >> stream, DOT_HEADERS
+ for modname, dependencies in dep_info.items():
+ done[modname] = 1
+ print >> stream, dot_node(modname)
+ for modname in dependencies:
+ if not done.has_key(modname):
+ done[modname] = 1
+ print >> stream, dot_node(modname)
+ for depmodname, dependencies in dep_info.items():
+ for modname in dependencies:
+ print >> stream, dot_edge(modname, depmodname)
+ print >> stream,'}'
+ stream.close()
+
+def make_graph(filename, dep_info, sect, gtype):
+ """generate a dependencies graph and add some information about it in the
+ report's section
+ """
+ dependencies_graph(filename, dep_info)
+ sect.append(Paragraph('%simports graph has been written to %s'
+ % (gtype, filename)))
+
+
+# the import checker itself ###################################################
+
+MSGS = {
+ 'F0401': ('Unable to import %r (%s)' ,
+ 'Used when pylint has been unable to import a module.'),
+ 'R0401': ('Cyclic import (%s)',
+ 'Used when a cyclic import between two or more modules is \
+ detected.'),
+
+ 'W0401': ('Wildcard import %s',
+ 'Used when `from module import *` is detected.'),
+ 'W0402': ('Uses of a deprecated module %r',
+ 'Used a module marked as deprecated is imported.'),
+ 'W0403': ('Relative import %r',
+ 'Used when an import relative to the package directory is \
+ detected.'),
+ 'W0404': ('Reimport %r (imported line %s)',
+ 'Used when a module is reimported multiple times.'),
+ 'W0406': ('Module import itself',
+ 'Used when a module is importing itself.'),
+
+ 'W0410': ('__future__ import is not the first non docstring statement',
+ 'Python 2.5 and greater require __future__ import to be the \
+ first non docstring statement in the module.'),
+ }
+
+class ImportsChecker(BaseChecker):
+ """checks for
+ * external modules dependencies
+ * relative / wildcard imports
+ * cyclic imports
+ * uses of deprecated modules
+ """
+
+ __implements__ = IASTNGChecker
+
+ name = 'imports'
+ msgs = MSGS
+ priority = -2
+
+ options = (('deprecated-modules',
+ {'default' : ('regsub','string', 'TERMIOS',
+ 'Bastion', 'rexec'),
+ 'type' : 'csv',
+ 'metavar' : '<modules>',
+ 'help' : 'Deprecated modules which should not be used, \
+separated by a comma'}
+ ),
+ ('import-graph',
+ {'default' : '',
+ 'type' : 'string',
+ 'metavar' : '<file.dot>',
+ 'help' : 'Create a graph of every (i.e. internal and \
+external) dependencies in the given file (report R0402 must not be disabled)'}
+ ),
+ ('ext-import-graph',
+ {'default' : '',
+ 'type' : 'string',
+ 'metavar' : '<file.dot>',
+ 'help' : 'Create a graph of external dependencies in the \
+given file (report R0402 must not be disabled)'}
+ ),
+ ('int-import-graph',
+ {'default' : '',
+ 'type' : 'string',
+ 'metavar' : '<file.dot>',
+ 'help' : 'Create a graph of internal dependencies in the \
+given file (report R0402 must not be disabled)'}
+ ),
+
+ )
+
+ def __init__(self, linter=None):
+ BaseChecker.__init__(self, linter)
+ self.stats = None
+ self.import_graph = None
+ self.__int_dep_info = self.__ext_dep_info = None
+ self.reports = (('R0401', 'External dependencies',
+ self.report_external_dependencies),
+ ('R0402', 'Modules dependencies graph',
+ self.report_dependencies_graph),
+ )
+
+ def open(self):
+ """called before visiting project (i.e set of modules)"""
+ self.linter.add_stats(dependencies={})
+ self.linter.add_stats(cycles=[])
+ self.stats = self.linter.stats
+ self.import_graph = {}
+
+ def close(self):
+ """called before visiting project (i.e set of modules)"""
+ for cycle in get_cycles(self.import_graph):
+ self.add_message('R0401', args=' -> '.join(cycle))
+
+ def visit_import(self, node):
+ """triggered when an import statement is seen"""
+ for name, _ in node.names:
+ self._check_deprecated(node, name)
+ relative = self._check_relative(node, name)
+ self._imported_module(node, name, relative)
+ # handle reimport
+ self._check_reimport(node, name)
+
+
+ def visit_from(self, node):
+ """triggered when an import statement is seen"""
+ basename = node.modname
+ if basename == '__future__':
+ # check this is the first non docstring statement in the module
+ if node.previous_sibling():
+ self.add_message('W0410', node=node)
+ self._check_deprecated(node, basename)
+ relative = self._check_relative(node, basename)
+ for name, _ in node.names:
+ if name == '*':
+ self.add_message('W0401', args=basename, node=node)
+ continue
+ # handle reimport
+ self._check_reimport(node, name, basename)
+ # analyze dependencies
+ fullname = '%s.%s' % (basename, name)
+ if fullname.find('.') > -1:
+ try:
+ # XXXFIXME: don't use get_module_part which doesn't take
+ # care of package precedence
+ fullname = get_module_part(fullname,
+ context_file=node.root().file)
+ except ImportError, ex:
+ self.add_message('F0401', args=(fullname, ex), node=node)
+ continue
+ self._imported_module(node, fullname, relative)
+
+ def _imported_module(self, node, mod_path, relative):
+ """notify an imported module, used to analyze dependencies
+ """
+ context_name = node.root().name
+ if relative:
+ mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]),
+ mod_path)
+ if context_name == mod_path:
+ # module importing itself !
+ self.add_message('W0406', node=node)
+ elif not is_standard_module(mod_path):
+ # handle dependencies
+ mod_paths = self.stats['dependencies'].setdefault(mod_path, [])
+ if not context_name in mod_paths:
+ mod_paths.append(context_name)
+ if is_standard_module( mod_path, (self.package_dir(),) ):
+ # update import graph
+ mgraph = self.import_graph.setdefault(context_name, [])
+ if not mod_path in mgraph:
+ mgraph.append(mod_path)
+
+ def _check_relative(self, node, mod_path):
+ """check relative import module"""
+ # check for relative import
+ context_file = node.root().file
+ relative = is_relative(mod_path, context_file)
+ if relative:
+ self.add_message('W0403', args=mod_path, node=node)
+ return relative
+
+ def _check_deprecated(self, node, mod_path):
+ """check if the module is deprecated"""
+ for mod_name in self.config.deprecated_modules:
+ if mod_path.startswith(mod_name) and \
+ (len(mod_path) == len(mod_name)
+ or mod_path[len(mod_name)] == '.'):
+ self.add_message('W0402', node=node, args=mod_path)
+
+ def _check_reimport(self, node, name, basename=None):
+ """check if the import is necessary (i.e. not already done)
+ """
+ frame = node.frame()
+ first = get_first_import(frame, name, basename)
+ if isinstance(first, (astng.Import, astng.From)) and first is not node \
+ and not are_exclusive(first, node):
+ self.add_message('W0404', node=node, args=(name, first.lineno))
+ else:
+ root = node.root()
+ if root is frame:
+ return
+ first = get_first_import(root, name, basename)
+ if not isinstance(first, (astng.Import, astng.From)):
+ return
+ if first is not node and not are_exclusive(first, node):
+ self.add_message('W0404', node=node,
+ args=(name, first.lineno))
+
+
+ def report_external_dependencies(self, sect, _, dummy):
+ """return a verbatim layout for displaying dependencies
+ """
+ dep_info = make_tree_defs(self._external_dependencies_info().items())
+ if not dep_info:
+ raise EmptyReport()
+ tree_str = repr_tree_defs(dep_info)
+ sect.append(VerbatimText(tree_str))
+
+ def report_dependencies_graph(self, sect, _, dummy):
+ """write dependencies as a dot (graphviz) file"""
+ dep_info = self.stats['dependencies']
+ if not dep_info or not (self.config.import_graph
+ or self.config.ext_import_graph
+ or self.config.int_import_graph):
+ raise EmptyReport()
+ filename = self.config.import_graph
+ if filename:
+ make_graph(filename, dep_info, sect, '')
+ filename = self.config.ext_import_graph
+ if filename:
+ make_graph(filename, self._external_dependencies_info(),
+ sect, 'external ')
+ filename = self.config.int_import_graph
+ if filename:
+ make_graph(filename, self._internal_dependencies_info(),
+ sect, 'internal ')
+
+ def _external_dependencies_info(self):
+ """return cached external dependencies information or build and
+ cache them
+ """
+ if self.__ext_dep_info is None:
+ self.__ext_dep_info = filter_dependencies_info(
+ self.stats['dependencies'], self.package_dir(), 'external')
+ return self.__ext_dep_info
+
+ def _internal_dependencies_info(self):
+ """return cached internal dependencies information or build and
+ cache them
+ """
+ if self.__int_dep_info is None:
+ self.__int_dep_info = filter_dependencies_info(
+ self.stats['dependencies'], self.package_dir(), 'internal')
+ return self.__int_dep_info
+
+
+def register(linter):
+ """required method to auto register this checker """
+ linter.register_checker(ImportsChecker(linter))
diff --git a/checkers/misc.py b/checkers/misc.py
new file mode 100644
index 000000000..7b59b9557
--- /dev/null
+++ b/checkers/misc.py
@@ -0,0 +1,127 @@
+# pylint: disable-msg=W0511
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+Check source code is ascii only or has an encoding declaration (PEP 263)
+"""
+
+__revision__ = '$Id: misc.py,v 1.19 2005-11-02 09:21:47 syt Exp $'
+
+import re
+
+from pylint.interfaces import IRawChecker
+from pylint.checkers import BaseChecker
+
+def is_ascii(string):
+ """return true if non ascii characters are detected in the given string
+ """
+ if string:
+ return max([ord(char) for char in string]) < 128
+ return True
+
+# regexp matching both emacs and vim declaration
+ENCODING_RGX = re.compile("[^#]*#*.*coding[:=]\s*([^\s]+)")
+
+def guess_encoding(string):
+ """try to guess encoding from a python file as string
+ return None if not found
+ """
+ assert type(string) is type(''), type(string)
+ # check for UTF-8 byte-order mark
+ if string.startswith('\xef\xbb\xbf'):
+ return 'UTF-8'
+ first_lines = string.split('\n', 2)[:2]
+ for line in first_lines:
+ # check for emacs / vim encoding declaration
+ match = ENCODING_RGX.match(line)
+ if match is not None:
+ return match.group(1)
+
+
+MSGS = {
+ 'E0501': ('Non ascii characters found but no encoding specified (PEP 263)',
+ 'Used when some non ascii characters are detected but now \
+ encoding is specified, as explicited in the PEP 263.'),
+ 'E0502': ('Wrong encoding specified (%s)',
+ 'Used when a known encoding is specified but the file doesn\'t \
+ seem to be actually in this encoding.'),
+ 'E0503': ('Unknown encoding specified (%s)',
+ 'Used when an encoding is specified, but it\'s unknown to Python.'
+ ),
+
+ 'W0511': ('%s',
+ 'Used when a warning note as FIXME or XXX is detected.'),
+ }
+
+class EncodingChecker(BaseChecker):
+ """checks for:
+ * warning notes in the code like FIXME, XXX
+ * PEP 263: source code with non ascii character but no encoding declaration
+ """
+ __implements__ = IRawChecker
+
+ # configuration section name
+ name = 'miscellaneous'
+ msgs = MSGS
+
+ options = (('notes',
+ {'type' : 'csv', 'metavar' : '<comma separated values>',
+ 'default' : ('FIXME', 'XXX', 'TODO'),
+ 'help' : 'List of note tags to take in consideration, \
+separated by a comma. Default to FIXME, XXX, TODO'
+ }),
+ )
+
+ def __init__(self, linter=None):
+ BaseChecker.__init__(self, linter)
+
+ def process_module(self, stream):
+ """inspect the source file to found encoding problem or fixmes like
+ notes
+ """
+ # source encoding
+ data = stream.read()
+ if not is_ascii(data):
+ encoding = guess_encoding(data)
+ if encoding is None:
+ self.add_message('E0501', line=1)
+ else:
+ try:
+ unicode(data, encoding)
+ except UnicodeError:
+ self.add_message('E0502', args=encoding, line=1)
+ except LookupError:
+ self.add_message('E0503', args=encoding, line=1)
+ del data
+ # warning notes in the code
+ stream.seek(0)
+ notes = []
+ for note in self.config.notes:
+ notes.append(re.compile(note))
+ linenum = 1
+ for line in stream.readlines():
+ for note in notes:
+ match = note.search(line)
+ if match:
+ self.add_message('W0511', args=line[match.start():-1],
+ line=linenum)
+ break
+ linenum += 1
+
+
+
+def register(linter):
+ """required method to auto register this checker"""
+ linter.register_checker(EncodingChecker(linter))
diff --git a/checkers/newstyle.py b/checkers/newstyle.py
new file mode 100644
index 000000000..540b60f58
--- /dev/null
+++ b/checkers/newstyle.py
@@ -0,0 +1,148 @@
+# Copyright (c) 2005-2006 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""check for new / old style related problems
+"""
+
+__revision__ = "$Id: newstyle.py,v 1.8 2006-03-05 14:39:38 syt Exp $"
+
+from logilab import astng
+
+from pylint.interfaces import IASTNGChecker
+from pylint.checkers import BaseChecker
+
+MSGS = {
+ 'E1001': ('Use __slots__ on an old style class',
+ 'Used when an old style class use the __slots__ attribute.'),
+ 'E1002': ('Use super on an old style class',
+ 'Used when an old style class use the super builtin.'),
+ 'E1003': ('Bad first argument %r given to super class',
+ 'Used when another argument than the current class is given as \
+ first argument of the super builtin.'),
+ 'E1010': ('Raising a new style class',
+ 'Used when a new style class is raised since it\'s not yet \
+ possible.'),
+
+ 'W1001': ('Use of "property" on an old style class',
+ 'Used when PyLint detect the use of the builtin "property" \
+ on an old style class while this is relying on new style \
+ classes features'),
+ 'W1010': ('Exception doesn\'t inherit from standard "Exception" class',
+ 'Used when a custom exception class is raised but doesn\'t \
+ inherit from the builtin "Exception" class.'),
+ }
+
+
+class NewStyleConflictChecker(BaseChecker):
+ """checks for usage of new style capabilities on old style classes and
+ other new/old styles conflicts problems
+ * use of property, __slots__, super
+ * "super" usage
+ * raising a new style class as exception
+ """
+
+ __implements__ = (IASTNGChecker,)
+
+ # configuration section name
+ name = 'newstyle'
+ # messages
+ msgs = MSGS
+ priority = -2
+ # configuration options
+ options = ()
+
+# def __init__(self, linter=None):
+# BaseChecker.__init__(self, linter)
+
+ def visit_class(self, node):
+ """check __slots__ usage
+ """
+ if '__slots__' in node and not node.newstyle:
+ self.add_message('E1001', node=node)
+
+ def visit_callfunc(self, node):
+ """check property usage"""
+ parent = node.parent.frame()
+ if (isinstance(parent, astng.Class) and
+ not parent.newstyle and
+ isinstance(node.node, astng.Name)):
+ name = node.node.name
+ if name == 'property':
+ self.add_message('W1001', node=node)
+
+ def visit_raise(self, node):
+ """check for raising new style class
+ """
+ # ignore empty raise
+ if node.expr1 is None:
+ return
+ if not isinstance(node.expr1, (astng.Const, astng.Mod)):
+ try:
+ name = node.expr1.nodes_of_class(astng.Name).next()
+ value = name.infer().next()
+ except (StopIteration, astng.ResolveError):
+ pass
+ else:
+ if isinstance(value, astng.Class):
+ if value.newstyle:
+ self.add_message('E1010', node=node)
+ elif not inherit_from_std_ex(value):
+ self.add_message('W1010', node=node)
+
+ def visit_function(self, node):
+ """check use of super"""
+ # ignore actual functions or method within a new style class
+ if not node.is_method():
+ return
+ klass = node.parent.frame()
+ for stmt in node.nodes_of_class(astng.CallFunc):
+ expr = stmt.node
+ if not isinstance(expr, astng.Getattr):
+ continue
+ call = expr.expr
+ # skip the test if using super
+ if isinstance(call, astng.CallFunc) and \
+ isinstance(call.node, astng.Name) and \
+ call.node.name == 'super':
+ if not klass.newstyle:
+ # super should not be used on an old style class
+ self.add_message('E1002', node=node)
+ else:
+ # super first arg should be the class
+ try:
+ supcls = (call.args and call.args[0].infer().next()
+ or None)
+ except astng.InferenceError:
+ continue
+ if klass is not supcls:
+ supcls = getattr(supcls, 'name', supcls)
+ self.add_message('E1003', node=node, args=supcls)
+
+
+
+def inherit_from_std_ex(node):
+ """return true if the given class node is subclass of
+ exceptions.Exception
+ """
+ if node.name == 'Exception' and node.root().name == 'exceptions':
+ return True
+ for parent in node.ancestors(recurs=False):
+ if inherit_from_std_ex(parent):
+ return True
+ return False
+
+def register(linter):
+ """required method to auto register this checker """
+ linter.register_checker(NewStyleConflictChecker(linter))
diff --git a/checkers/raw_metrics.py b/checkers/raw_metrics.py
new file mode 100644
index 000000000..be1003400
--- /dev/null
+++ b/checkers/raw_metrics.py
@@ -0,0 +1,130 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+Raw metrics checker
+"""
+
+__revision__ = "$Id: raw_metrics.py,v 1.15 2005-11-02 09:21:47 syt Exp $"
+
+import tokenize
+
+# pylint now requires pylint >= 2.2, so this is no longer necessary
+#if not hasattr(tokenize, 'NL'):
+# raise ValueError("tokenize.NL doesn't exist -- tokenize module too old")
+
+from logilab.common.ureports import Table
+
+from pylint.interfaces import IRawChecker
+from pylint.checkers import BaseRawChecker, EmptyReport
+from pylint.reporters import diff_string
+
+
+def report_raw_stats(sect, stats, old_stats):
+ """calculate percentage of code / doc / comment / empty
+ """
+ total_lines = stats['total_lines']
+ if not total_lines:
+ raise EmptyReport()
+ sect.description = '%s lines have been analyzed' % total_lines
+ lines = ('type', 'number', '%', 'previous', 'difference')
+ for node_type in ('code', 'docstring', 'comment', 'empty'):
+ key = node_type + '_lines'
+ total = stats[key]
+ percent = float(total * 100) / total_lines
+ old = old_stats.get(key, None)
+ if old is not None:
+ diff_str = diff_string(old, total)
+ else:
+ old, diff_str = 'NC', 'NC'
+ lines += (node_type, str(total), '%.2f' % percent,
+ str(old), diff_str)
+ sect.append(Table(children=lines, cols=5, rheaders=1))
+
+
+class RawMetricsChecker(BaseRawChecker):
+ """does not check anything but gives some raw metrics :
+ * total number of lines
+ * total number of code lines
+ * total number of docstring lines
+ * total number of comments lines
+ * total number of empty lines
+ """
+
+ __implements__ = (IRawChecker,)
+
+ # configuration section name
+ name = 'metrics'
+ # configuration options
+ options = ( )
+ # messages
+ msgs = {}
+ # reports
+ reports = ( ('R0701', 'Raw metrics', report_raw_stats), )
+
+ def __init__(self, linter):
+ BaseRawChecker.__init__(self, linter)
+ self.stats = None
+
+ def open(self):
+ """init statistics"""
+ self.stats = self.linter.add_stats(total_lines=0, code_lines=0,
+ empty_lines=0, docstring_lines=0,
+ comment_lines=0)
+
+ def process_tokens(self, tokens):
+ """update stats"""
+ i = 0
+ tokens = list(tokens)
+ while i < len(tokens):
+ i, lines_number, line_type = get_type(tokens, i)
+ self.stats['total_lines'] += lines_number
+ self.stats[line_type] += lines_number
+
+
+JUNK = (tokenize.NL, tokenize.INDENT, tokenize.NEWLINE, tokenize.ENDMARKER)
+
+def get_type(tokens, start_index):
+ """return the line type : docstring, comment, code, empty
+ """
+ i = start_index
+ tok_type = tokens[i][0]
+ start = tokens[i][2]
+ pos = start
+ line_type = None
+ while i < len(tokens) and tokens[i][2][0] == start[0]:
+ tok_type = tokens[i][0]
+ pos = tokens[i][3]
+ if line_type is None:
+ if tok_type == tokenize.STRING:
+ line_type = 'docstring_lines'
+ elif tok_type == tokenize.COMMENT:
+ line_type = 'comment_lines'
+ elif tok_type in JUNK:
+ pass
+ else:
+ line_type = 'code_lines'
+ i += 1
+
+ if line_type is None:
+ line_type = 'empty_lines'
+ elif i < len(tokens) and tok_type == tokenize.NEWLINE:
+ i += 1
+ return i, pos[0] - start[0] + 1, line_type
+
+
+def register(linter):
+ """ required method to auto register this checker """
+ linter.register_checker(RawMetricsChecker(linter))
+
diff --git a/checkers/similar.py b/checkers/similar.py
new file mode 100644
index 000000000..f4b3a6c80
--- /dev/null
+++ b/checkers/similar.py
@@ -0,0 +1,329 @@
+# pylint: disable-msg=w0622
+# Copyright (c) 2004 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""a similarties / code duplication command line tool and pylint checker
+"""
+from __future__ import generators
+
+__revision__ = '$Id: similar.py,v 1.14 2006-03-29 08:24:32 syt Exp $'
+
+import sys
+
+from logilab.common.compat import set, izip, sum, enumerate
+from logilab.common.ureports import Table
+
+from pylint.interfaces import IRawChecker
+from pylint.checkers import BaseChecker, table_lines_from_stats
+
+
+class Similar:
+ """finds copy-pasted lines of code in a project"""
+
+ def __init__(self, min_lines=4, ignore_comments=False, ignore_docstrings=False):
+ self.min_lines = min_lines
+ self.ignore_comments = ignore_comments
+ self.ignore_docstrings = ignore_docstrings
+ self.linesets = []
+
+ def append_stream(self, streamid, stream):
+ """append a file to search for similarities"""
+ self.linesets.append(LineSet(streamid,
+ stream.readlines(),
+ self.ignore_comments,
+ self.ignore_docstrings))
+
+ def run(self):
+ """start looking for similarities and display results on stdout"""
+ self._display_sims(self._compute_sims())
+
+ def _compute_sims(self):
+ """compute similarities in appended files"""
+ no_duplicates = {}
+ for num, lineset1, idx1, lineset2, idx2 in self._iter_sims():
+ duplicate = no_duplicates.setdefault(num, [])
+ for couples in duplicate:
+ if (lineset1, idx1) in couples or (lineset2, idx2) in couples:
+ couples.add( (lineset1, idx1) )
+ couples.add( (lineset2, idx2) )
+ break
+ else:
+ duplicate.append( set([(lineset1, idx1), (lineset2, idx2)]) )
+ sims = []
+ for num, ensembles in no_duplicates.iteritems():
+ for couples in ensembles:
+ sims.append( (num, couples) )
+ sims.sort()
+ sims.reverse()
+ return sims
+
+ def _display_sims(self, sims):
+ """display computed similarities on stdout"""
+ nb_lignes_dupliquees = 0
+ for num, couples in sims:
+ print
+ print num, "similar lines in", len(couples), "files"
+ couples = list(couples)
+ couples.sort()
+ for lineset, idx in couples:
+ print "==%s:%s" % (lineset.name, idx)
+ for line in lineset._real_lines[idx:idx+num]:
+ print " ", line,
+ nb_lignes_dupliquees += num * (len(couples)-1)
+ nb_total_lignes = sum([len(lineset) for lineset in self.linesets])
+ print "TOTAL lines=%s duplicates=%s percent=%s" \
+ % (nb_total_lignes, nb_lignes_dupliquees,
+ nb_lignes_dupliquees*1. / nb_total_lignes)
+
+ def _find_common(self, lineset1, lineset2):
+ """find similarities in the two given linesets"""
+ lines1 = lineset1.enumerate_stripped
+ lines2 = lineset2.enumerate_stripped
+ find = lineset2.find
+ index1 = 0
+ min_lines = self.min_lines
+ while index1 < len(lineset1):
+ skip = 1
+ num = 0
+ for index2 in find( lineset1[index1] ):
+ non_blank = 0
+ for num, ((_, line1), (_, line2)) in enumerate(
+ izip(lines1(index1), lines2(index2))):
+ if line1 != line2:
+ if non_blank > min_lines:
+ yield num, lineset1, index1, lineset2, index2
+ skip = max(skip, num)
+ break
+ if line1:
+ non_blank += 1
+ else:
+ # we may have reach the end
+ num += 1
+ if non_blank > min_lines:
+ yield num, lineset1, index1, lineset2, index2
+ skip = max(skip, num)
+ index1 += skip
+
+ def _iter_sims(self):
+ """iterate on similarities among all files, by making a cartesian
+ product
+ """
+ for idx, lineset in enumerate(self.linesets[:-1]):
+ for lineset2 in self.linesets[idx+1:]:
+ for sim in self._find_common(lineset, lineset2):
+ yield sim
+
+def stripped_lines(lines, ignore_comments, ignore_docstrings):
+ strippedlines = []
+ docstring = None
+ for line in lines:
+ line = line.strip()
+ if ignore_docstrings:
+ if not docstring and \
+ (line.startswith('"""') or line.startswith("'''")):
+ docstring = line[:3]
+ line = line[3:]
+ if docstring:
+ if line.endswith(docstring):
+ docstring = None
+ line = ''
+ # XXX cut when a line begins with code but end with a comment
+ if ignore_comments and line.startswith('#'):
+ line = ''
+ strippedlines.append(line)
+ return strippedlines
+
+class LineSet:
+ """Holds and indexes all the lines of a single source file"""
+ def __init__(self, name, lines, ignore_comments=False, ignore_docstrings=False):
+ self.name = name
+ self._real_lines = lines
+ self._stripped_lines = stripped_lines(lines, ignore_comments, ignore_docstrings)
+ self._index = self._mk_index()
+
+ def __str__(self):
+ return '<Lineset for %s>' % self.name
+
+ def __len__(self):
+ return len(self._real_lines)
+
+ def __getitem__(self, index):
+ return self._stripped_lines[index]
+
+ def __cmp__(self, other):
+ return cmp(self.name, other.name)
+
+ def __hash__(self):
+ return id(self)
+
+ def enumerate_stripped(self, start_at=0):
+ """return an iterator on stripped lines, starting from a given index
+ if specified, else 0
+ """
+ idx = start_at
+ if start_at:
+ lines = self._stripped_lines[start_at:]
+ else:
+ lines = self._stripped_lines
+ for line in lines:
+ #if line:
+ yield idx, line
+ idx += 1
+
+ def find(self, stripped_line):
+ """return positions of the given stripped line in this set"""
+ return self._index.get(stripped_line, ())
+
+ def _mk_index(self):
+ """create the index for this set"""
+ index = {}
+ for line_no, line in enumerate(self._stripped_lines):
+ if line:
+ index.setdefault(line, []).append( line_no )
+ return index
+
+
+MSGS = {'R0801': ('Similar lines in %s files\n%s',
+ 'Indicates that a set of similar lines has been detected \
+ among multiple file. This usually means that the code should \
+ be refactored to avoid this duplication.')}
+
+def report_similarities(sect, stats, old_stats):
+ """make a layout with some stats about duplication"""
+ lines = ['', 'now', 'previous', 'difference']
+ lines += table_lines_from_stats(stats, old_stats,
+ ('nb_duplicated_lines',
+ 'percent_duplicated_lines'))
+ sect.append(Table(children=lines, cols=4, rheaders=1, cheaders=1))
+
+
+# wrapper to get a pylint checker from the similar class
+class SimilarChecker(BaseChecker, Similar):
+ """checks for similarities and duplicated code. This computation may be
+ memory / CPU intensive, so you should disable it if you experiments some
+ problems.
+ """
+
+ __implements__ = (IRawChecker,)
+ # configuration section name
+ name = 'similarities'
+ # messages
+ msgs = MSGS
+ # configuration options
+ # for available dict keys/values see the optik parser 'add_option' method
+ options = (('min-similarity-lines',
+ {'default' : 4, 'type' : "int", 'metavar' : '<int>',
+ 'help' : 'Minimum lines number of a similarity.'}),
+ ('ignore-comments',
+ {'default' : True, 'type' : 'yn', 'metavar' : '<y or n>',
+ 'help': 'Ignore comments when computing similarities.'}
+ ),
+ ('ignore-docstrings',
+ {'default' : True, 'type' : 'yn', 'metavar' : '<y or n>',
+ 'help': 'Ignore docstrings when computing similarities.'}
+ ),
+ )
+ # reports
+ reports = ( ('R0801', 'Duplication', report_similarities), )
+
+ def __init__(self, linter=None):
+ BaseChecker.__init__(self, linter)
+ Similar.__init__(self, min_lines=4,
+ ignore_comments=True, ignore_docstrings=True)
+ self.stats = None
+
+ def set_option(self, opt_name, value, action=None, opt_dict=None):
+ """method called to set an option (registered in the options list)
+
+ overriden to report options setting to Similar
+ """
+ BaseChecker.set_option(self, opt_name, value, action, opt_dict)
+ if opt_name == 'min-similarity-lines':
+ self.min_lines = self.config.min_similarity_lines
+ elif opt_name == 'ignore-comments':
+ self.ignore_comments = self.config.ignore_comments
+ elif opt_name == 'ignore-docstrings':
+ self.ignore_docstrings = self.config.ignore_docstrings
+
+ def open(self):
+ """init the checkers: reset linesets and statistics information"""
+ self.linesets = []
+ self.stats = self.linter.add_stats(nb_duplicated_lines=0,
+ percent_duplicated_lines=0)
+
+ def process_module(self, stream):
+ """process a module
+
+ the module's content is accessible via the stream object
+
+ stream must implements the readlines method
+ """
+ self.append_stream(self.linter.current_name, stream)
+
+ def close(self):
+ """compute and display similarities on closing (i.e. end of parsing)"""
+ total = sum([len(lineset) for lineset in self.linesets])
+ duplicated = 0
+ stats = self.stats
+ for num, couples in self._compute_sims():
+ msg = []
+ for lineset, idx in couples:
+ msg.append("==%s:%s" % (lineset.name, idx))
+ msg.sort()
+ for line in lineset._real_lines[idx:idx+num]:
+ msg.append(line.rstrip())
+ self.add_message('R0801', args=(len(couples), '\n'.join(msg)))
+ duplicated += num * (len(couples) - 1)
+ stats['nb_duplicated_lines'] = duplicated
+ stats['percent_duplicated_lines'] = total and duplicated * 100. / total
+
+
+def register(linter):
+ """required method to auto register this checker """
+ linter.register_checker(SimilarChecker(linter))
+
+def usage(status=0):
+ """display command line usage information"""
+ print "finds copy pasted blocks in a set of files"
+ print
+ print 'Usage: similar [-d|--duplicates min_duplicated_lines] \
+[--ignore-comments] file1...'
+ sys.exit(status)
+
+def run(argv=None):
+ """standalone command line access point"""
+ argv = argv or sys.argv[1:]
+ from getopt import getopt
+ s_opts = 'hd:'
+ l_opts = ('help', 'duplicates=', 'ignore-comments')
+ min_lines = 4
+ ignore_comments = False
+ opts, args = getopt(argv, s_opts, l_opts)
+ for opt, val in opts:
+ if opt in ('-d', '--duplicates'):
+ min_lines = int(val)
+ elif opt in ('-h', '--help'):
+ usage()
+ elif opt == '--ignore-comments':
+ ignore_comments = True
+ if not args:
+ usage(1)
+ sim = Similar(min_lines, ignore_comments)
+ for filename in args:
+ sim.append_stream(filename, open(filename))
+ sim.run()
+
+if __name__ == '__main__':
+ run()
diff --git a/checkers/typecheck.py b/checkers/typecheck.py
new file mode 100644
index 000000000..e8c435510
--- /dev/null
+++ b/checkers/typecheck.py
@@ -0,0 +1,181 @@
+# Copyright (c) 2006 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""try to find more bugs in the code using astng inference capabilities
+"""
+
+__revision__ = "$Id: typecheck.py,v 1.12 2006-04-19 16:16:20 syt Exp $"
+
+from logilab import astng
+
+from pylint.interfaces import IASTNGChecker
+from pylint.checkers import BaseChecker
+
+MSGS = {
+ 'E1101': ('%s %r has no %r member',
+ 'Used when a class is accessed for an unexistant member.'),
+ 'E1102': ('%s is not callable',
+ 'Used when an object being called has been infered to a non \
+ callable object'),
+ 'E1111': ('Assigning to function call which doesn\'t return',
+ 'Used when an assigment is done on a function call but the \
+ infered function doesn\'t return anything.'),
+ 'W1111': ('Assigning to function call which only returns None',
+ 'Used when an assigment is done on a function call but the \
+ infered function returns nothing but None.'),
+ }
+
+
+class TypeChecker(BaseChecker):
+ """try to find bugs in the code using type inference
+ """
+
+ __implements__ = (IASTNGChecker,)
+
+ # configuration section name
+ name = 'typecheck'
+ # messages
+ msgs = MSGS
+ priority = -1
+ # configuration options
+ options = (
+ ('ignore-mixin-members',
+ {'default' : True, 'type' : 'yn', 'metavar': '<y_or_n>',
+ 'help' : 'Tells wether missing members accessed in mixin \
+class should be ignored. A mixin class is detected if its name ends with \
+"mixin" (case insensitive).'}
+ ),
+
+ ('zope',
+ {'default' : False, 'type' : 'yn', 'metavar': '<y_or_n>',
+ 'help' : 'When zope mode is activated, consider the \
+acquired-members option to ignore access to some undefined attributes.'}
+ ),
+ ('acquired-members',
+ {'default' : (
+ 'REQUEST', 'acl_users', 'aq_parent'),
+ 'type' : 'csv',
+ 'metavar' : '<members names>',
+ 'help' : 'List of members which are usually get through \
+zope\'s acquisition mecanism and so shouldn\'t trigger E0201 when accessed \
+(need zope=yes to be considered.'}
+ ),
+ )
+
+ def visit_getattr(self, node):
+ """check that the accessed attribute exists"""
+ # if we are running in zope mode, is it an acquired attribute ?
+ if self.config.zope and node.attrname in self.config.acquired_members:
+ return
+ try:
+ infered = list(node.expr.infer())
+ for owner in infered:
+ # skip yes object
+ if owner is astng.YES:
+ continue
+ # if there is ambiguity, skip None
+ if len(infered) > 1 and isinstance(owner, astng.Const) \
+ and owner.value is None:
+ continue
+ # XXX "super" call
+ owner_name = getattr(owner, 'name', 'None')
+ if owner_name == 'super' and \
+ owner.root().name == '__builtin__':
+ continue
+ if getattr(owner, 'type', None) == 'metaclass':
+ continue
+ if self.config.ignore_mixin_members \
+ and owner_name[-5:].lower() == 'mixin':
+ continue
+ #print owner.name, owner.root().name
+ try:
+ owner.getattr(node.attrname)
+ except AttributeError:
+ # XXX method / function
+ continue
+ except astng.NotFoundError:
+ if isinstance(owner, astng.Instance):
+ if hasattr(owner, 'has_dynamic_getattr') and owner.has_dynamic_getattr():
+ continue
+ # XXX
+ if getattr(owner, 'name', None) == 'Values' and \
+ owner.root().name == 'optparse':
+ continue
+ _type = 'Instance of'
+ elif isinstance(owner, astng.Module):
+ _type = 'Module'
+ else:
+ _type = 'Class'
+ self.add_message('E1101', node=node,
+ args=(_type, owner_name, node.attrname))
+ # XXX: stop on the first found
+ # this is a bad solution to fix func_noerror_socket_member.py
+ break
+ except astng.InferenceError:
+ pass
+
+ def visit_assign(self, node):
+ """check that if assigning to a function call, the function is
+ possibly returning something valuable
+ """
+ if not isinstance(node.expr, astng.CallFunc):
+ return
+ function_node = self._safe_infer(node.expr.node)
+ # skip class, generator and uncomplete function definition
+ if not (isinstance(function_node, astng.Function) and
+ function_node.root().fully_defined()):
+ return
+ if function_node.is_generator() \
+ or function_node.is_abstract(pass_is_abstract=False):
+ return
+ returns = list(function_node.nodes_of_class(astng.Return,
+ skip_klass=astng.Function))
+ if len(returns) == 0:
+ self.add_message('E1111', node=node)
+ else:
+ for rnode in returns:
+ if not (isinstance(rnode.value, astng.Name)
+ and rnode.value.name == 'None'):
+ break
+ else:
+ self.add_message('W1111', node=node)
+
+ def visit_callfunc(self, node):
+ """check that called method are infered to callable objects
+ """
+ called = self._safe_infer(node.node)
+ # only function, generator and object defining __call__ are allowed
+ if called is not None and not called.callable():
+ self.add_message('E1102', node=node, args=node.node.as_string())
+
+ def _safe_infer(self, node):
+ """return the infered value for the given node.
+ Return None if inference failed or if there is some ambiguity (more than
+ one node has been infered)
+ """
+ try:
+ inferit = node.infer()
+ value = inferit.next()
+ except astng.InferenceError:
+ return
+ try:
+ inferit.next()
+ return # None if there is ambiguity on the infered node
+ except StopIteration:
+ return value
+
+def register(linter):
+ """required method to auto register this checker """
+ linter.register_checker(TypeChecker(linter))
diff --git a/checkers/utils.py b/checkers/utils.py
new file mode 100644
index 000000000..cb3a99b4d
--- /dev/null
+++ b/checkers/utils.py
@@ -0,0 +1,144 @@
+# pylint: disable-msg=W0611
+#
+# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""some functions that may be usefull for various checkers
+"""
+
+__revision__ = '$Id: utils.py,v 1.16 2006-03-03 09:25:34 syt Exp $'
+
+from logilab import astng
+from logilab.astng.utils import are_exclusive
+try:
+ # python >= 2.4
+ COMP_NODE_TYPES = (astng.ListComp, astng.GenExpr)
+ FOR_NODE_TYPES = (astng.For, astng.ListCompFor, astng.GenExprFor)
+except AttributeError:
+ COMP_NODE_TYPES = astng.ListComp
+ FOR_NODE_TYPES = (astng.For, astng.ListCompFor)
+
+def is_error(node):
+ """return true if the function does nothing but raising an exception"""
+ for child_node in node.code.getChildNodes():
+ if isinstance(child_node, astng.Raise):
+ return True
+ return False
+
+def is_empty(node):
+ """return true if the given node does nothing but 'pass'"""
+ for child_node in node.getChildNodes():
+ if isinstance(child_node, astng.Pass):
+ return True
+ else:
+ return False
+
+builtins = __builtins__.copy()
+SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__')
+
+def is_builtin(name): # was is_native_builtin
+ """return true if <name> could be considered as a builtin defined by python
+ """
+ if builtins.has_key(name):
+ return True
+ if name in SPECIAL_BUILTINS:
+ return True
+ return False
+
+def is_defined_before(var_node, comp_node_types=COMP_NODE_TYPES):
+ """return True if the variable node is defined by a parent node (list
+ or generator comprehension, lambda) or in a previous sibling node
+ one the same line (statement_defining ; statement_using)
+ """
+ varname = var_node.name
+ _node = var_node.parent
+ while _node:
+ if isinstance(_node, comp_node_types):
+ for ass_node in _node.nodes_of_class(astng.AssName):
+ if ass_node.name == varname:
+ return True
+ elif isinstance(_node, astng.For):
+ for ass_node in _node.assign.nodes_of_class(astng.AssName):
+ if ass_node.name == varname:
+ return True
+ elif isinstance(_node, (astng.Lambda, astng.Function)):
+ if varname in _node.argnames:
+ return True
+ if getattr(_node, 'name', None) == varname:
+ return True
+ break
+ _node = _node.parent
+ # possibly multiple statements on the same line using semi colon separator
+ stmt = var_node.statement()
+ _node = stmt.previous_sibling()
+ lineno = stmt.lineno
+ while _node and _node.lineno == lineno:
+ for ass_node in _node.nodes_of_class(astng.AssName):
+ if ass_node.name == varname:
+ return True
+ for imp_node in _node.nodes_of_class( (astng.From, astng.Import)):
+ if varname in [name[1] or name[0] for name in imp_node.names]:
+ return True
+ _node = _node.previous_sibling()
+ return False
+
+def is_func_default(node):
+ """return true if the name is used in function default argument's value
+ """
+ parent = node.parent
+ if parent is None:
+ return 0
+ if isinstance(parent, astng.Function) and parent.defaults and \
+ node in parent.defaults:
+ return 1
+ return is_func_default(parent)
+
+def is_ancestor_name(frame, node):
+ """return True if `frame` is a astng.Class node with `node` in the
+ subtree of its bases attribute
+ """
+ try:
+ bases = frame.bases
+ except AttributeError:
+ return False
+ for base in bases:
+ if node in base.nodes_of_class(astng.Name):
+ return True
+ return False
+
+def assign_parent(node):
+ """return the higher parent which is not an AssName, AssTuple or AssList
+ node
+ """
+ while node and isinstance(node, (astng.AssName,
+ astng.AssTuple,
+ astng.AssList)):
+ node = node.parent
+ return node
+
+def overrides_an_abstract_method(class_node, name):
+ """return True if pnode is a parent of node"""
+ for ancestor in class_node.ancestors():
+ if name in ancestor and isinstance(ancestor[name], astng.Function) and \
+ ancestor[name].is_abstract(pass_is_abstract=False):
+ return True
+ return False
+
+def overrides_a_method(class_node, name):
+ """return True if <name> is a method overriden from an ancestor"""
+ for ancestor in class_node.ancestors():
+ if name in ancestor and isinstance(ancestor[name], astng.Function):
+ return True
+ return False
diff --git a/checkers/variables.py b/checkers/variables.py
new file mode 100644
index 000000000..134da997f
--- /dev/null
+++ b/checkers/variables.py
@@ -0,0 +1,428 @@
+# Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""variables checkers for Python code
+"""
+
+__revision__ = "$Id: variables.py,v 1.69 2006-04-19 09:17:40 syt Exp $"
+
+from copy import copy
+
+from logilab.common.compat import enumerate
+from logilab import astng
+from logilab.astng.lookup import builtin_lookup
+
+from pylint.interfaces import IASTNGChecker
+from pylint.checkers import BaseChecker
+from pylint.checkers.utils import is_error, is_builtin, is_func_default, \
+ is_ancestor_name, assign_parent, are_exclusive, \
+ is_defined_before #, is_parent, FOR_NODE_TYPES
+
+
+
+MSGS = {
+ 'E0601': ('Using variable %r before assignment',
+ 'Used when a local variable is accessed before it\'s \
+ assignment.'),
+ 'E0602': ('Undefined variable %r',
+ 'Used when an undefined variable is accessed.'),
+
+ 'E0611': ('No name %r in module %r',
+ 'Used when a name cannot be found in a module.'),
+
+ 'W0601': ('Global variable %r undefined at the module level',
+ 'Used when a variable is defined through the "global" statement \
+ but the variable is not defined in the module scope.'),
+ 'W0602': ('Using global for %r but no assigment is done',
+ 'Used when a variable is defined through the "global" statement \
+ but no assigment to this variable is done.'),
+ 'W0603': ('Using the global statement', # W0121
+ 'Used when you use the "global" statement to update a global \
+ variable. PyLint just try to discourage this \
+ usage. That doesn\'t mean you can not use it !'),
+ 'W0604': ('Using the global statement at the module level', # W0103
+ 'Used when you use the "global" statement at the module level \
+ since it has no effect'),
+ 'W0611': ('Unused import %s',
+ 'Used when an imported module or variable is not used.'),
+ 'W0612': ('Unused variable %r',
+ 'Used when a variable is defined but not used.'),
+ 'W0613': ('Unused argument %r',
+ 'Used when a function or method argument is not used.'),
+
+ 'W0621': ('Redefining name %r from outer scope (line %s)',
+ 'Used when a variable\'s name hide a name defined in the outer \
+ scope.'),
+ 'W0622': ('Redefining built-in %r',
+ 'Used when a variable or function override a built-in.'),
+
+ 'W0631': ('Using possibly undefined loop variable %r',
+ 'Used when an loop variable (i.e. defined by a for loop or \
+ a list comprehension or a generator expression) is used outside \
+ the loop.'),
+ }
+
+class VariablesChecker(BaseChecker):
+ """checks for
+ * unused variables / imports
+ * undefined variables
+ * redefinition of variable from builtins or from an outer scope
+ * use of variable before assigment
+ """
+
+ __implements__ = IASTNGChecker
+
+ name = 'variables'
+ msgs = MSGS
+ priority = -1
+ options = (
+ ("init-import",
+ {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'help' : 'Tells wether we should check for unused import in \
+__init__ files.'}),
+ ("dummy-variables-rgx",
+ {'default': ('_|dummy'),
+ 'type' :'regexp', 'metavar' : '<regexp>',
+ 'help' : 'A regular expression matching names used \
+ for dummy variables (i.e. not used).'}),
+ ("additional-builtins",
+ {'default': (), 'type' : 'csv',
+ 'metavar' : '<comma separated list>',
+ 'help' : 'List of additional names supposed to be defined in \
+builtins. Remember that you should avoid to define new builtins when possible.'
+ }),
+ )
+ def __init__(self, linter=None):
+ BaseChecker.__init__(self, linter)
+ self._to_consume = None
+ self._checking_mod_attr = None
+ self._vars = None
+
+ def visit_module(self, node):
+ """visit module : update consumption analysis variable
+ checks globals doesn't overrides builtins
+ """
+ self._to_consume = [(copy(node.locals), {}, 'module')]
+ self._vars = []
+ for name, stmts in node.locals.items():
+ if name in ('__name__', '__doc__', '__file__', '__path__') \
+ and len(stmts) == 1:
+ # only the definition added by the astng builder, continue
+ continue
+ if self._is_builtin(name):
+ self.add_message('W0622', args=name, node=stmts[0])
+
+ def leave_module(self, node):
+ """leave module: check globals
+ """
+ assert len(self._to_consume) == 1
+ not_consumed = self._to_consume.pop()[0]
+ # don't check unused imports in __init__ files
+ if not self.config.init_import and node.package:
+ return
+ for name, stmts in not_consumed.items():
+ stmt = stmts[0]
+ if isinstance(stmt, astng.Import) or (
+ isinstance(stmt, astng.From) and stmt.modname != '__future__'):
+ self.add_message('W0611', args=name, node=stmt)
+ del self._to_consume
+ del self._vars
+
+ def visit_class(self, node):
+ """visit class: update consumption analysis variable
+ """
+ self._to_consume.append((copy(node.locals), {}, 'class'))
+
+ def leave_class(self, _):
+ """leave class: update consumption analysis variable
+ """
+ # do not check for not used locals here (no sense)
+ self._to_consume.pop()
+
+ def visit_lambda(self, node):
+ """visit lambda: update consumption analysis variable
+ """
+ self._to_consume.append((copy(node.locals), {}, 'lambda'))
+
+ def leave_lambda(self, _):
+ """leave lambda: update consumption analysis variable
+ """
+ # do not check for not used locals here
+ self._to_consume.pop()
+
+ def visit_function(self, node):
+ """visit function: update consumption analysis variable and check locals
+ """
+ globs = node.root().globals
+ for name, stmt in node.items():
+ if globs.has_key(name) and not isinstance(stmt, astng.Global):
+ line = globs[name][0].lineno
+ self.add_message('W0621', args=(name, line), node=stmt)
+ elif self._is_builtin(name):
+ self.add_message('W0622', args=name, node=stmt)
+ self._to_consume.append((copy(node.locals), {}, 'function'))
+ self._vars.append({})
+
+ def leave_function(self, node):
+ """leave function: check function's locals are consumed
+ """
+ not_consumed = self._to_consume.pop()[0]
+ self._vars.pop(0)
+ is_method = node.is_method()
+ klass = node.parent.frame()
+ # don't check arguments of abstract methods or within an interface
+ if is_method and (klass.type == 'interface' or node.is_abstract()):
+ return
+ if is_error(node):
+ return
+ authorized_rgx = self.config.dummy_variables_rgx
+ for name, stmts in not_consumed.items():
+ # ignore some special names specified by user configuration
+ if authorized_rgx.match(name):
+ continue
+ # ignore names imported by the global statement
+ # FIXME: should only ignore them if it's assigned latter
+ stmt = stmts[0]
+ if isinstance(stmt, astng.Global):
+ continue
+ # care about functions with unknown argument (builtins)
+ if node.argnames is not None and name in node.argnames:
+ # don't warn if the first argument of a method is not used
+ if is_method and node.argnames and name == node.argnames[0]:
+ continue
+ # don't check callback arguments
+ if node.name.startswith('cb_') or \
+ node.name.endswith('_cb'):
+ continue
+ self.add_message('W0613', args=name, node=node)
+ else:
+ self.add_message('W0612', args=name, node=stmt)
+
+ def visit_global(self, node):
+ """check names imported exists in the global scope"""
+ frame = node.frame()
+ if isinstance(frame, astng.Module):
+ self.add_message('W0604', node=node)
+ return
+ module = frame.root()
+ default_message = True
+ for name in node.names:
+ try:
+ assign_nodes = module.getattr(name)
+ except astng.NotFoundError:
+ # unassigned global, skip
+ assign_nodes = []
+ for anode in assign_nodes:
+ if anode.frame() is frame:
+ # same scope level assigment
+ break
+ else:
+ # global but no assigment
+ self.add_message('W0602', args=name, node=node)
+ default_message = False
+ if not assign_nodes:
+ continue
+ for anode in assign_nodes:
+ if anode.frame() is module:
+ # module level assigment
+ break
+ else:
+ # global undefined at the module scope
+ self.add_message('W0601', args=name, node=node)
+ default_message = False
+ if default_message:
+ self.add_message('W0603', node=node)
+
+ def _loopvar_name(self, node, name):
+ # filter variables according to node's scope
+ astmts = [stmt for stmt in node.lookup(name)[1]
+ if hasattr(stmt, 'ass_type') and
+ not stmt.statement().parent_of(node)]
+ # filter variables according their respective scope
+ if not astmts or astmts[0].statement().parent_of(node):
+ _astmts = []
+ else:
+ _astmts = astmts[:1]
+ for i, stmt in enumerate(astmts[1:]):
+ if astmts[i].statement().parent_of(stmt):
+ continue
+ _astmts.append(stmt)
+ astmts = _astmts
+ if len(astmts) == 1:
+ ass = astmts[0].ass_type()
+ if isinstance(ass, (astng.For, astng.ListCompFor, astng.GenExpr)) \
+ and not ass.statement() is node.statement():
+ self.add_message('W0631', args=name, node=node)
+
+ def visit_name(self, node):
+ """check that a name is defined if the current scope and doesn't
+ redefine a built-in
+ """
+ name = node.name
+ stmt = node.statement()
+ frame = stmt.frame()
+ # if the name node is used as a function default argument's value, then
+ # start from the parent frame of the function instead of the function
+ # frame
+ if is_func_default(node) or is_ancestor_name(frame, node):
+ start_index = len(self._to_consume) - 2
+ else:
+ start_index = len(self._to_consume) - 1
+ # iterates through parent scopes, from the inner to the outer
+ for i in range(start_index, -1, -1):
+ to_consume, consumed, scope_type = self._to_consume[i]
+ # if the current scope is a class scope but it's not the inner
+ # scope, ignore it. This prevents to access this scope instead of
+ # the globals one in function members when there are some common
+ # names
+ if scope_type == 'class' and i != start_index:
+ continue
+ # the name has already been consumed, only check it's not a loop
+ # variable used outside the loop
+ if consumed.has_key(name):
+ self._loopvar_name(node, name)
+ break
+ # mark the name as consumed if it's defined in this scope
+ # (ie no KeyError is raise by "to_consume[name]"
+ try:
+ consumed[name] = to_consume[name]
+ # checks for use before assigment
+ # FIXME: the last condition should just check attribute access
+ # is protected by a try: except NameError: (similar to #9219)
+ defnode = assign_parent(to_consume[name][0])
+ if defnode is not None:
+ defstmt = defnode.statement()
+ defframe = defstmt.frame()
+ maybee0601 = True
+ if not frame is defframe:
+ maybee0601 = False
+ elif defframe.parent is None:
+ # we are at the module level, check the name is not
+ # defined in builtins
+ if builtin_lookup(name)[1]:
+ maybee0601 = False
+ else:
+ # we are in a local scope, check the name is not
+ # defined in global or builtin scope
+ if defframe.root().lookup(name)[1]:
+ maybee0601 = False
+ if (maybee0601
+ and stmt.source_line() <= defstmt.source_line()
+ and not is_defined_before(node)
+ and not are_exclusive(stmt, defstmt)):
+ self.add_message('E0601', args=name, node=node)
+ del to_consume[name]
+ # check it's not a loop variable used outside the loop
+ self._loopvar_name(node, name)
+ break
+ except KeyError:
+ continue
+ else:
+ # we have not found the name, if it isn't a builtin, that's an
+ # undefined name !
+ if not self._is_builtin(name):
+ self.add_message('E0602', args=name, node=node)
+
+ def visit_import(self, node):
+ """check modules attribute accesses"""
+ for name, _ in node.names:
+ name_parts = name.split('.')
+ try:
+ module = node.infer(name_parts[0], asname=False).next()
+ except astng.ResolveError:
+ continue
+ self._check_module_attrs(node, module, name_parts[1:])
+
+ def visit_from(self, node):
+ """check modules attribute accesses"""
+ name_parts = node.modname.split('.')
+ try:
+ module = node.root().import_module(name_parts[0])
+ except KeyboardInterrupt:
+ raise
+ except:
+ return
+ module = self._check_module_attrs(node, module, name_parts[1:])
+ if not module:
+ return
+ for name, _ in node.names:
+ if name == '*':
+ continue
+ self._check_module_attrs(node, module, name.split('.'))
+
+## def leave_getattr(self, node):
+## """check modules attribute accesses
+
+## this function is a "leave_" because when parsing 'a.b.c'
+## we want to check the innermost expression first.
+## """
+## if isinstance(node.expr, astng.Name):
+## try:
+## module = node.expr.infer().next()
+## except astng.InferenceError:
+## return
+## if not isinstance(module, astng.Module):
+## # Not a module, don't check
+## return
+## elif self._checking_mod_attr is not None:
+## module = self._checking_mod_attr
+## else:
+## return
+## self._checking_mod_attr = self._check_module_attrs(node, module,
+## [node.attrname])
+
+## def leave_default(self, node):
+## """by default, reset the _checking_mod_attr attribute"""
+## self._checking_mod_attr = None
+
+ def _check_module_attrs(self, node, module, module_names):
+ """check that module_names (list of string) are accessible through the
+ given module
+ if the latest access name corresponds to a module, return it
+ """
+ assert isinstance(module, astng.Module), module
+ while module_names:
+ name = module_names.pop(0)
+ if name == '__dict__':
+ module = None
+ break
+ try:
+ module = module.getattr(name)[0].infer().next()
+ except astng.NotFoundError:
+ self.add_message('E0611', args=(name, module.name), node=node)
+ return None
+ except astng.InferenceError:
+ return None
+ if module_names:
+ # FIXME: other message if name is not the latest part of
+ # module_names ?
+ modname = module and module.name or '__dict__'
+ self.add_message('E0611', node=node,
+ args=('.'.join(module_names), modname))
+ return None
+ if isinstance(module, astng.Module):
+ return module
+ return None
+
+ def _is_builtin(self, name):
+ """return True if the name is defined in the native builtin or
+ in the user specific builtins
+ """
+ return is_builtin(name) or name in self.config.additional_builtins
+
+
+def register(linter):
+ """required method to auto register this checker"""
+ linter.register_checker(VariablesChecker(linter))
diff --git a/config.py b/config.py
new file mode 100644
index 000000000..d5b4e9b09
--- /dev/null
+++ b/config.py
@@ -0,0 +1,137 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+ utilities for PyLint configuration :
+ _ pylintrc
+ _ pylint.d (PYLINT_HOME)
+"""
+
+__revision__ = "$Id: config.py,v 1.15 2005-12-13 16:26:35 syt Exp $"
+
+import pickle
+import os
+import sys
+from os.path import exists, isfile, join, expanduser, abspath
+
+# pylint home is used to save old runs results ################################
+
+if os.environ.has_key('PYLINTHOME'):
+ PYLINT_HOME = os.environ['PYLINTHOME']
+else:
+ USER_HOME = expanduser('~')
+ if USER_HOME == '~':
+ PYLINT_HOME = ".pylint.d"
+ else:
+ PYLINT_HOME = join(USER_HOME, '.pylint.d')
+
+if not exists(PYLINT_HOME):
+ try:
+ os.mkdir(PYLINT_HOME)
+ except OSError:
+ print >> sys.stderr, 'Unable to create directory %s' % PYLINT_HOME
+
+def get_pdata_path(base_name, recurs):
+ """return the path of the file which should contain old search data for the
+ given base_name with the given options values
+ """
+ base_name = base_name.replace(os.sep, '_')
+ return join(PYLINT_HOME, "%s%s%s"%(base_name, recurs, '.stats'))
+
+def load_results(base):
+ """try to unpickle and return data from file if it exists and is not
+ corrupted
+
+ return an empty dictionary if it doesn't exists
+ """
+ data_file = get_pdata_path(base, 1)
+ try:
+ return pickle.load(open(data_file))
+ except:
+ return {}
+
+def save_results(results, base):
+ """pickle results"""
+ data_file = get_pdata_path(base, 1)
+ try:
+ pickle.dump(results, open(data_file, 'w'))
+ except OSError:
+ print >> sys.stderr, 'Unable to create file %s' % data_file
+
+# location of the configuration file ##########################################
+
+# is there a pylint rc file in the current directory ?
+if exists('pylintrc'):
+ PYLINTRC = abspath('pylintrc')
+elif os.environ.has_key('PYLINTRC') and exists(os.environ['PYLINTRC']):
+ PYLINTRC = os.environ['PYLINTRC']
+else:
+ USER_HOME = expanduser('~')
+ if USER_HOME == '~' or USER_HOME == '/root':
+ PYLINTRC = ".pylintrc"
+ else:
+ PYLINTRC = join(USER_HOME, '.pylintrc')
+if not isfile(PYLINTRC):
+ if isfile('/etc/pylintrc'):
+ PYLINTRC = '/etc/pylintrc'
+ else:
+ PYLINTRC = None
+
+ENV_HELP = '''
+The following environment variables are used :
+ * PYLINTHOME
+ path to the directory where data of persistent run will be stored. If not
+found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working
+directory) . The current PYLINTHOME is %(PYLINT_HOME)s.
+ * PYLINTRC
+ path to the configuration file. If not found, it will use the first
+existant file in ~/.pylintrc, /etc/pylintrc. The current PYLINTRC is
+%(PYLINTRC)s.
+''' % globals()
+
+# evaluation messages #########################################################
+
+def get_note_message(note):
+ """return a message according to note
+ note is a float < 10 (10 is the highest note)
+ """
+ assert note <= 10, "Note is %.2f. Either you cheated, or pylint's \
+broken!" % note
+ if note < 0:
+ msg = 'You have to do something quick !'
+ elif note < 1:
+ msg = 'Hey! This is really dreadful. Or maybe pylint is buggy?'
+ elif note < 2:
+ msg = "Come on! You can't be proud of this code"
+ elif note < 3:
+ msg = 'Hum... Needs work.'
+ elif note < 4:
+ msg = 'Wouldn\'t you be a bit lazy?'
+ elif note < 5:
+ msg = 'A little more work would make it acceptable.'
+ elif note < 6:
+ msg = 'Just the bare minimum. Give it a bit more polish. '
+ elif note < 7:
+ msg = 'This is okay-ish, but I\'m sure you can do better.'
+ elif note < 8:
+ msg = 'If you commit now, people should not be making nasty \
+comments about you on c.l.py'
+ elif note < 9:
+ msg = 'That\'s pretty good. Good work mate.'
+ elif note < 10:
+ msg = 'So close to being perfect...'
+ else:
+ msg = 'Wow ! Now this deserves our uttermost respect.\nPlease send \
+your code to python-projects@logilab.org'
+ return msg
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 000000000..c28605ead
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,205 @@
+pylint (0.11.0-1) unstable; urgency=low
+
+ * new upstream release, depending on python-astng 0.16
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 19 Apr 2006 18:10:47 +0200
+
+pylint (0.10.0-1) unstable; urgency=low
+
+ * new upstream release, depending on python-astng 0.15
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 6 Mar 2006 09:43:19 +0100
+
+pylint (0.9.0-3) unstable; urgency=low
+
+ * Added missing provides/replaces/conflicts on pylint-test (closes: #352316)
+
+ -- Alexandre Fayolle <afayolle@debian.org> Mon, 13 Feb 2006 10:07:26 +0100
+
+pylint (0.9.0-2) unstable; urgency=low
+
+ * Build a single package which installs modules in /usr/lib/site-python
+ (closes: #351130)
+ * Remove duplication from man page (closes: #349689)
+ * Fixed typo in control file
+ * upload new release to Debian
+
+ -- Alexandre Fayolle <afayolle@debian.org> Fri, 10 Feb 2006 16:03:37 +0100
+
+pylint (0.9.0-1) unstable; urgency=low
+
+ * fix false positive with staticmethod used on a metaclass (closes: #341121)
+ * reorganization to install into site-python, removing the need for
+ pythonX.X- packages and for the pylint-common and pylint-test packages
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 10 Jan 2006 14:19:57 +0100
+
+pylint (0.8.1-1) unstable; urgency=low
+
+ * added missing dependancy to logilab-astng
+ * added missing .docs and .examples files
+ * update control'standards-version to 3.6.2
+ * fixed FSF address in the copyright file
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 7 Nov 2005 15:40:52 +0100
+
+pylint (0.8.0-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 21 Oct 2005 18:44:24 +0200
+
+pylint (0.7.0-1) unstable; urgency=low
+
+ * new upstream release (closes: #310957)
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 27 May 2005 11:17:44 +0200
+
+pylint (0.6.4-1) unstable; urgency=low
+
+ * new upstream release
+ * added man page for pylint
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 14 Apr 2005 12:02:15 +0200
+
+pylint (0.6.3-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 24 Feb 2005 17:44:35 +0100
+
+pylint (0.6.2-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 16 Feb 2005 12:00:47 +0100
+
+pylint (0.6.1-1) unstable; urgency=low
+
+ * new upstream release
+ * added option to specify rc file location (closes: #265159)
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 4 Feb 2005 16:48:09 +0100
+
+pylint (0.6.0-1) unstable; urgency=low
+
+ * new upstream release
+ * build package for python 2.4
+ * remove unused directory from logilab-common.dirs
+ * updated copyright
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 20 Jan 2005 18:06:29 +0100
+
+pylint (0.5.0-2) unstable; urgency=low
+
+ * Patched pylint.el using latest upsrteam CVS (closes: #280870)
+
+ -- Alexandre Fayolle <afayolle@debian.org> Mon, 15 Nov 2004 10:59:51 +0100
+
+pylint (0.5.0-1) unstable; urgency=low
+
+ * use Build-depends instead of Build-depends-indep in control
+ * new upstream release
+ * updated debian/watch file to version 2
+
+ -- Alexandre Fayolle <afayolle@debian.org> Tue, 9 Nov 2004 16:22:47 +0100
+
+pylint (0.4.2-2) unstable; urgency=low
+
+ * fixed typos in debian/control (closes: #265156)
+ * updated description of pylint-test
+ * changed dependency on pylint-common to a recommendation (closes: #265157)
+ * updated maintainer address
+
+ -- Alexandre Fayolle <afayolle@debian.org> Sun, 15 Aug 2004 10:39:06 +0200
+
+pylint (0.4.2-1) unstable; urgency=low
+
+ * new upstream release
+ * initial upload to Debian (closes: #258235)
+
+ -- Alexandre Fayolle <alexandre.fayolle@logilab.fr> Thu, 8 Jul 2004 12:54:18 +0200
+
+pylint (0.4.0-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 10 May 2004 17:03:04 +0200
+
+pylint (0.3.3-1) unstable; urgency=low
+
+ * new upstream release
+ * emacs lisp for pylint in a new pylint-common package
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 16 Feb 2004 18:09:23 +0100
+
+pylint (0.3.2-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 23 Dec 2003 14:56:04 +0100
+
+pylint (0.3.1-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 5 Dec 2003 16:20:44 +0100
+
+pylint (0.3.0-1) unstable; urgency=low
+
+ * new upstream release
+ * depends on logilab.common >= 0.4
+ * build depends on debhelper >= 4.0
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 19 Nov 2003 11:07:45 +0100
+
+pylint (0.2.1-2) unstable; urgency=low
+
+ * fixed dependency on logilab-common (>=0.3.4) since earlier versions
+ caused bugs with some python2.3 code
+ * included sample pylintrc files with the documentation
+ * added documentation that had disappeared in the previous 0.2.1-1
+ * only puts html documentation in doc/html/, all others in doc/
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 10 Oct 2003 12:11:53 +0200
+
+pylint (0.2.1-1) unstable; urgency=low
+
+ * new upstream release
+ * package renamed to pylint instead of logilab-pylint
+ * move tests in a separated package
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 10 Oct 2003 09:39:22 +0200
+
+logilab-pylint (0.2.0-1) unstable; urgency=low
+
+ * new upstream release
+ * dropped python2.1 support
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 12 Sep 2003 18:26:15 +0200
+
+logilab-pylint (0.1.2-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 11 Jun 2003 15:21:44 +0200
+
+logilab-pylint (0.1.1-2) unstable; urgency=low
+
+ * fix dependencie to logilab.common
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 4 Jun 2003 18:07:45 +0200
+
+logilab-pylint (0.1.1-1) unstable; urgency=low
+
+ * New upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 19 May 2003 15:10:25 +0200
+
+logilab-pylint (0.1.0-1) unstable; urgency=low
+
+ * Initial Release.
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 23 Apr 2003 14:42:05 +0200
+
diff --git a/debian/control b/debian/control
new file mode 100644
index 000000000..25560a0f5
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,30 @@
+Source: pylint
+Section: python
+Priority: optional
+Maintainer: Sylvain Thenault <sylvain.thenault@logilab.fr>
+Uploaders: Alexandre Fayolle <afayolle@debian.org>
+Build-Depends: debhelper (>= 4.0.0), python-dev
+Standards-Version: 3.6.2
+
+Package: pylint
+Architecture: all
+Depends: python, python-logilab-common (>= 0.13.0), python-logilab-astng (>= 0.16.0), python-tk, emacsen-common
+Provides: python2.2-pylint, python2.3-pylint, python2.4-pylint, pylint-common, pylint-test
+Conflicts: python2.2-pylint, python2.3-pylint, python2.4-pylint, pylint-common, pylint-test
+Replaces: python2.2-pylint, python2.3-pylint, python2.4-pylint, pylint-common, pylint-test
+Description: python code static checker
+ Pylint is a Python source code analyzer which looks for programming
+ errors, helps enforcing a coding standard and sniffs for some code
+ smells (as defined in Martin Fowler's Refactoring book)
+ .
+ Pylint can be seen as another PyChecker since nearly all tests you
+ can do with PyChecker can also be done with Pylint. However, Pylint
+ offers some more features, like checking length of lines of code,
+ checking if variable names are well-formed according to your coding
+ standard, or checking if declared interfaces are truly implemented,
+ and much more.
+ .
+ Additionally, it is possible to write plugins to add your own checks.
+ .
+ Homepage: http://www.logilab.org/projects/pylint
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 000000000..e574a1339
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,29 @@
+This package was debianized by Sylvain Thenault <sylvain.thenault@logilab.fr> Sat, 13 Apr 2002 19:05:23 +0200.
+
+It was downloaded from ftp://ftp.logilab.org/pub/pylint
+
+Upstream Author:
+
+ Sylvain Thenault <sylvain.thenault@logilab.fr>
+
+Copyright:
+
+Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com).
+Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+On Debian systems, the complete text of the GNU General Public License
+may be found in '/usr/share/common-licenses/GPL'.
diff --git a/debian/pylint.dirs b/debian/pylint.dirs
new file mode 100644
index 000000000..3b56eda3b
--- /dev/null
+++ b/debian/pylint.dirs
@@ -0,0 +1,7 @@
+usr/lib/site-python
+usr/lib/site-python/pylint
+usr/share/doc/pylint
+usr/share/emacs/site-lisp/
+usr/share/emacs/site-lisp/pylint
+usr/share/doc/pylint
+usr/share/doc/pylint/test
diff --git a/debian/pylint.docs b/debian/pylint.docs
new file mode 100644
index 000000000..27ad78732
--- /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 000000000..66a848063
--- /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 000000000..9795dc45a
--- /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 000000000..646bd819b
--- /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 000000000..e39721e20
--- /dev/null
+++ b/debian/pylint.examples
@@ -0,0 +1 @@
+examples/*
diff --git a/debian/pylint.manpages b/debian/pylint.manpages
new file mode 100644
index 000000000..12b9a3324
--- /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 000000000..aa64362cb
--- /dev/null
+++ b/debian/pylint.postinst
@@ -0,0 +1,24 @@
+#! /bin/sh -e
+#
+
+
+
+# precompile python files
+VERSION=2.3
+PACKAGEDIR=/usr/lib/site-python/pylint
+case "$1" in
+ configure|abort-upgrade|abort-remove|abort-deconfigure)
+ python$VERSION -O /usr/lib/python$VERSION/compileall.py -q $PACKAGEDIR
+ python$VERSION /usr/lib/python$VERSION/compileall.py -q $PACKAGEDIR
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/pylint.prerm b/debian/pylint.prerm
new file mode 100644
index 000000000..dbe214406
--- /dev/null
+++ b/debian/pylint.prerm
@@ -0,0 +1,14 @@
+#! /bin/sh -e
+#
+
+# remove .pyc and .pyo files
+dpkg --listfiles pylint |
+ awk '$0~/\.py$/ {print $0"c\n" $0"o"}' |
+ xargs rm -f >&2
+
+
+
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/rules b/debian/rules
new file mode 100644
index 000000000..35284a422
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,84 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+#
+# adapted by Logilab for automatic generation by debianize
+# (part of the devtools project, http://www.logilab.org/projects/devtools)
+#
+# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# This is the debhelper compatability version to use.
+export DH_COMPAT=4
+
+
+
+build: build-stamp
+build-stamp:
+ dh_testdir
+ python setup.py -q build
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+ rm -rf build
+ find . -name "*.pyc" | xargs rm -f
+ rm -f changelog.gz
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+ python setup.py -q install_lib --no-compile --install-dir=debian/pylint/usr/lib/site-python
+ python setup.py -q install_headers --install-dir=debian/pylint/usr/include/
+ python setup.py -q install_scripts --install-dir=debian/pylint/usr/bin/
+ # remove test directory (installed in a separated package)
+ rm -rf debian/pylint/usr/lib/site-python/pylint/test
+ if head -1 debian/pylint/usr/bin/pylint | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \
+ sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/pylint; \
+ fi
+ chmod a+x debian/pylint/usr/bin/pylint
+ if head -1 debian/pylint/usr/bin/pylint-gui | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \
+ sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/pylint-gui; \
+ fi
+ chmod a+x debian/pylint/usr/bin/pylint-gui
+ if head -1 debian/pylint/usr/bin/symilar | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \
+ sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/symilar; \
+ fi
+ chmod a+x debian/pylint/usr/bin/symilar
+ install -m 644 elisp/pylint.el debian/pylint/usr/share/emacs/site-lisp/pylint/
+ # install tests
+ (cd test && find . -type f -not \( -path '*/CVS/*' -or -name '*.pyc' \) -exec install -D --mode=644 {} ../debian/pylint/usr/share/doc/pylint/test/{} \;)
+
+
+# Build architecture-independent files here.
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_install -i
+ gzip -9 -c ChangeLog > changelog.gz
+ dh_installchangelogs -i
+ dh_installexamples -i
+ dh_installdocs -i README TODO changelog.gz
+ dh_installman -i
+ dh_installemacsen
+ dh_link -i
+ dh_compress -i -X.py -X.ini -X.xml -Xtest
+ dh_fixperms -i
+ dh_installdeb -i
+ dh_gencontrol -i
+ dh_md5sums -i
+ dh_builddeb -i
+
+
+
+binary: binary-indep
+.PHONY: build clean binary binary-indep
+
diff --git a/debian/watch b/debian/watch
new file mode 100644
index 000000000..52cc0a24b
--- /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 000000000..b28f83ef9
--- /dev/null
+++ b/doc/FAQ.txt
@@ -0,0 +1,154 @@
+Frequently Asked Questions / Usage tips for PyLint
+==================================================
+
+
+Question:
+ Is it possible to give file as argument to pylint, instead of module ?
+
+Answer:
+ pylint expects the name of a package or module as argument. As a convenience,
+ you can give to it a file name if it's possible to guess a module name from
+ the file's path, using the python path. Some examples :
+
+ "pylint mymodule.py" should always works since the current working
+ directory is automatically added on top of the python path
+
+ "pylint directory/mymodule.py" will work if "directory" is a python
+ package (i.e. has an __init__.py file) or if "directory" is in the
+ python path.
+
+ "pylint /whatever/directory/mymodule.py" will work if either:
+
+ - "/whatever/directory" is in the python path
+
+ - your cwd is "/whatever/directory"
+
+ - "directory" is a python package and "/whatever" is in the python
+ path
+
+ - "directory" is a python package and your cwd is "/whatever"
+ and so on...
+
+
+
+Question:
+ I'm using psyobj from psyco_ and get a lot of spurious "unused variables
+ messages". Is it normal ?
+
+Answer:
+ Yes. That's actually due to a bug in psyco, making the locals()
+ function for objects inheriting from *psyobj* returning an empty
+ dictionary. For the moment, the only way to fix this is to use the
+ PYLINT_IMPORT environment variable to not use psyco during pylint
+ checking. Sample code ::
+
+ import os
+ try:
+ if os.environ.has_key('PYLINT_IMPORT'):
+ raise ImportError()
+ from psyco.classes import psyobj
+ except ImportError:
+ class psyobj:
+ pass
+
+ NOTICE: this problem should not occurs with pylint >= 0.5 since from
+ this version pylint is not looking anymore for information in living
+ objects (i.e. it doesn't anymore import analysed modules)
+
+
+
+Question:
+ I've a function / method which is a callback where I do not have any
+ control on received argument, and pylint is complaining about unused
+ arguments. What can I do to avoid those warnings ?
+
+Answer:
+ prefix (ui) the callback's name by `cb_`, as in cb_onclick(...). By
+ doing so arguments usage won't be checked. Another solution is to
+ use one of the name defined in the "dummy-variables" configuration
+ variable for unused argument ("_" and "dummy" by default).
+
+
+
+Question:
+ When pylint is considering a class as an interface ?
+
+Answer:
+ A class is considered as an interface if there is a class named
+ "Interface" somewhere in it ancestor's tree.
+
+
+
+Question:
+ When pylint is considering that a class is implementing a given
+ interface ?
+
+Answer:
+ Pylint is using the Zope 2 interfaces conventions, and so is
+ considering that a class is implementing interfaces listed in its
+ __implements__ attribute.
+
+
+
+Question:
+ When pylint is considering a class as an abstract class ?
+
+Answer:
+ A class is considered as an abstract class if at least one of its
+ methods is doing nothing but raising NotImplementedError
+
+
+
+Question:
+ Is there some way to disable some message for a particular module
+ only ?
+
+Answer:
+ Yes, you can disable or enable (globally disabled) message at the
+ module level by adding the corresponding option in a comment at the
+ top of the file: ::
+
+ # pylint: disable-msg=W0401, E0202
+ # pylint: enable-msg=C0302
+
+
+
+Question:
+ I've a mixin class relying on attributes of the mixed class, and I
+ would like to not have the "access to undefined member" message on
+ this class. Is it possible ?
+
+Answer:
+ Yes :o) To do so you have to set the ignore-mixin-members option to
+ "yes" (this is the default value) and to name your mixin class with
+ a name which ends with "mixin" (whatever case)
+
+
+
+Question:
+ Is it possible to locally disabling a particular message for a block
+ of code or for a single line of code ?
+Answer:
+ Yes, this feature has been added in pylint 0.11. This may be done by
+ adding "#pylint: disable-msg=W0123,E4567" at the desired block level
+ or at the end of the desired line of code
+
+
+
+Question:
+ Where are stored persistent data necessary to make comparison between
+ to successive run ?
+
+Answer:
+ Analysis data are stored as pickle file in a directory which is
+ localized using the following rules:
+
+ * value of the PYLINTHOME environment variable if set
+
+ * ".pylint.d" subdirectory of the user's home directory if it is found
+ (not always findable on Windows platforms)
+
+ * ".pylint.d" directory in the current directory
+
+
+.. _psyco: http://psyco.sf.net
diff --git a/doc/features.txt b/doc/features.txt
new file mode 100644
index 000000000..2cc41e0f8
--- /dev/null
+++ b/doc/features.txt
@@ -0,0 +1,678 @@
+PyLint features
+===============
+
+.. contents::
+
+Master
+------
+
+Description
+~~~~~~~~~~~
+lint Python modules using external checkers.
+
+This is the main checker controling the other ones and the reports
+generation. It is itself both a raw checker and an astng checker in order
+to:
+* handle message activation / deactivation at the module level
+* handle some basic but necessary stats'data (number of classes, methods...)
+
+This checker also defines the following reports:
+* R0001: Total errors / warnings
+* R0002: % errors / warnings by module
+* R0003: Messages
+* R0004: Global evaluation
+
+Messages
+~~~~~~~~
+I0001:
+ Used to inform that a built-in module has not been checked using the raw
+ checkers. This message belongs to the master checker.
+
+I0010:
+ Used when an inline option is either badly formatted or can't be used inside
+ modules. This message belongs to the master checker.
+
+I0011:
+ Used when an inline option disable a message or a messages category. This
+ message belongs to the master checker.
+
+I0012:
+ Used when an inline option enable a message or a messages category. This
+ message belongs to the master checker.
+
+E0001:
+ Used when a syntax error is raised for a module. This message belongs to the
+ master checker.
+
+E0011:
+ Used when an unknown inline option is encountered. This message belongs to the
+ master checker.
+
+E0012:
+ Used when an bad value for an inline option is encountered. This message
+ belongs to the master checker.
+
+F0001:
+ Used when an error occured preventing the analyzing of a module (unable to
+ find it for instance). This message belongs to the master checker.
+
+F0002:
+ Used when an unexpected error occured while building the ASTNG representation.
+ This is usually accomopagned by a traceback. Please report such errors ! This
+ message belongs to the master checker.
+
+F0003:
+ Used to indicate that the user asked to analyze a builtin module which has
+ been skipped. This message belongs to the master checker.
+
+
+
+Basic
+-----
+
+Description
+~~~~~~~~~~~
+checks for :
+* doc strings
+* modules / classes / functions / methods / arguments / variables name
+* number of arguments, local variables, branchs, returns and statements in
+functions, methods
+* required module attributes
+* dangerous default values as arguments
+* redefinition of function / method / class
+* uses of the global statement
+
+This checker also defines the following reports:
+* R0101: Statistics by type
+
+Messages
+~~~~~~~~
+C0102:
+ Used when the name is listed in the black list (unauthorized names). This
+ message belongs to the basic checker.
+
+C0103:
+ Used when the name doesn't match the regular expression associated to its type
+ (constant, variable, class...). This message belongs to the basic checker.
+
+C0111:
+ Used when a module, function, class or method has no docstring. Some special
+ methods like __init__ doesn't necessary require a docstring. This message
+ belongs to the basic checker.
+
+C0112:
+ Used when a module, function, class or method has an empty docstring (it would
+ be to easy ;). This message belongs to the basic checker.
+
+C0121:
+ Used when an attribute required for modules is missing. This message belongs
+ to the basic checker.
+
+W0101:
+ Used when there is some code behind a "return" or "raise" statement, which
+ will never be accessed. This message belongs to the basic checker.
+
+W0102:
+ Used when a mutable value as list or dictionary is detected in a default value
+ for an argument. This message belongs to the basic checker.
+
+W0104:
+ Used when a statement doesn't have (or at least seems to) any effect. This
+ message belongs to the basic checker.
+
+W0122:
+ Used when you use the "exec" statement, to discourage its usage. That doesn't
+ mean you can not use it ! This message belongs to the basic checker.
+
+W0141:
+ Used when a black listed builtin function is used (see the bad-function
+ option). Usual black listed functions are the ones like map, or filter , where
+ Python offers now some cleaner alternative like list comprehension. This
+ message belongs to the basic checker.
+
+W0142:
+ Used when a function or method is called using `*args` or `**kwargs` to
+ dispatch arguments. This doesn't improve readility and should be used with
+ care. This message belongs to the basic checker.
+
+E0101:
+ Used when the special class method __ini__ has an explicit return value. This
+ message belongs to the basic checker.
+
+E0102:
+ Used when a function / class / method is redefined. This message belongs to
+ the basic checker.
+
+E0103:
+ Used when break or continue keywords are used outside a loop. This message
+ belongs to the basic checker.
+
+
+
+Variables
+---------
+
+Description
+~~~~~~~~~~~
+checks for
+* unused variables / imports
+* undefined variables
+* redefinition of variable from builtins or from an outer scope
+* use of variable before assigment
+
+
+Messages
+~~~~~~~~
+W0601:
+ Used when a variable is defined through the "global" statement but the
+ variable is not defined in the module scope. This message belongs to the
+ variables checker.
+
+W0602:
+ Used when a variable is defined through the "global" statement but no
+ assigment to this variable is done. This message belongs to the variables
+ checker.
+
+W0603:
+ Used when you use the "global" statement to update a global variable. PyLint
+ just try to discourage this usage. That doesn't mean you can not use it ! This
+ message belongs to the variables checker.
+
+W0604:
+ Used when you use the "global" statement at the module level since it has no
+ effect This message belongs to the variables checker.
+
+W0611:
+ Used when an imported module or variable is not used. This message belongs to
+ the variables checker.
+
+W0612:
+ Used when a variable is defined but not used. This message belongs to the
+ variables checker.
+
+W0613:
+ Used when a function or method argument is not used. This message belongs to
+ the variables checker.
+
+W0621:
+ Used when a variable's name hide a name defined in the outer scope. This
+ message belongs to the variables checker.
+
+W0622:
+ Used when a variable or function override a built-in. This message belongs to
+ the variables checker.
+
+W0631:
+ Used when an loop variable (i.e. defined by a for loop or a list comprehension
+ or a generator expression) is used outside the loop. This message belongs to
+ the variables checker.
+
+E0601:
+ Used when a local variable is accessed before it's assignment. This message
+ belongs to the variables checker.
+
+E0602:
+ Used when an undefined variable is accessed. This message belongs to the
+ variables checker.
+
+E0611:
+ Used when a name cannot be found in a module. This message belongs to the
+ variables checker.
+
+
+
+Typecheck
+---------
+
+Description
+~~~~~~~~~~~
+try to find bugs in the code using type inference
+
+
+Messages
+~~~~~~~~
+W1111:
+ Used when an assigment is done on a function call but the infered function
+ returns nothing but None. This message belongs to the typecheck checker.
+
+E1101:
+ Used when a class is accessed for an unexistant member. This message belongs
+ to the typecheck checker.
+
+E1102:
+ Used when an object being called has been infered to a non callable object
+ This message belongs to the typecheck checker.
+
+E1111:
+ Used when an assigment is done on a function call but the infered function
+ doesn't return anything. This message belongs to the typecheck checker.
+
+
+
+Design
+------
+
+Description
+~~~~~~~~~~~
+checks for sign of poor/misdesign:
+* number of methods, attributes, local variables...
+* size, complexity of functions, methods
+
+
+Messages
+~~~~~~~~
+R0901:
+ Used when class has too many parent classes. This message belongs to the
+ design checker.
+
+R0902:
+ Used when class has too many instance attributes. This message belongs to the
+ design checker.
+
+R0903:
+ Used when class has not enough public methods. This message belongs to the
+ design checker.
+
+R0904:
+ Used when class has too many public methods. This message belongs to the
+ design checker.
+
+R0911:
+ Used when a function or method has too many return statement. This message
+ belongs to the design checker.
+
+R0912:
+ Used when a function or method has too many branches. This message belongs to
+ the design checker.
+
+R0913:
+ Used when a function or method takes too many arguments. This message belongs
+ to the design checker.
+
+R0914:
+ Used when a function or method has too many local variables. This message
+ belongs to the design checker.
+
+R0915:
+ Used when a function or method has too many statements. You should then split
+ it in smaller functions / methods. This message belongs to the design checker.
+
+R0921:
+ Used when an abstract class is not used as ancestor anywhere. This message
+ belongs to the design checker.
+
+R0922:
+ Used when an abstract class is used less than X times as ancestor. This
+ message belongs to the design checker.
+
+R0923:
+ Used when an interface class is not implemented anywhere. This message belongs
+ to the design checker.
+
+
+
+Classes
+-------
+
+Description
+~~~~~~~~~~~
+checks for :
+* methods without self as first argument
+* overriden methods signature
+* access only to existant members via self
+* attributes not defined in the __init__ method
+* supported interfaces implementation
+* unreachable code
+
+
+Messages
+~~~~~~~~
+C0202:
+ Used when a class method has an attribute different than "cls" as first
+ argument, to easily differentiate them from regular instance methods. This
+ message belongs to the classes checker.
+
+C0203:
+ Used when a metaclass method has an attribute different the "mcs" as first
+ argument. This message belongs to the classes checker.
+
+R0201:
+ Used when a method doesn't use its bound instance, and so could be written as
+ a function. This message belongs to the classes checker.
+
+W0201:
+ Used when an instance attribute is defined outside the __init__ method. This
+ message belongs to the classes checker.
+
+W0211:
+ Used when a static method has "self" or "cls" as first argument. This message
+ belongs to the classes checker.
+
+W0221:
+ Used when a method has a different number of arguments than in the implemented
+ interface or in an overriden method. This message belongs to the classes
+ checker.
+
+W0222:
+ Used when a method signature is different than in the implemented interface or
+ in an overriden method. This message belongs to the classes checker.
+
+W0223:
+ Used when an abstract method (ie raise NotImplementedError) is not overriden
+ in concrete class. This message belongs to the classes checker.
+
+W0231:
+ Used when an ancestor class method has an __init__ method which is not called
+ by a derived class. This message belongs to the classes checker.
+
+W0232:
+ Used when a class has no __init__ method, neither its parent classes. This
+ message belongs to the classes checker.
+
+W0233:
+ Used when an __init__ method is called on a class which is not in the direct
+ ancestors for the analysed class. This message belongs to the classes checker.
+
+E0202:
+ Used when a class defines a method which is hiden by an instance attribute
+ from an ancestor class. This message belongs to the classes checker.
+
+E0203:
+ Used when an instance member is accessed before it's actually assigned. This
+ message belongs to the classes checker.
+
+E0211:
+ Used when a method which should have the bound instance as first argument has
+ no argument defined. This message belongs to the classes checker.
+
+E0213:
+ Used when a method has an attribute different the "self" as first argument.
+ This message belongs to the classes checker.
+
+E0221:
+ Used when a class claims to implement an interface which is not a class. This
+ message belongs to the classes checker.
+
+E0222:
+ Used when a method declared in an interface is missing from a class
+ implementing this interface This message belongs to the classes checker.
+
+F0202:
+ Used when PyLint has been unable to check methods signature compatibility for
+ an unexpected raison. Please report this kind if you don't make sense of it.
+ This message belongs to the classes checker.
+
+F0220:
+ Used when a PyLint as failed to find interfaces implemented by a class This
+ message belongs to the classes checker.
+
+
+
+Imports
+-------
+
+Description
+~~~~~~~~~~~
+checks for
+* external modules dependencies
+* relative / wildcard imports
+* cyclic imports
+* uses of deprecated modules
+
+This checker also defines the following reports:
+* R0401: External dependencies
+* R0402: Modules dependencies graph
+
+Messages
+~~~~~~~~
+R0401:
+ Used when a cyclic import between two or more modules is detected. This
+ message belongs to the imports checker.
+
+W0401:
+ Used when `from module import *` is detected. This message belongs to the
+ imports checker.
+
+W0402:
+ Used a module marked as deprecated is imported. This message belongs to the
+ imports checker.
+
+W0403:
+ Used when an import relative to the package directory is detected. This
+ message belongs to the imports checker.
+
+W0404:
+ Used when a module is reimported multiple times. This message belongs to the
+ imports checker.
+
+W0406:
+ Used when a module is importing itself. This message belongs to the imports
+ checker.
+
+W0410:
+ Python 2.5 and greater require __future__ import to be the first non docstring
+ statement in the module. This message belongs to the imports checker.
+
+F0401:
+ Used when pylint has been unable to import a module. This message belongs to
+ the imports checker.
+
+
+
+Newstyle
+--------
+
+Description
+~~~~~~~~~~~
+checks for usage of new style capabilities on old style classes and
+other new/old styles conflicts problems
+* use of property, __slots__, super
+* "super" usage
+* raising a new style class as exception
+
+
+Messages
+~~~~~~~~
+W1001:
+ Used when PyLint detect the use of the builtin "property" on an old style
+ class while this is relying on new style classes features This message belongs
+ to the newstyle checker.
+
+W1010:
+ Used when a custom exception class is raised but doesn't inherit from the
+ builtin "Exception" class. This message belongs to the newstyle checker.
+
+E1001:
+ Used when an old style class use the __slots__ attribute. This message belongs
+ to the newstyle checker.
+
+E1002:
+ Used when an old style class use the super builtin. This message belongs to
+ the newstyle checker.
+
+E1003:
+ Used when another argument than the current class is given as first argument
+ of the super builtin. This message belongs to the newstyle checker.
+
+E1010:
+ Used when a new style class is raised since it's not yet possible. This
+ message belongs to the newstyle checker.
+
+
+
+Exceptions
+----------
+
+Description
+~~~~~~~~~~~
+checks for
+* excepts without exception filter
+* string exceptions
+
+
+Messages
+~~~~~~~~
+W0701:
+ Used when a string exception is raised. This message belongs to the exceptions
+ checker.
+
+W0702:
+ Used when an except clause doesn't specify exceptions type to catch. This
+ message belongs to the exceptions checker.
+
+W0703:
+ Used when an except catch Exception instances. This message belongs to the
+ exceptions checker.
+
+W0704:
+ Used when an except clause does nothing but "pass" and there is no "else"
+ clause. This message belongs to the exceptions checker.
+
+W0706:
+ Used when a variable used to raise an exception is initially assigned to a
+ value which can't be used as an exception. This message belongs to the
+ exceptions checker.
+
+E0701:
+ Used when except clauses are not in the correct order (from the more specific
+ to the more generic). If you don't fix the order, some exceptions may not be
+ catched by the most specific handler. This message belongs to the exceptions
+ checker.
+
+E0702:
+ Used when something which is neither a class, an instance or a string is
+ raised (i.e. a `TypeError` will be raised). This message belongs to the
+ exceptions checker.
+
+
+
+Similarities
+------------
+
+Description
+~~~~~~~~~~~
+checks for similarities and duplicated code. This computation may be
+memory / CPU intensive, so you should disable it if you experiments some
+problems.
+
+This checker also defines the following reports:
+* R0801: Duplication
+
+Messages
+~~~~~~~~
+R0801:
+ Indicates that a set of similar lines has been detected among multiple file.
+ This usually means that the code should be refactored to avoid this
+ duplication. This message belongs to the similarities checker.
+
+
+
+Format
+------
+
+Description
+~~~~~~~~~~~
+checks for :
+* unauthorized constructions
+* strict indentation
+* line length
+* use of <> instead of !=
+
+
+Messages
+~~~~~~~~
+C0301:
+ Used when a line is longer than a given number of characters. This message
+ belongs to the format checker.
+
+C0321:
+ Used when more than on statement are found on the same line. This message
+ belongs to the format checker.
+
+C0322:
+ Used when one of the following operator (!= | <= | == | >= | < | > | = | \+= |
+ -= | \*= | /= | %) is not preceded by a space. This message belongs to the
+ format checker.
+
+C0323:
+ Used when one of the following operator (!= | <= | == | >= | < | > | = | \+= |
+ -= | \*= | /= | %) is not followed by a space. This message belongs to the
+ format checker.
+
+C0324:
+ Used when a comma (",") is not followed by a space. This message belongs to
+ the format checker.
+
+W0302:
+ Used when a module has too much lines, reducing its readibility. This message
+ belongs to the format checker.
+
+W0311:
+ Used when an unexpected number of indentation's tabulations or spaces has been
+ found. This message belongs to the format checker.
+
+W0312:
+ Used when there are some mixed tabs and spaces in a module. This message
+ belongs to the format checker.
+
+W0331:
+ Used when the deprecated "<>" operator is used instead of "!=". This message
+ belongs to the format checker.
+
+W0332:
+ Used when a lower case "l" is used to mark a long integer. You should use a
+ upper case "L" since the letter "l" looks too much like the digit "1" This
+ message belongs to the format checker.
+
+F0321:
+ Used when an unexpected error occured in bad format detection. Please report
+ the error if it occurs. This message belongs to the format checker.
+
+
+
+Miscellaneous
+-------------
+
+Description
+~~~~~~~~~~~
+checks for:
+* warning notes in the code like FIXME, XXX
+* PEP 263: source code with non ascii character but no encoding declaration
+
+
+Messages
+~~~~~~~~
+W0511:
+ Used when a warning note as FIXME or XXX is detected. This message belongs to
+ the miscellaneous checker.
+
+E0501:
+ Used when some non ascii characters are detected but now encoding is
+ specified, as explicited in the PEP 263. This message belongs to the
+ miscellaneous checker.
+
+E0502:
+ Used when a known encoding is specified but the file doesn't seem to be
+ actually in this encoding. This message belongs to the miscellaneous checker.
+
+E0503:
+ Used when an encoding is specified, but it's unknown to Python. This message
+ belongs to the miscellaneous checker.
+
+
+
+Metrics
+-------
+
+Description
+~~~~~~~~~~~
+does not check anything but gives some raw metrics :
+* total number of lines
+* total number of code lines
+* total number of docstring lines
+* total number of comments lines
+* total number of empty lines
+
+This checker also defines the following reports:
+* R0701: Raw metrics
+
diff --git a/doc/makefile b/doc/makefile
new file mode 100644
index 000000000..944893ef8
--- /dev/null
+++ b/doc/makefile
@@ -0,0 +1,36 @@
+MKHTML=mkdoc
+MKHTML_OPT=--doctype article --param toc.section.depth=1 --target html --stylesheet single-file
+
+SRC=.
+
+
+all: quickstart.html features.html FAQ.html examples man
+
+FAQ.html: ${SRC}/FAQ.txt
+ ${MKHTML} ${MKHTML_OPT} ${SRC}/FAQ.txt
+
+quickstart.html: ${SRC}/quickstart.txt
+ ${MKHTML} ${MKHTML_OPT} ${SRC}/quickstart.txt
+
+features.html:
+ chmod u+w ${SRC}/features.txt
+ echo "PyLint features" > ${SRC}/features.txt
+ echo "===============" >> ${SRC}/features.txt
+ echo "" >> ${SRC}/features.txt
+ echo ".. contents::" >> ${SRC}/features.txt
+ echo "" >> ${SRC}/features.txt
+ pylint --list-msgs >> ${SRC}/features.txt
+ ${MKHTML} ${MKHTML_OPT} ${SRC}/features.txt
+
+examples:
+ chmod u+w ../examples/pylintrc
+ pylint --generate-rcfile > ../examples/pylintrc
+
+man:
+ chmod u+w ../man/pylint.1
+ pylint --generate-man > ../man/pylint.1
+
+clean:
+ rm -f *.html
+
+.PHONY: features.html
diff --git a/doc/quickstart.txt b/doc/quickstart.txt
new file mode 100644
index 000000000..042d377cf
--- /dev/null
+++ b/doc/quickstart.txt
@@ -0,0 +1,216 @@
+=================
+Pylint Quickstart
+=================
+
+:Author: Alexandre Fayolle
+:Organization: Logilab
+:Version: $Revision: 1.10 $
+:Date: $Date: 2005-04-15 10:40:17 $
+
+.. contents::
+
+
+This document is meant to get you started with Pylint. It assumes that
+you have installed pylint following the instructions in the README
+document found in the source documentation.
+
+
+What is pylint?
+---------------
+
+Pylint is a tool that checks for errors in python code, tries to
+enforce a coding standard and looks for smelling code . This is
+similar but nevertheless different from what pychecker_ provides,
+especially since pychecker explicitely does not bother with coding
+style. The default coding style used by pylint is close to
+`Guido's style guide`_. For more information about code smells, refer
+to Martin Fowler's `refactoring book`_
+
+Pylint will display a number of errors and warnings as it analyzes the
+code, as well as some statistics about the number of warnings and
+errors found in different files. If you run pylint twice, it will
+display the statistics from the previous run together with the ones
+from the current run, so that you can see if the code has improved or
+not.
+
+Last but not least, the code is given an overall mark, based on the
+number an severity of the warnings and errors. This has proven to
+be very motivating for programmers.
+
+
+Invoking pylint
+---------------
+
+Pylint is meant to be called from the commant line. The usage is ::
+
+ pylint [options] module_or_package
+
+You should give pylint the name of a Python package or module. Pylint
+will ``import`` this package or module, so you should pay attention to
+your ``PYTHONPATH``, since it is a common error to analyze an
+installed version of a module instead of the development version.
+
+It is also possible to analyze python files, with a few
+restriction. The thing to keep in mind is that pylint will try to
+convert the file name to a module name, and only be able to process
+the file if it succeeds. ::
+
+ pylint mymodule.py
+
+should always works since the current working
+directory is automatically added on top of the python path ::
+
+ pylint directory/mymodule.py
+
+will work if "directory" is a python package (i.e. has an __init__.py
+file) or if "directory" is in the python path.
+
+For more details on this see the FAQ_.
+
+You can also start a thin gui around pylint (require TkInter) by
+typing ::
+
+ pylint-gui
+
+This should open a window where you can enter the name of the package
+or module to check, at pylint messages will be displayed in the user
+interface.
+
+
+Pylint output
+-------------
+
+The default format for the output is raw text. But passing pylint the
+``--html`` option will produce an HTML document.
+
+There are several sections in pylint's output.
+
+Source code analysis section
+''''''''''''''''''''''''''''
+
+For each python module,
+pylint will first display a few '*' characters followed by the name
+of the module. Then, a number of messages with the following
+format: ::
+
+ MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE
+
+You can get another output format, useful since it's recognized by
+most editors or other development tools using the ``--parseable=y``
+option.
+
+The message type can be:
+
+ * [R]efactor for a "good practice" metric violation
+ * [C]onvention for coding standard violation
+ * [W]arning for stylistic problems, or minor programming issues
+ * [E]rror for important programming issues (i.e. most probably bug)
+ * [F]atal for errors which prevented further processing
+
+Sometimes the line of code which caused the error is displayed with
+a caret pointing to the error. This may be generalized in future
+versions of pylint.
+
+Example (extracted from a run of pylint on itself...):
+
+::
+
+ ************* Module pylint.checkers.format
+ W: 50: Too long line (86/80)
+ W:108: Operator not followed by a space
+ print >>sys.stderr, 'Unable to match %r', line
+ ^
+ W:141: Too long line (81/80)
+ W: 74:searchall: Unreachable code
+ W:171:FormatChecker.process_tokens: Redefining built-in (type)
+ W:150:FormatChecker.process_tokens: Too many local variables (20/15)
+ W:150:FormatChecker.process_tokens: Too many branchs (13/12)
+
+
+Reports section
+'''''''''''''''
+
+Following the analysis message, pylint will display a set of report,
+each one focusing on a particular aspect of the project, such as number
+of messages by categories, modules dependancies...
+
+For instance, the metrics report displays summaries gathered from the
+current
+run.
+
+ * the number of processed modules
+ * for each module, the percentage of errors and warnings
+ * the total number of errors and warnings
+ * percentage of classes, functions and modules with docstrings, and
+ a comparison from the previous run
+ * percentage of classes, functions and modules with correct name
+ * (according the the coding standard), and a comparison from the
+ previous run
+ * a list of external dependencies found in the code, and where they appear
+
+Also, a global evaluation for the code is computed, and an
+optional witty comment is displayed (if ``--comment=y`` was
+specified on the command line).
+
+
+Command line options
+--------------------
+
+First of all, we have two basic (but useful) options.
+
+--version show program's version number and exit
+-h, --help show help about the command line options
+
+Pylint is architectured around several checkers. By default all
+checkers are enabled. You can disable a specific checker by specifying
+``--enable-<checker>=n``, or disable all checkers using
+``--disable-all`` and afterwards enable specific checkers with
+``--enable-<checker>=y``. See the list of available features_ for a
+description of provided checkers with their functionalities.
+
+Each checker has some specific options, which can take either a yes/no
+value, an integer, a python regular expression, or a comma separated
+list of values (which are generally used to override a regular
+expression in special cases). For a full list of options, use ``--help``
+
+Specifying all the options suitable for your setup and coding
+standards can be tedious, so it is possible to use a rc file to
+specify the default values. Pylint looks for /etc/pylintrc and
+~/.pylintrc. The ``--generate-rcfile`` option will generate a
+commented configuration file according to the current configuration on
+standard output and exit. You can put other options before this one to
+use them in the configuration, or start with the default values and
+hand tune the configuration.
+
+Other useful global options include:
+
+--zope Initialize Zope products before starting
+--ignore=file Add <file> (may be a directory) to the black
+ list. It should be a base name, not a path.
+ You may set this option multiple times.
+--statistics=y_or_n Compute statistics on collected data.
+--persistent=y_or_n Pickle collected data for later comparisons.
+--comment=y_or_n Add a comment according to your evaluation note.
+--parseable=y_or_n Use a parseable output format.
+--html=y_or_n Use HTML as output format instead of text.
+--enable-msg=msgids Enable the given messages.
+--disable-msg=msgids Disable the given messages.
+--enable-msg-cat=cats Enable all messages in the given categories.
+--disable-msg-cat=cats Disable all messages in the given categories.
+
+
+
+Bug reports
+-----------
+
+You think you have found a bug in Pylint? Well, this may be the case
+since Pylint is under development. Please take the time to send a bug
+report to python-projects@logilab.org. This mailing list is also a
+nice place to discuss Pylint issues.
+
+
+.. _pychecker: http://pychecker.sf.net
+.. _features: features.html
+.. _FAQ: FAQ.html
+.. _`Guido's style guide`: http://www.python.org/doc/essays/styleguide.html
+.. _`refactoring book`: http://www.refactoring.com/
diff --git a/elisp/pylint.el b/elisp/pylint.el
new file mode 100644
index 000000000..84f5da698
--- /dev/null
+++ b/elisp/pylint.el
@@ -0,0 +1,37 @@
+
+;; adapted from pychecker for pylint
+(defun my-python-hook ()
+ (defun pylint ()
+ "Run pylint against the file behind the current buffer after
+ checking if unsaved buffers should be saved."
+
+ (interactive)
+ (let* ((file (buffer-file-name (current-buffer)))
+ (command (concat "pylint --parseable=y \"" file "\"")))
+ (save-some-buffers (not compilation-ask-about-save) nil) ; save files.
+ (compile-internal command "No more errors or warnings" "pylint")))
+ (local-set-key [f1] 'pylint)
+ (local-set-key [f2] 'previous-error)
+ (local-set-key [f3] 'next-error)
+
+ (define-key
+ py-mode-map
+ [menu-bar Python pylint-separator]
+ '("--" . pylint-seperator))
+
+ (define-key
+ py-mode-map
+ [menu-bar Python next-error]
+ '("Next error" . next-error))
+ (define-key
+ py-mode-map
+ [menu-bar Python prev-error]
+ '("Previous error" . previous-error))
+ (define-key
+ py-mode-map
+ [menu-bar Python lint]
+ '("Pylint" . pylint))
+
+ )
+
+(add-hook 'python-mode-hook 'my-python-hook)
diff --git a/elisp/startup b/elisp/startup
new file mode 100644
index 000000000..ad9d07188
--- /dev/null
+++ b/elisp/startup
@@ -0,0 +1,17 @@
+;; -*-emacs-lisp-*-
+;;
+;; Emacs startup file for the Debian GNU/Linux %PACKAGE% package
+;;
+;; Originally contributed by Nils Naumann <naumann@unileoben.ac.at>
+;; Modified by Dirk Eddelbuettel <edd@debian.org>
+;; Adapted for dh-make by Jim Van Zandt <jrv@vanzandt.mv.com>
+
+;; The %PACKAGE% package follows the Debian/GNU Linux 'emacsen' policy and
+;; byte-compiles its elisp files for each 'emacs flavor' (emacs19,
+;; xemacs19, emacs20, xemacs20...). The compiled code is then
+;; installed in a subdirectory of the respective site-lisp directory.
+;; We have to add this to the load-path:
+(setq load-path (cons (concat "/usr/share/"
+ (symbol-name flavor)
+ "/site-lisp/%PACKAGE%") load-path))
+(load-library "pylint")
diff --git a/examples/custom.py b/examples/custom.py
new file mode 100644
index 000000000..73b64e149
--- /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 000000000..701f6e902
--- /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 000000000..fc079849c
--- /dev/null
+++ b/examples/pylintrc
@@ -0,0 +1,352 @@
+# lint Python modules using external checkers.
+#
+# This is the main checker controling the other ones and the reports
+# generation. It is itself both a raw checker and an astng checker in order
+# to:
+# * handle message activation / deactivation at the module level
+# * handle some basic but necessary stats'data (number of classes, methods...)
+#
+# This checker also defines the following reports:
+# * R0001: Total errors / warnings
+# * R0002: % errors / warnings by module
+# * R0003: Messages
+# * R0004: Global evaluation
+[MASTER]
+
+# Profiled execution.
+profile=no
+
+# Add <file or directory> to the black list. It should be a base name, not a
+# path. You may set this option multiple times.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# Set the cache size for astng objects.
+cache-size=500
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[REPORTS]
+
+# Tells wether to display a full report or only the messages
+reports=yes
+
+# Use HTML as output format instead of text
+html=no
+
+# Use a parseable text output format, so your favorite text editor will be able
+# to jump to the line corresponding to a message.
+parseable=no
+
+# Colorizes text output using ansi escape codes
+color=no
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note).You have access to the variables errors warning, statement which
+# respectivly contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (R0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (R0004).
+comment=no
+
+# Include message's id in output
+include-ids=no
+
+
+# checks for :
+# * doc strings
+# * modules / classes / functions / methods / arguments / variables name
+# * number of arguments, local variables, branchs, returns and statements in
+# functions, methods
+# * required module attributes
+# * dangerous default values as arguments
+# * redefinition of function / method / class
+# * uses of the global statement
+#
+# This checker also defines the following reports:
+# * R0101: Statistics by type
+[BASIC]
+
+# Enable / disable this checker
+enable-basic=yes
+
+# Required attributes for module, separated by a comma
+required-attributes=__revision__
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+no-docstring-rgx=__.*__
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,apply,input
+
+
+# try to find bugs in the code using type inference
+#
+[TYPECHECK]
+
+# Enable / disable this checker
+enable-typecheck=yes
+
+# Tells wether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# When zope mode is activated, consider the acquired-members option to ignore
+# access to some undefined attributes.
+zope=no
+
+# List of members which are usually get through zope's acquisition mecanism and
+# so shouldn't trigger E0201 when accessed (need zope=yes to be considered.
+acquired-members=REQUEST,acl_users,aq_parent
+
+
+# checks for
+# * unused variables / imports
+# * undefined variables
+# * redefinition of variable from builtins or from an outer scope
+# * use of variable before assigment
+#
+[VARIABLES]
+
+# Enable / disable this checker
+enable-variables=yes
+
+# Tells wether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching names used for dummy variables (i.e. not used).
+dummy-variables-rgx=_|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+# checks for sign of poor/misdesign:
+# * number of methods, attributes, local variables...
+# * size, complexity of functions, methods
+#
+[DESIGN]
+
+# Enable / disable this checker
+enable-design=yes
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branchs=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+# checks for :
+# * methods without self as first argument
+# * overriden methods signature
+# * access only to existant members via self
+# * attributes not defined in the __init__ method
+# * supported interfaces implementation
+# * unreachable code
+#
+[CLASSES]
+
+# Enable / disable this checker
+enable-classes=yes
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+
+# checks for
+# * external modules dependencies
+# * relative / wildcard imports
+# * cyclic imports
+# * uses of deprecated modules
+#
+# This checker also defines the following reports:
+# * R0401: External dependencies
+# * R0402: Modules dependencies graph
+[IMPORTS]
+
+# Enable / disable this checker
+enable-imports=yes
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report R0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report R0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report R0402 must
+# not be disabled)
+int-import-graph=
+
+
+# checks for usage of new style capabilities on old style classes and
+# other new/old styles conflicts problems
+# * use of property, __slots__, super
+# * "super" usage
+# * raising a new style class as exception
+#
+[NEWSTYLE]
+
+# Enable / disable this checker
+enable-newstyle=yes
+
+
+# checks for
+# * excepts without exception filter
+# * string exceptions
+#
+[EXCEPTIONS]
+
+# Enable / disable this checker
+enable-exceptions=yes
+
+
+# checks for similarities and duplicated code. This computation may be
+# memory / CPU intensive, so you should disable it if you experiments some
+# problems.
+#
+# This checker also defines the following reports:
+# * R0801: Duplication
+[SIMILARITIES]
+
+# Enable / disable this checker
+enable-similarities=yes
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+
+# checks for :
+# * unauthorized constructions
+# * strict indentation
+# * line length
+# * use of <> instead of !=
+#
+[FORMAT]
+
+# Enable / disable this checker
+enable-format=yes
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+
+# checks for:
+# * warning notes in the code like FIXME, XXX
+# * PEP 263: source code with non ascii character but no encoding declaration
+#
+[MISCELLANEOUS]
+
+# Enable / disable this checker
+enable-miscellaneous=yes
+
+# List of note tags to take in consideration, separated by a comma. Default to
+# FIXME, XXX, TODO
+notes=FIXME,XXX,TODO
+
+
+# does not check anything but gives some raw metrics :
+# * total number of lines
+# * total number of code lines
+# * total number of docstring lines
+# * total number of comments lines
+# * total number of empty lines
+#
+# This checker also defines the following reports:
+# * R0701: Raw metrics
+[METRICS]
+
+# Enable / disable this checker
+enable-metrics=yes
diff --git a/examples/pylintrc_camelcase b/examples/pylintrc_camelcase
new file mode 100644
index 000000000..0dd9266f6
--- /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]*$
+
+
diff --git a/gui.py b/gui.py
new file mode 100644
index 000000000..ca17d4b13
--- /dev/null
+++ b/gui.py
@@ -0,0 +1,84 @@
+"""Tkinker gui for pylint"""
+
+__revision__ = '$Id: gui.py,v 1.8 2004-08-27 10:18:54 syt Exp $'
+
+from Tkinter import Tk, Frame, Listbox, Entry, Label, Button, Scrollbar
+from Tkinter import TOP, LEFT, RIGHT, BOTTOM, END, X, Y, BOTH
+import os
+import sys
+
+if sys.platform.startswith('win'):
+ PYLINT = 'pylint.bat'
+else:
+ PYLINT = 'pylint'
+
+class LintGui:
+ """Build and control a window to interact with pylint"""
+
+ def __init__(self, root=None):
+ self.root = root or Tk()
+ self.root.title('Pylint')
+ top_frame = Frame(self.root)
+ res_frame = Frame(self.root)
+ btn_frame = Frame(self.root)
+ top_frame.pack(side=TOP, fill=X)
+ res_frame.pack(side=TOP, fill=BOTH, expand=True)
+ btn_frame.pack(side=TOP, fill=X)
+
+ Label(top_frame, text='Module or package').pack(side=LEFT)
+ self.txtModule = Entry(top_frame, background='white')
+ self.txtModule.bind('<Return>', self.run_lint)
+ self.txtModule.pack(side=LEFT, expand=True, fill=X)
+ Button(top_frame, text='Run', command=self.run_lint).pack(side=LEFT)
+
+ scrl = Scrollbar(res_frame)
+ self.results = Listbox(res_frame,
+ background='white',
+ font='fixed',
+ selectmode='browse',
+ yscrollcommand=scrl.set)
+ scrl.configure(command=self.results.yview)
+ self.results.pack(side=LEFT, expand=True, fill=BOTH)
+ scrl.pack(side=RIGHT, fill=Y)
+
+ Button(btn_frame, text='Quit', command=self.quit).pack(side=BOTTOM)
+ #self.root.bind('<ctrl-q>', self.quit)
+ self.txtModule.focus_set()
+
+ def mainloop(self):
+ """lauch the mainloop of the application"""
+ self.root.mainloop()
+
+ def quit(self, _=None):
+ """quit the application"""
+ self.root.quit()
+
+ def run_lint(self, _=None):
+ """lauches pylint"""
+ colors = {'W:':'red1', 'E:': 'red4',
+ 'W:': 'red3', '**': 'navy'}
+
+ self.root.configure(cursor='watch')
+ self.results.focus_set()
+ self.results.delete(0, END)
+ self.results.update()
+ module = self.txtModule.get()
+ pout = os.popen('%s %s' % (PYLINT, module), 'r')
+ for line in pout.xreadlines():
+ line = line.rstrip()
+ self.results.insert(END, line)
+ fg_color = colors.get(line[:2], 'black')
+ self.results.itemconfigure(END, fg=fg_color)
+ self.results.update()
+ self.root.configure(cursor='')
+
+def Run(args):
+ """launch pylint gui from args"""
+ if args:
+ print 'USAGE: pylint-gui\n launch a simple pylint gui using Tk'
+ return
+ gui = LintGui()
+ gui.mainloop()
+
+if __name__ == '__main__':
+ Run(sys.argv[1:])
diff --git a/interfaces.py b/interfaces.py
new file mode 100644
index 000000000..9771aafe2
--- /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')
diff --git a/lint.py b/lint.py
new file mode 100644
index 000000000..a77fc0288
--- /dev/null
+++ b/lint.py
@@ -0,0 +1,879 @@
+# Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com).
+# Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" %prog [options] module_or_package
+
+ Check that a module satisfy a coding standard (and more !).
+
+ %prog --help
+
+ Display this help message and exit.
+
+ %prog --help-msg <msg-id>[,<msg-id>]
+
+ Display help messages about given message identifiers and exit.
+"""
+
+__revision__ = "$Id: lint.py,v 1.115 2006-04-19 09:17:40 syt Exp $"
+
+# import this first to avoid further builtins pollution possibilities
+from pylint.checkers import utils
+
+import sys
+import os
+import re
+import tokenize
+from os.path import dirname, basename, splitext, exists, isdir, join, normpath
+
+from logilab.common.configuration import OptionsManagerMixIn, \
+ check_yn, check_csv
+from logilab.common.modutils import modpath_from_file, get_module_files, \
+ file_from_modpath, load_module_from_name
+from logilab.common.interface import implements
+from logilab.common.textutils import get_csv
+from logilab.common.fileutils import norm_open
+from logilab.common.ureports import Table, Text
+from logilab.common.compat import enumerate
+from logilab.common.__pkginfo__ import version as common_version
+
+from logilab.astng import ASTNGManager
+from logilab.astng.__pkginfo__ import version as astng_version
+
+from pylint.utils import UnknownMessage, MessagesHandlerMixIn, \
+ ReportsHandlerMixIn, MSG_TYPES, sort_checkers
+from pylint.interfaces import ILinter, IRawChecker, IASTNGChecker
+from pylint.checkers import BaseRawChecker, EmptyReport, \
+ table_lines_from_stats
+from pylint.reporters.text import TextReporter, TextReporter2, \
+ ColorizedTextReporter
+from pylint.reporters.html import HTMLReporter
+from pylint import config
+
+from pylint.__pkginfo__ import version
+
+
+OPTION_RGX = re.compile('\s*#*\s*pylint:(.*)')
+REPORTER_OPT_MAP = {'html': HTMLReporter,
+ 'parseable': TextReporter2,
+ 'color': ColorizedTextReporter}
+
+# Python Linter class #########################################################
+
+MSGS = {
+ 'F0001': ('%s',
+ 'Used when an error occured preventing the analyzing of a \
+ module (unable to find it for instance).'),
+ 'F0002': ('%s: %s',
+ 'Used when an unexpected error occured while building the ASTNG \
+ representation. This is usually accomopagned by a traceback. \
+ Please report such errors !'),
+ 'F0003': ('ignored builtin module %s',
+ 'Used to indicate that the user asked to analyze a builtin module\
+ which has been skipped.'),
+
+ 'I0001': ('Unable to run raw checkers on built-in module %s',
+ 'Used to inform that a built-in module has not been checked \
+ using the raw checkers.'),
+
+ 'I0010': ('Unable to consider inline option %r',
+ 'Used when an inline option is either badly formatted or can\'t \
+be used inside modules.'),
+
+ 'I0011': ('Locally disabling %r',
+ 'Used when an inline option disable a message or a messages \
+ category.'),
+ 'I0012': ('Locally enabling %r',
+ 'Used when an inline option enable a message or a messages \
+ category.'),
+
+ 'E0001': ('%s',
+ 'Used when a syntax error is raised for a module.'),
+
+ 'E0011': ('Unrecognized file option %r',
+ 'Used when an unknown inline option is encountered.'),
+ 'E0012': ('Bad option value %r',
+ 'Used when an bad value for an inline option is encountered.'),
+ }
+
+class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
+ BaseRawChecker):
+ """lint Python modules using external checkers.
+
+ This is the main checker controling the other ones and the reports
+ generation. It is itself both a raw checker and an astng checker in order
+ to:
+ * handle message activation / deactivation at the module level
+ * handle some basic but necessary stats'data (number of classes, methods...)
+ """
+
+ __implements__ = (ILinter, IRawChecker, IASTNGChecker)
+
+ name = 'master'
+ priority = 0
+ msgs = MSGS
+ may_be_disabled = False
+
+ options = (('ignore',
+ {'type' : 'csv', 'metavar' : '<file>',
+ 'dest' : 'black_list', 'default' : ('CVS',),
+ 'help' : 'Add <file or directory> to the black list. It \
+should be a base name, not a path. You may set this option multiple times.'}),
+
+ ('persistent',
+ {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'help' : 'Pickle collected data for later comparisons.'}),
+
+ ('cache-size',
+ {'default': 500, 'type' : 'int', 'metavar': '<size>',
+ 'help' : 'Set the cache size for astng objects.'}),
+
+ ('load-plugins',
+ {'type' : 'csv', 'metavar' : '<modules>', 'default' : (),
+ 'help' : 'List of plugins (as comma separated values of \
+python modules names) to load, usually to register additional checkers.'}),
+
+ ('reports',
+ {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'short': 'r',
+ 'group': 'Reports',
+ 'help' : 'Tells wether to display a full report or only the\
+ messages'}),
+ ('html',
+ {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'group': 'Reports',
+ 'help' : 'Use HTML as output format instead of text'}),
+
+ ('parseable',
+ {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'short': 'p',
+ 'group': 'Reports',
+ 'help' : 'Use a parseable text output format, so your favorite\
+ text editor will be able to jump to the line corresponding to a message.'}),
+
+ ('color',
+ {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'group': 'Reports',
+ 'help' : 'Colorizes text output using ansi escape codes'}),
+
+ ('files-output',
+ {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'group': 'Reports',
+ 'help' : 'Put messages in a separate file for each module / \
+package specified on the command line instead of printing them on stdout. \
+Reports (if any) will be written in a file name "pylint_global.[txt|html]".'}),
+
+ ('evaluation',
+ {'type' : 'string', 'metavar' : '<python_expression>',
+ 'group': 'Reports',
+ 'default': '10.0 - ((float(5 * error + warning + refactor + \
+convention) / statement) * 10)',
+ 'help' : 'Python expression which should return a note less \
+than 10 (10 is the highest note).You have access to the variables errors \
+warning, statement which respectivly contain the number of errors / warnings\
+ messages and the total number of statements analyzed. This is used by the \
+ global evaluation report (R0004).'}),
+
+ ('comment',
+ {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'group': 'Reports',
+ 'help' : 'Add a comment according to your evaluation note. \
+This is used by the global evaluation report (R0004).'}),
+
+ ('include-ids',
+ {'type' : 'yn', 'metavar' : '<y_or_n>', 'default' : 0,
+ 'short': 'i',
+ 'group': 'Reports',
+ 'help' : 'Include message\'s id in output'}),
+
+ ('enable-msg-cat',
+ {'type' : 'csv', 'metavar': '<msg cats>',
+ 'group': 'Reports',
+ 'help' : 'Enable all messages in the listed categories.'}),
+
+ ('disable-msg-cat',
+ {'type' : 'csv', 'metavar': '<msg cats>',
+ 'group': 'Reports',
+ 'help' : 'Disable all messages in the listed categories.'}),
+
+ ('enable-msg',
+ {'type' : 'csv', 'metavar': '<msg ids>',
+ 'group': 'Reports',
+ 'help' : 'Enable the message with the given id.'}),
+
+ ('disable-msg',
+ {'type' : 'csv', 'metavar': '<msg ids>',
+ 'group': 'Reports',
+ 'help' : 'Disable the message with the given id.'}),
+
+ ('enable-report',
+ {'type' : 'csv', 'metavar': '<rpt ids>',
+ 'group': 'Reports',
+ 'help' : 'Enable the report with the given id.'}),
+
+ ('disable-report',
+ {'type' : 'csv', 'metavar': '<rpt ids>',
+ 'group': 'Reports',
+ 'help' : 'Disable the report with the given id.'}),
+
+ )
+
+ option_groups = (
+ ('Reports', 'Options related to messages / statistics reporting'),
+ )
+
+ def __init__(self, options=(), reporter=None, option_groups=(),
+ pylintrc=None):
+ # some stuff has to be done before ancestors initialization...
+ #
+ # checkers / reporter / astng manager
+ self.reporter = None
+ self.set_reporter(reporter or TextReporter(sys.stdout))
+ self.manager = ASTNGManager()
+ self._checkers = {}
+ # visit variables
+ self.base_name = None
+ self.base_file = None
+ self.current_name = None
+ self.current_file = None
+ self.stats = None
+ # init options
+ self.options = options + PyLinter.options
+ self.option_groups = option_groups + PyLinter.option_groups
+ self._options_methods = {
+ 'enable-report': self.enable_report,
+ 'disable-report': self.disable_report,
+ 'enable-msg': self.enable_message,
+ 'disable-msg': self.disable_message,
+ 'enable-msg-cat': self.enable_message_category,
+ 'disable-msg-cat': self.disable_message_category}
+ full_version = '%%prog %s, \nastng %s, common %s\nPython %s' % (
+ version, astng_version, common_version, sys.version)
+ OptionsManagerMixIn.__init__(self, usage=__doc__,
+ version=full_version,
+ config_file=pylintrc or config.PYLINTRC)
+ MessagesHandlerMixIn.__init__(self)
+ ReportsHandlerMixIn.__init__(self)
+ BaseRawChecker.__init__(self)
+ # provided reports
+ self.reports = (('R0001', 'Total errors / warnings',
+ report_error_warning_stats),
+ ('R0002', '% errors / warnings by module',
+ report_error_warning_by_module_stats),
+ ('R0003', 'Messages',
+ report_messages_stats),
+ ('R0004', 'Global evaluation',
+ self.report_evaluation),
+ )
+ self.register_checker(self)
+ self._dynamic_plugins = []
+
+ def load_plugin_modules(self, modnames):
+ """take a list of module names which are pylint plugins and load
+ and register them
+ """
+ for modname in modnames:
+ if modname in self._dynamic_plugins:
+ continue
+ self._dynamic_plugins.append(modname)
+ module = load_module_from_name(modname)
+ module.register(self)
+
+ def set_reporter(self, reporter):
+ """set the reporter used to display messages and reports"""
+ self.reporter = reporter
+ reporter.linter = self
+
+ def set_option(self, opt_name, value, action=None, opt_dict=None):
+ """overriden from configuration.OptionsProviderMixin to handle some
+ special options
+ """
+ if opt_name in self._options_methods:
+ if value:
+ meth = self._options_methods[opt_name]
+ value = check_csv(None, opt_name, value)
+ if isinstance(value, (list, tuple)):
+ for _id in value :
+ meth(_id)
+ else :
+ meth(value)
+ elif opt_name == 'cache-size':
+ self.manager.set_cache_size(int(value))
+# elif opt_name = 'load-plugins':
+# self.load_plugin_modules(get_csv(value))
+ elif opt_name in REPORTER_OPT_MAP and check_yn(None, opt_name, value):
+ self.set_reporter(REPORTER_OPT_MAP[opt_name]())
+ BaseRawChecker.set_option(self, opt_name, value, action, opt_dict)
+
+ # checkers manipulation methods ###########################################
+
+ def register_checker(self, checker):
+ """register a new checker
+
+ checker is an object implementing IRawChecker or / and IASTNGChecker
+ """
+ assert checker.priority <= 0, 'checker priority can\'t be >= 0'
+ self._checkers[checker] = 1
+ if hasattr(checker, 'reports'):
+ for r_id, r_title, r_cb in checker.reports:
+ self.register_report(r_id, r_title, r_cb, checker)
+ if checker.__doc__ is None:
+ checker.__doc__ = 'no documentation available for this checker'
+ need_space = 80 - (len(checker.__doc__.splitlines()[-1]) % 80)
+ checker.__doc__ += """%s
+This checker also defines the following reports:
+ * %s
+""" % (' ' * need_space,
+ '\n * '.join(['% -76s' % ('%s: %s' % report[:2])
+ for report in checker.reports]))
+ self.register_options_provider(checker)
+ if hasattr(checker, 'msgs'):
+ self.register_messages(checker)
+
+
+ def disable_all_checkers(self):
+ """disable all possible checkers """
+ for checker in self._checkers.keys():
+ checker.enable(False)
+
+ def disable_noerror_checkers(self):
+ """disable all checkers without error messages, and the
+ 'miscellaneous' checker which can be safely deactivated in debug
+ mode
+ """
+ for checker in self._checkers.keys():
+ if checker.name == 'miscellaneous':
+ checker.enable(False)
+ continue
+ for msgid in getattr(checker, 'msgs', {}).keys():
+ if msgid[0] == 'E':
+ checker.enable(True)
+ break
+ else:
+ checker.enable(False)
+
+ # block level option handling #############################################
+ #
+ # see func_block_disable_msg.py test case for expected behaviour
+
+ def process_tokens(self, tokens):
+ """process tokens from the current module to search for module/block
+ level options
+ """
+ comment = tokenize.COMMENT
+ newline = tokenize.NEWLINE
+ #line_num = 0
+ for (tok_type, _, start, _, line) in tokens:
+ if tok_type not in (comment, newline):
+ continue
+ #if start[0] == line_num:
+ # continue
+ match = OPTION_RGX.search(line)
+ if match is None:
+ continue
+ try:
+ opt, value = match.group(1).split('=', 1)
+ except ValueError:
+ self.add_message('I0010', args=match.group(1).strip(),
+ line=start[0])
+ continue
+ opt = opt.strip()
+ #line_num = start[0]
+ if opt in self._options_methods and not opt.endswith('-report'):
+ meth = self._options_methods[opt]
+ for msgid in get_csv(value):
+ try:
+ meth(msgid, 'module', start[0])
+ except UnknownMessage:
+ self.add_message('E0012', args=msgid, line=start[0])
+ else:
+ self.add_message('E0011', args=opt, line=start[0])
+
+ def collect_block_lines(self, node, msg_state):
+ """walk ast to collect block level options line numbers"""
+ # recurse on children (depth first)
+ for child in node.getChildNodes():
+ self.collect_block_lines(child, msg_state)
+ for msgid, lines in msg_state.items():
+ #if msg in self._module_msgs_state:
+ # continue
+ for lineno, state in lines.items():
+ first = node.source_line()
+ last = node.last_source_line()
+ if lineno >= first and lineno <= last:
+ # set state for all lines for this block
+ first, last = node.block_range(lineno)
+ for line in xrange(first, last+1):
+ # do not override existing entries
+ if not line in self._module_msgs_state.get(msgid, ()):
+ try:
+ self._module_msgs_state[msgid][line] = state
+ except KeyError:
+ self._module_msgs_state[msgid] = {line: state}
+ del lines[lineno]
+
+
+ # code checking methods ###################################################
+
+ def check(self, files_or_modules):
+ """main checking entry: check a list of files or modules from their
+ name.
+ """
+ self.reporter.include_ids = self.config.include_ids
+ if not isinstance(files_or_modules, (list, tuple)):
+ files_or_modules = (files_or_modules,)
+ checkers = sort_checkers(self._checkers.keys())
+ rev_checkers = checkers[:]
+ rev_checkers.reverse()
+ # notify global begin
+ for checker in checkers:
+ checker.open()
+ # check modules or packages
+ for something in files_or_modules:
+ self.base_name = self.base_file = normpath(something)
+ if exists(something):
+ # this is a file or a directory
+ try:
+ modname = '.'.join(modpath_from_file(something))
+ except ImportError:
+ modname = splitext(basename(something))[0]
+ if isdir(something):
+ filepath = join(something, '__init__.py')
+ else:
+ filepath = something
+ else:
+ # suppose it's a module or package
+ modname = something
+ try:
+ filepath = file_from_modpath(modname.split('.'))
+ if filepath is None:
+ self.set_current_module(modname)
+ self.add_message('F0003', args=modname)
+ continue
+ except ImportError, ex:
+ #if __debug__:
+ # import traceback
+ # traceback.print_exc()
+ self.set_current_module(modname)
+ msg = str(ex).replace(os.getcwd() + os.sep, '')
+ self.add_message('F0001', args=msg)
+ continue
+ if self.config.files_output:
+ reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension)
+ self.reporter.set_output(open(reportfile, 'w'))
+ self.check_file(filepath, modname, checkers)
+ # notify global end
+ for checker in rev_checkers:
+ checker.close()
+
+ def check_file(self, filepath, modname, checkers):
+ """check a module or package from its name
+ if modname is a package, recurse on its subpackages / submodules
+ """
+## print 'CHECKING', filepath, modname
+ # get the given module representation
+ self.base_name = modname
+ self.base_file = normpath(filepath)
+ # check this module
+ astng = self._check_file(filepath, modname, checkers)
+ if astng is None:
+ return
+ # recurse in package except if __init__ was explicitly given
+ if not modname.endswith('.__init__') and astng.package:
+ for filepath in get_module_files(dirname(filepath),
+ self.config.black_list):
+ if filepath == self.base_file:
+ continue
+ modname = '.'.join(modpath_from_file(filepath))
+ self._check_file(filepath, modname, checkers)
+
+ def _check_file(self, filepath, modname, checkers):
+ """check a module by building its astng representation"""
+ self.set_current_module(modname, filepath)
+ # get the module representation
+ astng = self.get_astng(filepath, modname)
+ if astng is not None:
+ # set the base file if necessary
+ self.base_file = self.base_file or astng.file
+ # fix the current file (if the source file was not available or
+ # if its actually a c extension
+ self.current_file = astng.file
+ # and check it
+ self.check_astng_module(astng, checkers)
+ return astng
+
+ def set_current_module(self, modname, filepath=None):
+ """set the name of the currently analyzed module and
+ init statistics for it
+ """
+ self.current_name = modname
+ self.current_file = filepath or modname
+ self.stats['by_module'][modname] = {}
+ self.stats['by_module'][modname]['statement'] = 0
+ for msg_cat in MSG_TYPES.values():
+ self.stats['by_module'][modname][msg_cat] = 0
+ self._module_msgs_state = {}
+ self._module_msg_cats_state = {}
+
+ def get_astng(self, filepath, modname):
+ """return a astng representation for a module"""
+ try:
+ return self.manager.astng_from_file(filepath, modname)
+ except SyntaxError, ex:
+ self.add_message('E0001', line=ex.lineno, args=ex.msg)
+ except KeyboardInterrupt:
+ raise
+ except Exception, ex:
+ #if __debug__:
+ # import traceback
+ # traceback.print_exc()
+ self.add_message('F0002', args=(ex.__class__, ex))
+
+
+ def check_astng_module(self, astng, checkers):
+ """check a module from its astng representation, real work"""
+ # call raw checkers if possible
+ if not astng.pure_python:
+ self.add_message('I0001', args=astng.name)
+ else:
+ #assert astng.file.endswith('.py')
+ stream = norm_open(astng.file)
+ # invoke IRawChecker interface on self to fetch module/block
+ # level options
+ self.process_module(stream)
+ # walk ast to collect line numbers
+ orig_state = self._module_msgs_state.copy()
+ self._module_msgs_state = {}
+ self.collect_block_lines(astng, orig_state)
+
+ for checker in checkers:
+ if implements(checker, IRawChecker) and checker is not self:
+ stream.seek(0)
+ checker.process_module(stream)
+ # generate events to astng checkers
+ self.astng_events(astng, [checker for checker in checkers
+ if implements(checker, IASTNGChecker)])
+
+ def astng_events(self, astng, checkers, _reversed_checkers=None):
+ """generate event to astng checkers according to the current astng
+ node and recurse on its children
+ """
+ if _reversed_checkers is None:
+ _reversed_checkers = checkers[:]
+ _reversed_checkers.reverse()
+ if astng.is_statement():
+ self.stats['statement'] += 1
+ # generate events for this node on each checkers
+ for checker in checkers:
+ checker.visit(astng)
+ # recurse on children
+ for child in astng.getChildNodes():
+ self.astng_events(child, checkers, _reversed_checkers)
+ for checker in _reversed_checkers:
+ checker.leave(astng)
+
+
+ # IASTNGChecker interface #################################################
+
+ def open(self):
+ """initialize counters"""
+ self.stats = { 'by_module' : {},
+ 'by_msg' : {},
+ 'statement' : 0
+ }
+ for msg_cat in MSG_TYPES.values():
+ self.stats[msg_cat] = 0
+
+ def close(self):
+ """close the whole package /module, it's time to make reports !
+
+ if persistent run, pickle results for later comparison
+ """
+ # load old results if any
+ old_stats = config.load_results(self.base_name)
+ if self.config.reports:
+ self.make_reports(self.stats, old_stats)
+ # save results if persistent run
+ if self.config.persistent:
+ config.save_results(self.stats, self.base_name)
+
+ # specific reports ########################################################
+
+ def report_evaluation(self, sect, stats, old_stats):
+ """make the global evaluation report"""
+ # check with at least check 1 statements (usually 0 when there is a
+ # syntax error preventing pylint from further processing)
+ if stats['statement'] == 0:
+ raise EmptyReport()
+ # get a global note for the code
+ evaluation = self.config.evaluation
+ try:
+ note = eval(evaluation, {}, self.stats)
+ except Exception, ex:
+ msg = 'An exception occured while rating: %s' % ex
+ else:
+ stats['global_note'] = note
+ msg = 'Your code has been rated at %.2f/10' % note
+ if old_stats.has_key('global_note'):
+ msg += ' (previous run: %.2f/10)' % old_stats['global_note']
+ if self.config.comment:
+ msg = '%s\n%s' % (msg, config.get_note_message(note))
+ sect.append(Text(msg))
+
+# some reporting functions ####################################################
+
+def report_error_warning_stats(sect, stats, old_stats):
+ """make total errors / warnings report"""
+ lines = ['type', 'number', 'previous', 'difference']
+ lines += table_lines_from_stats(stats, old_stats,
+ ('convention', 'refactor',
+ 'warning', 'error'))
+ sect.append(Table(children=lines, cols=4, rheaders=1))
+
+def report_messages_stats(sect, stats, _):
+ """make messages type report"""
+ if not stats['by_msg']:
+ # don't print this report when we didn't detected any errors
+ raise EmptyReport()
+ in_order = [(value, msg_id)
+ for msg_id, value in stats['by_msg'].items()
+ if not msg_id.startswith('I')]
+ in_order.sort()
+ in_order.reverse()
+ lines = ('message id', 'occurences')
+ for value, msg_id in in_order:
+ lines += (msg_id, str(value))
+ sect.append(Table(children=lines, cols=2, rheaders=1))
+
+def report_error_warning_by_module_stats(sect, stats, _):
+ """make errors / warnings by modules report"""
+ if len(stats['by_module']) == 1:
+ # don't print this report when we are analysing a single module
+ raise EmptyReport()
+ by_mod = {}
+ for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'):
+ total = stats[m_type]
+ for module in stats['by_module'].keys():
+ mod_total = stats['by_module'][module][m_type]
+ if total == 0:
+ percent = 0
+ else:
+ percent = float((mod_total)*100) / total
+ by_mod.setdefault(module, {})[m_type] = percent
+ sorted_result = []
+ for module, mod_info in by_mod.items():
+ sorted_result.append((mod_info['error'],
+ mod_info['warning'],
+ mod_info['refactor'],
+ mod_info['convention'],
+ module))
+ sorted_result.sort()
+ sorted_result.reverse()
+ lines = ['module', 'error', 'warning', 'refactor', 'convention']
+ for line in sorted_result:
+ if line[0] == 0 and line[1] == 0:
+ break
+ lines.append(line[-1])
+ for val in line[:-1]:
+ lines.append('%.2f' % val)
+ if len(lines) == 5:
+ raise EmptyReport()
+ sect.append(Table(children=lines, cols=5, rheaders=1))
+
+
+
+# utilities ###################################################################
+
+# this may help to import modules using gettext
+
+try:
+ __builtins__._ = str
+except AttributeError:
+ __builtins__['_'] = str
+
+
+def preprocess_options(args, search_for):
+ """look for some options (keys of <search_for>) which have to be processed
+ before others
+
+ values of <search_for> are callback functions to call when the option is
+ found
+ """
+ for i, arg in enumerate(args):
+ for option in search_for:
+ ##print arg, option
+ if arg.startswith('--%s=' % option):
+ search_for[option](option, arg[len(option)+3:])
+ del args[i]
+ elif arg == '--%s' % option:
+ search_for[option](option, args[i + 1])
+ del args[i:i+2]
+
+class Run:
+ """helper class to use as main for pylint :
+
+ run(*sys.argv[1:])
+ """
+
+ def __init__(self, args, reporter=None, quiet=0):
+ self._rcfile = None
+ self._plugins = []
+ preprocess_options(args, {'rcfile': self.cb_set_rcfile,
+ 'load-plugins': self.cb_add_plugins})
+ self.linter = linter = PyLinter((
+ ('rcfile',
+ {'action' : 'callback', 'callback' : lambda *args: 1,
+ 'type': 'string', 'metavar': '<file>',
+ 'help' : 'Specify a configuration file.'}),
+
+ ('disable-all',
+ {'action' : 'callback',
+ 'callback' : self.cb_disable_all_checkers,
+ 'help' : '''Disable all possible checkers. This option should
+ precede enable-* options.'''}),
+
+ ('help-msg',
+ {'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>',
+ 'callback' : self.cb_help_message,
+ 'help' : '''Display a help message for the given message id and \
+exit. This option may be a comma separated list.'''}),
+
+ ('list-msgs',
+ {'action' : 'callback', 'metavar': '<msg-id>',
+ 'callback' : self.cb_list_messages,
+ 'help' : 'List and explain every available messages.'}),
+
+ ('generate-rcfile',
+ {'action' : 'callback', 'callback' : self.cb_generate_config,
+ 'help' : '''Generate a sample configuration file according to \
+the current configuration. You can put other options before this one to use \
+them in the configuration. This option causes the program to exit'''}),
+
+ ('generate-man',
+ {'action' : 'callback', 'callback' : self.cb_generate_manpage,
+ 'help' : '''Generate a man page for pylint. This option causes \
+the program to exit'''}),
+
+ ('debug-mode',
+ {'action' : 'callback', 'callback' : self.cb_debug_mode,
+ 'help' : '''In debug mode, checkers without error messages are \
+disabled and for others, only the ERROR messages are displayed, and no reports \
+are done by default'''}),
+
+ ('profile',
+ {'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'default': False,
+ 'help' : 'Profiled execution.'}),
+
+ ), reporter=reporter, pylintrc=self._rcfile)
+ linter.quiet = quiet
+ # register standard checkers
+ from pylint import checkers
+ checkers.initialize(linter)
+ # load command line plugins
+ linter.load_plugin_modules(self._plugins)
+ # add some help section
+ linter.add_help_section('Environment variables', config.ENV_HELP)
+ linter.add_help_section('Output', '''
+Using the default text output, the message format is :
+ MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE
+There are 5 kind of message types :
+ * (C) convention, for programming standard violation
+ * (R) refactor, for bad code smell
+ * (W) warning, for python specific problems
+ * (E) error, for much probably bugs in the code
+ * (F) fatal, if an error occured which prevented pylint from doing further \
+processing.
+ ''')
+ # read configuration
+ linter.read_config_file()
+ # is there some additional plugins in the file configuration, in
+ config_parser = linter._config_parser
+ if config_parser.has_option('master', 'load-plugins'):
+ plugins = get_csv(config_parser.get('master', 'load-plugins'))
+ linter.load_plugin_modules(plugins)
+ # now we can load file config and command line, plugins (which can
+ # provide options) have been registered
+ linter.load_config_file()
+ args = linter.load_command_line_configuration(args)
+ if not args:
+ print linter.help()
+ sys.exit(1)
+ # insert current working directory to the python path to have a correct
+ # behaviour
+ sys.path.insert(0, os.getcwd())
+ if self.linter.config.profile:
+ print >> sys.stderr, '** profiled run'
+ from hotshot import Profile, stats
+ prof = Profile('stones.prof')
+ prof.runcall(linter.check, args)
+ prof.close()
+ data = stats.load('stones.prof')
+ data.strip_dirs()
+ data.sort_stats('time', 'calls')
+ data.print_stats(30)
+ else:
+ linter.check(args)
+ sys.path.pop(0)
+
+ def cb_set_rcfile(self, name, value):
+ """callback for option preprocessing (ie before optik parsing)"""
+ self._rcfile = value
+
+ def cb_add_plugins(self, name, value):
+ """callback for option preprocessing (ie before optik parsing)"""
+ self._plugins.extend(get_csv(value))
+
+ def cb_disable_all_checkers(self, *args, **kwargs):
+ """optik callback for disabling all checkers"""
+ self.linter.disable_all_checkers()
+
+ def cb_debug_mode(self, *args, **kwargs):
+ """debug mode:
+ * checkers without error messages are disabled
+ * for others, only the ERROR messages are displayed
+ * disable reports
+ * do not save execution information
+ """
+ self.linter.disable_noerror_checkers()
+ self.linter.set_option('disable-msg-cat', ('W', 'C', 'R', 'F', 'I'))
+ self.linter.set_option('reports', False)
+ self.linter.set_option('persistent', False)
+
+
+ def cb_generate_config(self, *args, **kwargs):
+ """optik callback for sample config file generation"""
+ self.linter.generate_config()
+ sys.exit(0)
+
+ def cb_generate_manpage(self, *args, **kwargs):
+ """optik callback for sample config file generation"""
+ from pylint import __pkginfo__
+ self.linter.generate_manpage(__pkginfo__)
+ sys.exit(0)
+
+ def cb_help_message(self, option, opt_name, value, parser):
+ """optik callback for printing some help about a particular message"""
+ self.linter.help_message(get_csv(value))
+ sys.exit(0)
+
+ def cb_list_messages(self, option, opt_name, value, parser):
+ """optik callback for printing available messages"""
+ self.linter.list_messages()
+ sys.exit(0)
+
+
+if __name__ == '__main__':
+ Run(sys.argv[1:])
diff --git a/man/pylint.1 b/man/pylint.1
new file mode 100644
index 000000000..eefa920f8
--- /dev/null
+++ b/man/pylint.1
@@ -0,0 +1,371 @@
+.TH pylint 1 "2006-4-20" pylint
+.SH NAME
+.B pylint
+\- python code static checker
+
+.SH SYNOPSIS
+.B pylint
+[
+.I OPTIONS
+] [
+.I <arguments>
+]
+
+.SH DESCRIPTION
+.B pylint
+is a Python source code analyzer which looks for programming
+errors, helps enforcing a coding standard and sniffs for some code
+smells (as defined in Martin Fowler's Refactoring book)
+
+Pylint can be seen as another PyChecker since nearly all tests you
+can do with PyChecker can also be done with Pylint. However, Pylint
+offers some more features, like checking length of lines of code,
+checking if variable names are well-formed according to your coding
+standard, or checking if declared interfaces are truly implemented,
+and much more.
+
+Additionally, it is possible to write plugins to add your own checks.
+
+.SH OPTIONS
+.IP "--version"
+show program's version number and exit
+.IP "--help, -h"
+show this help message and exit
+
+.SH MASTER
+lint Python modules using external checkers.
+
+ This is the main checker controling the other ones and the reports
+ generation. It is itself both a raw checker and an astng checker in order
+ to:
+ * handle message activation / deactivation at the module level
+ * handle some basic but necessary stats'data (number of classes, methods...)
+
+This checker also defines the following reports:
+ * R0001: Total errors / warnings
+ * R0002: % errors / warnings by module
+ * R0003: Messages
+ * R0004: Global evaluation
+
+.IP "--rcfile=<file>"
+Specify a configuration file.
+.IP "--disable-all"
+Disable all possible checkers. This option should precede enable-* options.
+.IP "--help-msg=<msg-id>"
+Display a help message for the given message id and exit. This option may be a comma separated list.
+.IP "--list-msgs"
+List and explain every available messages.
+.IP "--generate-rcfile"
+Generate a sample configuration file according to the current configuration. You can put other options before this one to use them in the configuration. This option causes the program to exit
+.IP "--generate-man"
+Generate a man page for pylint. This option causes the program to exit
+.IP "--debug-mode"
+In debug mode, checkers without error messages are disabled and for others, only the ERROR messages are displayed, and no reports are done by default
+.IP "--profile=<y_or_n>"
+Profiled execution.
+.IP "--ignore=<file>"
+Add <file or directory> to the black list. It should be a base name, not a path. You may set this option multiple times.
+.IP "--persistent=<y_or_n>"
+Pickle collected data for later comparisons.
+.IP "--cache-size=<size>"
+Set the cache size for astng objects.
+.IP "--load-plugins=<modules>"
+List of plugins (as comma separated values of python modules names) to load, usually to register additional checkers.
+
+.SH REPORTS
+Options related to messages / statistics reporting
+.IP "--reports=<y_or_n>, -r<y_or_n>"
+Tells wether to display a full report or only the messages
+.IP "--html=<y_or_n>"
+Use HTML as output format instead of text
+.IP "--parseable=<y_or_n>, -p<y_or_n>"
+Use a parseable text output format, so your favorite text editor will be able to jump to the line corresponding to a message.
+.IP "--color=<y_or_n>"
+Colorizes text output using ansi escape codes
+.IP "--files-output=<y_or_n>"
+Put messages in a separate file for each module / package specified on the command line instead of printing them on stdout. Reports (if any) will be written in a file name "pylint_global.[txt|html]".
+.IP "--evaluation=<python_expression>"
+Python expression which should return a note less than 10 (10 is the highest note).You have access to the variables errors warning, statement which respectivly contain the number of errors / warnings messages and the total number of statements analyzed. This is used by the global evaluation report (R0004).
+.IP "--comment=<y_or_n>"
+Add a comment according to your evaluation note. This is used by the global evaluation report (R0004).
+.IP "--include-ids=<y_or_n>, -i<y_or_n>"
+Include message's id in output
+.IP "--enable-msg-cat=<msg cats>"
+Enable all messages in the listed categories.
+.IP "--disable-msg-cat=<msg cats>"
+Disable all messages in the listed categories.
+.IP "--enable-msg=<msg ids>"
+Enable the message with the given id.
+.IP "--disable-msg=<msg ids>"
+Disable the message with the given id.
+.IP "--enable-report=<rpt ids>"
+Enable the report with the given id.
+.IP "--disable-report=<rpt ids>"
+Disable the report with the given id.
+
+.SH DESIGN
+checks for sign of poor/misdesign:
+ * number of methods, attributes, local variables...
+ * size, complexity of functions, methods
+
+.IP "--enable-design=<y_or_n>"
+Enable / disable this checker
+.IP "--max-args=<int>"
+Maximum number of arguments for function / method
+.IP "--max-locals=<int>"
+Maximum number of locals for function / method body
+.IP "--max-returns=<int>"
+Maximum number of return / yield for function / method body
+.IP "--max-branchs=<int>"
+Maximum number of branch for function / method body
+.IP "--max-statements=<int>"
+Maximum number of statements in function / method body
+.IP "--max-parents=<num>"
+Maximum number of parents for a class (see R0901).
+.IP "--max-attributes=<num>"
+Maximum number of attributes for a class (see R0902).
+.IP "--min-public-methods=<num>"
+Minimum number of public methods for a class (see R0903).
+.IP "--max-public-methods=<num>"
+Maximum number of public methods for a class (see R0904).
+
+.SH BASIC
+checks for :
+ * doc strings
+ * modules / classes / functions / methods / arguments / variables name
+ * number of arguments, local variables, branchs, returns and statements in
+functions, methods
+ * required module attributes
+ * dangerous default values as arguments
+ * redefinition of function / method / class
+ * uses of the global statement
+
+This checker also defines the following reports:
+ * R0101: Statistics by type
+
+.IP "--enable-basic=<y_or_n>"
+Enable / disable this checker
+.IP "--required-attributes=<attributes>"
+Required attributes for module, separated by a comma
+.IP "--no-docstring-rgx=<regexp>"
+Regular expression which should only match functions or classes name which do not require a docstring
+.IP "--module-rgx=<regexp>"
+Regular expression which should only match correct module names
+.IP "--const-rgx=<regexp>"
+Regular expression which should only match correct module level names
+.IP "--class-rgx=<regexp>"
+Regular expression which should only match correct class names
+.IP "--function-rgx=<regexp>"
+Regular expression which should only match correct function names
+.IP "--method-rgx=<regexp>"
+Regular expression which should only match correct method names
+.IP "--attr-rgx=<regexp>"
+Regular expression which should only match correct instance attribute names
+.IP "--argument-rgx=<regexp>"
+Regular expression which should only match correct argument names
+.IP "--variable-rgx=<regexp>"
+Regular expression which should only match correct variable names
+.IP "--inlinevar-rgx=<regexp>"
+Regular expression which should only match correct list comprehension / generator expression variable names
+.IP "--good-names=<names>"
+Good variable names which should always be accepted, separated by a comma
+.IP "--bad-names=<names>"
+Bad variable names which should always be refused, separated by a comma
+.IP "--bad-functions=<builtin function names>"
+List of builtins function names that should not be used, separated by a comma
+
+.SH CLASSES
+checks for :
+ * methods without self as first argument
+ * overriden methods signature
+ * access only to existant members via self
+ * attributes not defined in the __init__ method
+ * supported interfaces implementation
+ * unreachable code
+
+.IP "--enable-classes=<y_or_n>"
+Enable / disable this checker
+.IP "--ignore-iface-methods=<method names>"
+List of interface methods to ignore, separated by a comma. This is used for instance to not check methods defines in Zope's Interface base class.
+.IP "--defining-attr-methods=<method names>"
+List of method names used to declare (i.e. assign) instance attributes.
+
+.SH SIMILARITIES
+checks for similarities and duplicated code. This computation may be
+ memory / CPU intensive, so you should disable it if you experiments some
+ problems.
+
+This checker also defines the following reports:
+ * R0801: Duplication
+
+.IP "--enable-similarities=<y_or_n>"
+Enable / disable this checker
+.IP "--min-similarity-lines=<int>"
+Minimum lines number of a similarity.
+.IP "--ignore-comments=<y or n>"
+Ignore comments when computing similarities.
+.IP "--ignore-docstrings=<y or n>"
+Ignore docstrings when computing similarities.
+
+.SH EXCEPTIONS
+checks for
+ * excepts without exception filter
+ * string exceptions
+
+.IP "--enable-exceptions=<y_or_n>"
+Enable / disable this checker
+
+.SH FORMAT
+checks for :
+ * unauthorized constructions
+ * strict indentation
+ * line length
+ * use of <> instead of !=
+
+.IP "--enable-format=<y_or_n>"
+Enable / disable this checker
+.IP "--max-line-length=<int>"
+Maximum number of characters on a single line.
+.IP "--max-module-lines=<int>"
+Maximum number of lines in a module
+.IP "--indent-string=<string>"
+String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 tab).
+
+.SH IMPORTS
+checks for
+ * external modules dependencies
+ * relative / wildcard imports
+ * cyclic imports
+ * uses of deprecated modules
+
+This checker also defines the following reports:
+ * R0401: External dependencies
+ * R0402: Modules dependencies graph
+
+.IP "--enable-imports=<y_or_n>"
+Enable / disable this checker
+.IP "--deprecated-modules=<modules>"
+Deprecated modules which should not be used, separated by a comma
+.IP "--import-graph=<file.dot>"
+Create a graph of every (i.e. internal and external) dependencies in the given file (report R0402 must not be disabled)
+.IP "--ext-import-graph=<file.dot>"
+Create a graph of external dependencies in the given file (report R0402 must not be disabled)
+.IP "--int-import-graph=<file.dot>"
+Create a graph of internal dependencies in the given file (report R0402 must not be disabled)
+
+.SH MISCELLANEOUS
+checks for:
+ * warning notes in the code like FIXME, XXX
+ * PEP 263: source code with non ascii character but no encoding declaration
+
+.IP "--enable-miscellaneous=<y_or_n>"
+Enable / disable this checker
+.IP "--notes=<comma separated values>"
+List of note tags to take in consideration, separated by a comma. Default to FIXME, XXX, TODO
+
+.SH NEWSTYLE
+checks for usage of new style capabilities on old style classes and
+ other new/old styles conflicts problems
+ * use of property, __slots__, super
+ * "super" usage
+ * raising a new style class as exception
+
+.IP "--enable-newstyle=<y_or_n>"
+Enable / disable this checker
+
+.SH METRICS
+does not check anything but gives some raw metrics :
+ * total number of lines
+ * total number of code lines
+ * total number of docstring lines
+ * total number of comments lines
+ * total number of empty lines
+
+This checker also defines the following reports:
+ * R0701: Raw metrics
+
+.IP "--enable-metrics=<y_or_n>"
+Enable / disable this checker
+
+.SH TYPECHECK
+try to find bugs in the code using type inference
+
+.IP "--enable-typecheck=<y_or_n>"
+Enable / disable this checker
+.IP "--ignore-mixin-members=<y_or_n>"
+Tells wether missing members accessed in mixin class should be ignored. A mixin class is detected if its name ends with "mixin" (case insensitive).
+.IP "--zope=<y_or_n>"
+When zope mode is activated, consider the acquired-members option to ignore access to some undefined attributes.
+.IP "--acquired-members=<members names>"
+List of members which are usually get through zope's acquisition mecanism and so shouldn't trigger E0201 when accessed (need zope=yes to be considered.
+
+.SH VARIABLES
+checks for
+ * unused variables / imports
+ * undefined variables
+ * redefinition of variable from builtins or from an outer scope
+ * use of variable before assigment
+
+.IP "--enable-variables=<y_or_n>"
+Enable / disable this checker
+.IP "--init-import=<y_or_n>"
+Tells wether we should check for unused import in __init__ files.
+.IP "--dummy-variables-rgx=<regexp>"
+A regular expression matching names used for dummy variables (i.e. not used).
+.IP "--additional-builtins=<comma separated list>"
+List of additional names supposed to be defined in builtins. Remember that you should avoid to define new builtins when possible.
+
+.SH ENVIRONMENT VARIABLES
+
+The following environment variables are used :
+ * PYLINTHOME
+ path to the directory where data of persistent run will be stored. If not
+found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working
+directory) . The current PYLINTHOME is /home/syt/.pylint.d.
+ * PYLINTRC
+ path to the configuration file. If not found, it will use the first
+existant file in ~/.pylintrc, /etc/pylintrc. The current PYLINTRC is
+None.
+
+
+.SH OUTPUT
+
+Using the default text output, the message format is :
+ MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE
+There are 5 kind of message types :
+ * (C) convention, for programming standard violation
+ * (R) refactor, for bad code smell
+ * (W) warning, for python specific problems
+ * (E) error, for much probably bugs in the code
+ * (F) fatal, if an error occured which prevented pylint from doing further processing.
+
+
+.SH SEE ALSO
+/usr/share/doc/pythonX.Y-pylint/
+
+.SH COPYRIGHT
+Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com).
+Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
+http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published
+by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+MA 02111-1307 USA.
+.SH BUGS
+Please report bugs on the project's mailing list:
+mailto://python-projects@logilab.org
+
+.SH AUTHOR
+Sylvain Thenault <sylvain.thenault@logilab.fr>
+
diff --git a/reporters/__init__.py b/reporters/__init__.py
new file mode 100644
index 000000000..54d14bd19
--- /dev/null
+++ b/reporters/__init__.py
@@ -0,0 +1,67 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""utilities methods and classes for reporters
+
+ Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__revision__ = "$Id: __init__.py,v 1.14 2005-01-20 15:12:56 syt Exp $"
+
+import sys
+
+CMPS = ['=', '-', '+']
+
+def diff_string(old, new):
+ """given a old and new int value, return a string representing the
+ difference
+ """
+ diff = abs(old - new)
+ diff_str = "%s%s" % (CMPS[cmp(old, new)], diff and ('%.2f' % diff) or '')
+ return diff_str
+
+
+class EmptyReport(Exception):
+ """raised when a report is empty and so should not be displayed"""
+
+class BaseReporter:
+ """base class for reporters"""
+
+ extension = ''
+
+ def __init__(self, output=sys.stdout):
+ self.linter = None
+ self.include_ids = None
+ self.section = 0
+ self.out = None
+ self.set_output(output)
+
+ def set_output(self, output):
+ """set output stream"""
+ self.out = output
+
+ def writeln(self, string=''):
+ """write a line in the output buffer"""
+ print >> self.out, string
+
+ def display_results(self, layout):
+ """display results encapsulated in the layout tree"""
+ self.section = 0
+ if self.include_ids and hasattr(layout, 'report_id'):
+ layout.children[0].children[0].data += ' (%s)' % layout.report_id
+ self._display(layout)
+
+ def _display(self, layout):
+ """display the layout"""
+ raise NotImplementedError()
+
diff --git a/reporters/html.py b/reporters/html.py
new file mode 100644
index 000000000..625c4475a
--- /dev/null
+++ b/reporters/html.py
@@ -0,0 +1,64 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2002-2006 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+HTML reporter
+"""
+
+__revision__ = "$Id: html.py,v 1.14 2006-03-08 15:53:41 syt Exp $"
+
+import sys
+from cgi import escape
+
+from logilab.common.ureports import HTMLWriter, Section, Table
+
+from pylint.interfaces import IReporter
+from pylint.reporters import BaseReporter
+
+
+class HTMLReporter(BaseReporter):
+ """report messages and layouts in HTML
+ """
+
+ __implements__ = IReporter
+ extension = 'html'
+
+ def __init__(self, output=sys.stdout):
+ BaseReporter.__init__(self, output)
+ self.msgs = []
+
+ def add_message(self, msg_id, location, msg):
+ """manage message of different type and in the context of path"""
+ module, obj, line = location[1:]
+ if self.include_ids:
+ sigle = msg_id
+ else:
+ sigle = msg_id[0]
+ self.msgs += [sigle, module, obj, str(line), escape(msg)]
+
+ def _display(self, layout):
+ """launch layouts display
+
+ overriden from BaseReporter to add a blank line...
+ """
+ if self.msgs:
+ # add stored messages to the layout
+ msgs = ['type', 'module', 'object', 'line', 'message']
+ msgs += self.msgs
+ sect = Section('Messages')
+ layout.append(sect)
+ sect.append(Table(cols=5, children=msgs, rheaders=1))
+ self.msgs = []
+ HTMLWriter().format(layout, self.out)
+
diff --git a/reporters/text.py b/reporters/text.py
new file mode 100644
index 000000000..f4dfd2c01
--- /dev/null
+++ b/reporters/text.py
@@ -0,0 +1,151 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+Plain text reporter
+"""
+
+__revision__ = "$Id: text.py,v 1.21 2005-12-28 00:24:35 syt Exp $"
+
+import os
+import sys
+
+from logilab.common.ureports import TextWriter
+from logilab.common.textutils import colorize_ansi
+
+from pylint.interfaces import IReporter
+from pylint.reporters import BaseReporter
+
+TITLE_UNDERLINES = ['', '=', '-', '.']
+
+
+## def modname_to_path(modname, prefix=os.getcwd() + os.sep):
+## """transform a module name into a path"""
+## module = load_module_from_name(modname).__file__.replace(prefix, '')
+## return module.replace('.pyc', '.py').replace('.pyo', '.py')
+
+
+class TextReporter(BaseReporter):
+ """reports messages and layouts in plain text
+ """
+
+ __implements__ = IReporter
+ extension = 'txt'
+
+ def __init__(self, output=sys.stdout):
+ BaseReporter.__init__(self, output)
+ self._modules = {}
+
+ def add_message(self, msg_id, location, msg):
+ """manage message of different type and in the context of path"""
+ module, obj, line = location[1:]
+ if not self._modules.has_key(module):
+ self.writeln('************* Module %s' % module)
+ self._modules[module] = 1
+ if obj:
+ obj = ':%s' % obj
+ if self.include_ids:
+ sigle = msg_id
+ else:
+ sigle = msg_id[0]
+ self.writeln('%s:%3s%s: %s' % (sigle, line, obj, msg))
+
+ def _display(self, layout):
+ """launch layouts display"""
+ print >> self.out
+ TextWriter().format(layout, self.out)
+
+
+class TextReporter2(TextReporter):
+ """a reporter very similar to TextReporter, but display messages in a form
+ recognized by most text editors :
+
+ <filename>:<linenum>:<msg>
+ """
+ def __init__(self, output=sys.stdout, relative=True):
+ TextReporter.__init__(self, output)
+ if relative:
+ self._prefix = os.getcwd() + os.sep
+ else:
+ self._prefix = ''
+
+ def add_message(self, msg_id, location, msg):
+ """manage message of different type and in the context of path"""
+ path, _, obj, line = location
+ if obj:
+ obj = ', %s' % obj
+ if self.include_ids:
+ sigle = msg_id
+ else:
+ sigle = msg_id[0]
+ if self._prefix:
+ path = path.replace(self._prefix, '')
+## try:
+## modpath = self._modules[module]
+## except KeyError:
+## modpath = self._modules[module] = self.linter.current_file or \
+## modname_to_path(module)
+ self.writeln('%s:%s: [%s%s] %s' % (path, line, sigle, obj, msg))
+
+
+class ColorizedTextReporter(TextReporter):
+ """Simple TextReporter that colorizes text output"""
+
+ COLOR_MAPPING = {
+ "I" : ("green", None),
+ 'C' : (None, "bold"),
+ 'R' : ("magenta", "bold, italic"),
+ 'W' : ("blue", None),
+ 'E' : ("red", "bold"),
+ 'F' : ("red", "bold, underline"),
+ 'S' : ("yellow", "inverse"), # S stands for module Separator
+ }
+
+ def __init__(self, output=sys.stdout, color_mapping = None):
+ TextReporter.__init__(self, output)
+ self.color_mapping = color_mapping or \
+ dict(ColorizedTextReporter.COLOR_MAPPING)
+
+
+ def _get_decoration(self, msg_id):
+ """Returns the tuple color, style associated with msg_id as defined
+ in self.color_mapping
+ """
+ try:
+ return self.color_mapping[msg_id]
+ except KeyError:
+ return None, None
+
+ def add_message(self, msg_id, location, msg):
+ """manage message of different types, and colorize output
+ using ansi escape codes
+ """
+ module, obj, line = location[1:]
+ if not self._modules.has_key(module):
+ color, style = self._get_decoration('S')
+ modsep = colorize_ansi('************* Module %s' % module,
+ color, style)
+ self.writeln(modsep)
+ self._modules[module] = 1
+ if obj:
+ obj = ':%s' % obj
+ if self.include_ids:
+ sigle = msg_id
+ else:
+ sigle = msg_id[0]
+ color, style = self._get_decoration(sigle)
+ msg = colorize_ansi(msg, color, style)
+ sigle = colorize_ansi(sigle, color, style)
+ self.writeln('%s:%3s%s: %s' % (sigle, line, obj, msg))
+
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 000000000..78cfd7693
--- /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 000000000..412707939
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python
+# pylint: disable-msg=W0142,W0403,W0404,E0611,W0613,W0622,W0622,W0704,R0904
+#
+# Copyright (c) 2003 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Generic Setup script, takes package info from __pkginfo__.py file """
+
+from __future__ import nested_scopes
+
+__revision__ = '$Id: setup.py,v 1.26 2006-03-05 15:47:17 syt Exp $'
+
+import os
+import sys
+import shutil
+from distutils.core import setup
+from distutils.command import install_lib
+from os.path import isdir, exists, join, walk
+
+# import required features
+from __pkginfo__ import modname, version, license, short_desc, long_desc, \
+ web, author, author_email, classifiers
+# import optional features
+try:
+ from __pkginfo__ import distname
+except ImportError:
+ distname = modname
+try:
+ from __pkginfo__ import scripts
+except ImportError:
+ scripts = []
+try:
+ from __pkginfo__ import data_files
+except ImportError:
+ data_files = None
+try:
+ from __pkginfo__ import subpackage_of
+except ImportError:
+ subpackage_of = None
+try:
+ from __pkginfo__ import include_dirs
+except ImportError:
+ include_dirs = []
+try:
+ from __pkginfo__ import ext_modules
+except ImportError:
+ ext_modules = None
+
+BASE_BLACKLIST = ('CVS', 'debian', 'dist', 'build', '__buildlog')
+IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc')
+
+
+def ensure_scripts(linux_scripts):
+ """
+ Creates the proper script names required for each platform
+ (taken from 4Suite)
+ """
+ from distutils import util
+ if util.get_platform()[:3] == 'win':
+ scripts_ = [script + '.bat' for script in linux_scripts]
+ else:
+ scripts_ = linux_scripts
+ return scripts_
+
+
+def get_packages(directory, prefix):
+ """return a list of subpackages for the given directory
+ """
+ result = []
+ for package in os.listdir(directory):
+ absfile = join(directory, package)
+ if isdir(absfile):
+ if exists(join(absfile, '__init__.py')) or \
+ package in ('test', 'tests'):
+ if prefix:
+ result.append('%s.%s' % (prefix, package))
+ else:
+ result.append(package)
+ result += get_packages(absfile, result[-1])
+ return result
+
+def export(from_dir, to_dir,
+ blacklist=BASE_BLACKLIST,
+ ignore_ext=IGNORED_EXTENSIONS):
+ """make a mirror of from_dir in to_dir, omitting directories and files
+ listed in the black list
+ """
+ def make_mirror(arg, directory, fnames):
+ """walk handler"""
+ for norecurs in blacklist:
+ try:
+ fnames.remove(norecurs)
+ except ValueError:
+ pass
+ for filename in fnames:
+ # don't include binary files
+ if filename[-4:] in ignore_ext:
+ continue
+ if filename[-1] == '~':
+ continue
+ src = '%s/%s' % (directory, filename)
+ dest = to_dir + src[len(from_dir):]
+ print >> sys.stderr, src, '->', dest
+ if os.path.isdir(src):
+ if not exists(dest):
+ os.mkdir(dest)
+ else:
+ if exists(dest):
+ os.remove(dest)
+ shutil.copy2(src, dest)
+ try:
+ os.mkdir(to_dir)
+ except OSError, ex:
+ # file exists ?
+ import errno
+ if ex.errno != errno.EEXIST:
+ raise
+ walk(from_dir, make_mirror, None)
+
+
+EMPTY_FILE = '"""generated file, don\'t modify or your data will be lost"""\n'
+
+class MyInstallLib(install_lib.install_lib):
+ """extend install_lib command to handle package __init__.py and
+ include_dirs variable if necessary
+ """
+ def run(self):
+ """overriden from install_lib class"""
+ install_lib.install_lib.run(self)
+ # create Products.__init__.py if needed
+ if subpackage_of:
+ product_init = join(self.install_dir, subpackage_of, '__init__.py')
+ if not exists(product_init):
+ self.announce('creating %s' % product_init)
+ stream = open(product_init, 'w')
+ stream.write(EMPTY_FILE)
+ stream.close()
+ # manually install included directories if any
+ if include_dirs:
+ if subpackage_of:
+ base = join(subpackage_of, modname)
+ else:
+ base = modname
+ for directory in include_dirs:
+ dest = join(self.install_dir, base, directory)
+ export(directory, dest)
+
+def install(**kwargs):
+ """setup entry point"""
+ if subpackage_of:
+ package = subpackage_of + '.' + modname
+ kwargs['package_dir'] = {package : '.'}
+ packages = [package] + get_packages(os.getcwd(), package)
+ else:
+ kwargs['package_dir'] = {modname : '.'}
+ packages = [modname] + get_packages(os.getcwd(), modname)
+ kwargs['packages'] = packages
+ return setup(name = distname,
+ version = version,
+ license =license,
+ description = short_desc,
+ long_description = long_desc,
+ author = author,
+ author_email = author_email,
+ url = web,
+ classifiers = classifiers,
+ scripts = ensure_scripts(scripts),
+ data_files=data_files,
+ ext_modules=ext_modules,
+ cmdclass={'install_lib': MyInstallLib},
+ **kwargs
+ )
+
+if __name__ == '__main__' :
+ install()
diff --git a/test/fulltest.sh b/test/fulltest.sh
new file mode 100755
index 000000000..a62533915
--- /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 000000000..3de8891c8
--- /dev/null
+++ b/test/func_test.py
@@ -0,0 +1,193 @@
+# Copyright (c) 2002-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""functional/non regression tests for pylint"""
+
+__revision__ = '$Id: func_test.py,v 1.37 2005-12-28 14:58:22 syt Exp $'
+
+import unittest
+import sys
+import re
+import new
+from os import linesep
+from os.path import exists
+
+from logilab.common import testlib
+
+from utils import get_tests_info, fix_path, TestReporter
+
+from pylint.lint import PyLinter
+from pylint import checkers
+
+test_reporter = TestReporter()
+linter = PyLinter()
+linter.set_reporter(test_reporter)
+linter.config.persistent = 0
+linter.quiet = 1
+checkers.initialize(linter)
+
+PY23 = sys.version_info >= (2, 3)
+PY24 = sys.version_info >= (2, 4)
+
+
+if linesep != '\n':
+ LINE_RGX = re.compile(linesep)
+ def ulines(string):
+ return LINE_RGX.sub('\n', string)
+else:
+ def ulines(string):
+ return string
+
+INFO_TEST_RGX = re.compile('^func_i\d\d\d\d$')
+
+def exception_str(ex):
+ """function used to replace default __str__ method of exception instances"""
+ return 'in %s\n:: %s' % (ex.file, ', '.join(ex.args))
+
+class LintTestUsingModule(testlib.TestCase):
+
+ def test_functionality(self):
+ tocheck = ['input.'+self.module]
+ if self.depends:
+ tocheck += ['input.%s' % name.replace('.py', '')
+ for name, file in self.depends]
+ self._test(tocheck)
+
+ def _test(self, tocheck):
+ if INFO_TEST_RGX.match(self.module):
+ linter.enable_message_category('I')
+ else:
+ linter.disable_message_category('I')
+ try:
+ linter.check(tocheck)
+ except Exception, ex:
+ # need finalization to restore a correct state
+ linter.reporter.finalize()
+ ex.file = tocheck
+ ex.__str__ = new.instancemethod(exception_str, ex, None)
+ raise
+ if self.module.startswith('func_noerror_'):
+ expected = ''
+ else:
+ output = open(self.output)
+ expected = output.read().strip()
+ output.close()
+ got = linter.reporter.finalize().strip()
+ try:
+ self.assertLinesEquals(got, expected)
+ except Exception, ex:
+ ex.file = tocheck
+ ex.__str__ = new.instancemethod(exception_str, ex, None)
+ raise # AssertionError('%s: %r\n!=\n%r\n\n%s' % (self.module, got, expected, ex))
+
+class LintTestUsingFile(LintTestUsingModule):
+
+ def test_functionality(self):
+ tocheck = ['input/' + self.module + '.py']
+ if self.depends:
+ tocheck += ['input/%s' % name for name, file in self.depends]
+ self._test(tocheck)
+
+
+class TestTests(unittest.TestCase):
+ """check that all testable messages have been checked"""
+ def test(self):
+ todo = linter._messages.keys()
+ for msg_id in test_reporter.message_ids.keys():
+ todo.remove(msg_id)
+ todo.sort()
+ if PY23:
+ self.assertEqual(todo, ['E0503', 'F0002', 'F0202', 'F0321', 'I0001'])
+ else:
+ self.assertEqual(todo, ['F0002', 'F0202', 'F0321', 'I0001'])
+
+
+def make_tests(filter_rgx):
+ """generate tests classes from test info
+
+ return the list of generated test classes
+ """
+ if filter_rgx:
+ is_to_run = re.compile(filter_rgx).match
+ else:
+ is_to_run = lambda x: 1
+ tests = []
+ for module_file, messages_file in get_tests_info('func_', '.py') + [('nonexistant', 'messages/nonexistant.txt')]:
+ # skip those tests with python >= 2.3 since py2.3 detects them by itself
+ if PY23 and module_file == "func_unknown_encoding.py": #"func_nonascii_noencoding.py"):
+ continue
+ if not PY24:
+ if module_file == "func_noerror_staticmethod_as_decorator.py" or \
+ module_file.endswith('py24.py'):
+ continue
+ if not is_to_run(module_file):
+ continue
+ base = module_file.replace('func_', '').replace('.py', '')
+ dependancies = get_tests_info(base, '.py')
+
+ class LintTestUsingModuleTC(LintTestUsingModule):
+ module = module_file.replace('.py', '')
+ output = messages_file
+ depends = dependancies or None
+ tests.append(LintTestUsingModuleTC)
+
+ if MODULES_ONLY:
+ continue
+
+ class LintTestUsingFileTC(LintTestUsingFile):
+ module = module_file.replace('.py', '')
+ output = exists(messages_file + '2') and (messages_file + '2') or messages_file
+ depends = dependancies or None
+ tests.append(LintTestUsingFileTC)
+
+## # special test for f0003
+## module_file, messages_file in get_tests_info('func_f0003', '.pyc')
+## class LintTestSubclass(LintTest):
+## module = module_file.replace('.pyc', '')
+## output = messages_file
+## depends = dependancies or None
+## tests.append(LintTestSubclass)
+
+ class LintBuiltinModuleTest(LintTestUsingModule):
+ output = 'messages/builtin_module.txt'
+ module = 'sys'
+ def test_functionality(self):
+ self._test(['sys'])
+ tests.append(LintBuiltinModuleTest)
+
+ if not filter_rgx:
+ # test all features are tested :)
+ tests.append(TestTests)
+
+ return tests
+
+FILTER_RGX = None
+MODULES_ONLY = False
+
+def suite():
+ return unittest.TestSuite([unittest.makeSuite(test)
+ for test in make_tests(FILTER_RGX)])
+
+if __name__=='__main__':
+ if '-m' in sys.argv:
+ MODULES_ONLY = True
+ sys.argv.remove('-m')
+
+ if len(sys.argv) > 1:
+ FILTER_RGX = sys.argv[1]
+ del sys.argv[1]
+ unittest.main(defaultTest='suite')
+
+
diff --git a/test/func_test_sample_config.py b/test/func_test_sample_config.py
new file mode 100644
index 000000000..a65482794
--- /dev/null
+++ b/test/func_test_sample_config.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2002-2004 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""functional tests using the sample configuration file, should behave exactly
+as with the default configuration
+"""
+__revision__ = '$Id: func_test_sample_config.py,v 1.3 2005-04-15 10:40:24 syt Exp $'
+
+from func_test import *
+
+from os.path import join
+
+import pylint
+sample_config = join(pylint.__path__[0], 'examples', 'pylintrc')
+linter.load_file_configuration(sample_config)
+
+if __name__=='__main__':
+ unittest.main(defaultTest='suite')
diff --git a/test/input/__init__.py b/test/input/__init__.py
new file mode 100644
index 000000000..60e92b76c
--- /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 000000000..2051a7b96
--- /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 000000000..25aed5cae
--- /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 000000000..eda02e13b
--- /dev/null
+++ b/test/input/func_attrs_definition_order.py
@@ -0,0 +1,15 @@
+# pylint: disable-msg=R0903
+"""yo"""
+
+__revision__ = '$I$'
+
+class Aaaa:
+ """class with attributes defined in wrong order"""
+ def __init__(self):
+ var1 = self._var2
+ self._var2 = 3
+ print var1
+
+class Bbbb(object):
+ """hop"""
+ __revision__ = __revision__ # no problemo marge
diff --git a/test/input/func_bad_assigment_to_exception_var.py b/test/input/func_bad_assigment_to_exception_var.py
new file mode 100644
index 000000000..b404cb351
--- /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 000000000..f282ff783
--- /dev/null
+++ b/test/input/func_base_stmt_without_effect.py
@@ -0,0 +1,15 @@
+"""
+ 'W0103': ('Statement seems to have no effect',
+ 'Used when a statement doesn\'t have (or at least seems to) \
+ any effect.'),
+"""
+
+__revision__ = ''
+
+__revision__
+
+__revision__ <= 1
+
+__revision__.lower() # ok
+
+[i for i in __revision__] # ko
diff --git a/test/input/func_block_disable_msg.py b/test/input/func_block_disable_msg.py
new file mode 100644
index 000000000..f82fbd58a
--- /dev/null
+++ b/test/input/func_block_disable_msg.py
@@ -0,0 +1,87 @@
+"""pylint option block-disable-msg"""
+
+__revision__ = None
+
+class Foo(object):
+ """block-disable-msg test"""
+
+ def __init__(self):
+ pass
+
+ def meth1(self, arg):
+ """this issues a message"""
+ print self
+
+ def meth2(self, arg):
+ """and this one not"""
+ # pylint: disable-msg=W0613
+ print self\
+ + "foo"
+
+ def meth3(self):
+ """test one line disabling"""
+ # no error
+ print self.bla # pylint: disable-msg=E1101
+ # error
+ print self.blop
+
+ def meth4(self):
+ """test re-enabling"""
+ # pylint: disable-msg=E1101
+ # no error
+ print self.bla
+ print self.blop
+ # pylint: enable-msg=E1101
+ # error
+ print self.blip
+
+ def meth5(self):
+ """test IF sub-block re-enabling"""
+ # pylint: disable-msg=E1101
+ # no error
+ print self.bla
+ if self.blop:
+ # pylint: enable-msg=E1101
+ # error
+ print self.blip
+ else:
+ # no error
+ print self.blip
+ # no error
+ print self.blip
+
+ def meth6(self):
+ """test TRY/EXCEPT sub-block re-enabling"""
+ # pylint: disable-msg=E1101
+ # no error
+ print self.bla
+ try:
+ # pylint: enable-msg=E1101
+ # error
+ print self.blip
+ except UndefinedName: # pylint: disable-msg=E0602
+ # no error
+ print self.blip
+ # no error
+ print self.blip
+
+ def meth7(self):
+ """test one line block opening disabling"""
+ if self.blop: # pylint: disable-msg=E1101
+ # error
+ print self.blip
+ else:
+ # error
+ print self.blip
+ # error
+ print self.blip
+
+
+ def meth8(self):
+ """test late disabling"""
+ # error
+ print self.blip
+ # pylint: disable-msg=E1101
+ # no error
+ print self.bla
+ print self.blop
diff --git a/test/input/func_class_members.py b/test/input/func_class_members.py
new file mode 100644
index 000000000..8b2e969c2
--- /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 000000000..4186aa5fd
--- /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 000000000..0bf87278c
--- /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 000000000..7f2867292
--- /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 000000000..2d2f571ee
--- /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 000000000..f2bb592cd
--- /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 000000000..11ef99fda
--- /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 000000000..08e64028f
--- /dev/null
+++ b/test/input/func_e0101.py
@@ -0,0 +1,11 @@
+# pylint: disable-msg=R0903
+"""test __init__ return
+"""
+
+__revision__ = 'yo'
+
+class MyClass:
+ """dummy class"""
+
+ def __init__(self):
+ return 1
diff --git a/test/input/func_e0203.py b/test/input/func_e0203.py
new file mode 100644
index 000000000..d51de0de3
--- /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 000000000..ecbc51c39
--- /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 000000000..2bb108998
--- /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 000000000..af32fd883
--- /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 000000000..8a4ad323c
--- /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 000000000..b8673df7f
--- /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 000000000..e69de29bb
--- /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 000000000..8c414b6be
--- /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 000000000..af6de2409
--- /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 000000000..f8443fa1f
--- /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 000000000..0cfe2bd83
--- /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 000000000..da66d63ed
--- /dev/null
+++ b/test/input/func_format.py
@@ -0,0 +1,61 @@
+# pylint:disable-msg=C0103,W0104
+"""Check format
+"""
+__revision__ = ''
+
+notpreceded= 1
+notfollowed =1
+notfollowed <=1
+
+correct = 1
+correct >= 1
+
+def func(arg, arg2):
+ """test named argument
+ """
+ func(arg=arg+1,
+ arg2=arg2-arg)
+
+aaaa,bbbb = 1,2
+aaaa |= bbbb
+aaaa &= bbbb
+
+
+if aaaa: pass
+else:
+ aaaa,bbbb = 1,2
+ aaaa,bbbb = bbbb,aaaa
+
+bbbb = (1,2,3)
+
+aaaa = bbbb[1:]
+aaaa = bbbb[:1]
+aaaa = bbbb[:]
+
+aaaa = {aaaa:bbbb}
+
+
+# allclose(x,y) uses |x-y|<ATOL+RTOL*|y|
+"""docstring,should not match
+isn't it:yes!
+a=b
+"""
+aaaa = 'multiple lines\
+string,hehehe'
+
+
+boo = 2 # allclose(x,y) uses |x-y|<ATOL+RTOL*|y|
+
+def other(funky):
+ """yo, test formatted result with indentation"""
+ funky= funky+2
+
+html = """<option value="=">ist genau gleich</option>
+yo+=4
+"""
+html2 = """<option value='='>ist genau gleich</option>
+yo+=4
+"""
+
+func('''<body>Hello
+</body>''')
diff --git a/test/input/func_globals.py b/test/input/func_globals.py
new file mode 100644
index 000000000..f7e6c6536
--- /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 000000000..8b2f436b5
--- /dev/null
+++ b/test/input/func_i0010.py
@@ -0,0 +1,3 @@
+# pylint: disable-all
+"""disable-all is not usable as an inline option"""
+__revision__ = None
diff --git a/test/input/func_i0011.py b/test/input/func_i0011.py
new file mode 100644
index 000000000..d8202f30e
--- /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 000000000..bf521386b
--- /dev/null
+++ b/test/input/func_i0012.py
@@ -0,0 +1,5 @@
+# pylint:enable-msg=W0404
+"""check warning on local enabling
+"""
+__revision__ = 1
+
diff --git a/test/input/func_indent.py b/test/input/func_indent.py
new file mode 100644
index 000000000..57aae6ea3
--- /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 000000000..9d8950526
--- /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 000000000..5081476b7
--- /dev/null
+++ b/test/input/func_interfaces.py
@@ -0,0 +1,99 @@
+# pylint:disable-msg=R0201
+"""docstring"""
+__revision__ = ''
+
+class Interface:
+ """base class for interfaces"""
+
+class IMachin(Interface):
+ """docstring"""
+ def truc(self):
+ """docstring"""
+
+ def troc(self, argument):
+ """docstring"""
+
+class Correct1:
+ """docstring"""
+ __implements__ = IMachin
+
+ def __init__(self):
+ pass
+
+ def truc(self):
+ """docstring"""
+ pass
+
+ def troc(self, argument):
+ """docstring"""
+ pass
+
+class Correct2:
+ """docstring"""
+ __implements__ = (IMachin,)
+
+ def __init__(self):
+ pass
+
+ def truc(self):
+ """docstring"""
+ pass
+
+ def troc(self, argument):
+ """docstring"""
+ print argument
+
+class MissingMethod:
+ """docstring"""
+ __implements__ = IMachin,
+
+ def __init__(self):
+ pass
+
+ def troc(self, argument):
+ """docstring"""
+ print argument
+
+ def other(self):
+ """docstring"""
+
+class BadArgument:
+ """docstring"""
+ __implements__ = (IMachin,)
+
+ def __init__(self):
+ pass
+
+ def truc(self):
+ """docstring"""
+ pass
+
+ def troc(self):
+ """docstring"""
+ pass
+
+class InterfaceCantBeFound:
+ """docstring"""
+ __implements__ = undefined
+
+ def __init__(self):
+ """only to make pylint happier"""
+
+ def please(self):
+ """public method 1/2"""
+
+ def besilent(self):
+ """public method 2/2"""
+
+class InterfaceCantBeFound2:
+ """docstring"""
+ __implements__ = BadArgument.__implements__ + Correct2.__implements__
+
+ def __init__(self):
+ """only to make pylint happier"""
+
+ def please(self):
+ """public method 1/2"""
+
+ def besilent(self):
+ """public method 2/2"""
diff --git a/test/input/func_method_could_be_function.py b/test/input/func_method_could_be_function.py
new file mode 100644
index 000000000..5988b6845
--- /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 000000000..5bdbc6940
--- /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 000000000..e39266c8f
--- /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 000000000..be7b5c82a
--- /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 000000000..caf738760
--- /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 000000000..78909c082
--- /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 000000000..9a9cccdc9
--- /dev/null
+++ b/test/input/func_newstyle_exceptions.py
@@ -0,0 +1,35 @@
+# pylint: disable-msg=C0103
+"""test pb with exceptions and old/new style classes"""
+
+__revision__ = 1
+
+class OkException(Exception):
+ """bien bien bien"""
+
+class BofException:
+ """mouais"""
+
+class NewException(object):
+ """non !"""
+
+def fonctionOk():
+ """raise"""
+ raise OkException('hop')
+
+def fonctionBof():
+ """raise"""
+ raise BofException('hop')
+
+def fonctionNew():
+ """raise"""
+ raise NewException()
+
+def fonctionBof2():
+ """raise"""
+ raise BofException, 'hop'
+
+def fonctionNew2():
+ """raise"""
+ raise NewException
+
+
diff --git a/test/input/func_newstyle_property.py b/test/input/func_newstyle_property.py
new file mode 100644
index 000000000..cdce8facf
--- /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 000000000..e84c908d3
--- /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 000000000..5c77516ba
--- /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 000000000..30b2671df
--- /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 000000000..048d9384f
--- /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 000000000..d83ebbc05
--- /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 000000000..4d6ba0aec
--- /dev/null
+++ b/test/input/func_noerror_builtin_module_test.py
@@ -0,0 +1,11 @@
+"""test import from a builtin module"""
+
+__revision__ = None
+
+from math import log10
+
+def log10_2():
+ """bla bla bla"""
+ return log10(2)
+
+
diff --git a/test/input/func_noerror_defined_and_used_on_same_line.py b/test/input/func_noerror_defined_and_used_on_same_line.py
new file mode 100644
index 000000000..87d3180e4
--- /dev/null
+++ b/test/input/func_noerror_defined_and_used_on_same_line.py
@@ -0,0 +1,20 @@
+#pylint: disable-msg=C0111,C0321
+"""pylint complains about 'index' being used before definition"""
+
+__revision__ = None
+
+print [index
+ for index in range(10)]
+
+
+FILTER_FUNC = lambda x: not x
+
+def func(xxx): return xxx
+
+def func2(xxx): return xxx + func2(1)
+
+import sys; print sys.exc_info( )
+
+for i in range(10): print i
+
+j = 4; LAMB = lambda x: x+j
diff --git a/test/input/func_noerror_defined_and_used_on_same_line_py24.py b/test/input/func_noerror_defined_and_used_on_same_line_py24.py
new file mode 100644
index 000000000..8032a8f60
--- /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 000000000..b247b4412
--- /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 000000000..143ddc06e
--- /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 000000000..2e945a5e4
--- /dev/null
+++ b/test/input/func_noerror_encoding.py
@@ -0,0 +1,6 @@
+# -*- coding: ISO-8859-1 -*-
+""" check correct encoding declaration
+"""
+
+__revision__ = 'éééé'
+
diff --git a/test/input/func_noerror_exception.py b/test/input/func_noerror_exception.py
new file mode 100644
index 000000000..1c3d8b56a
--- /dev/null
+++ b/test/input/func_noerror_exception.py
@@ -0,0 +1,7 @@
+""" module doc """
+__revision__ = ''
+
+class MyException(Exception):
+ """a custom exception with its *own* __init__ !!"""
+ def __init__(self, msg):
+ Exception.__init__(self, msg)
diff --git a/test/input/func_noerror_indirect_interface.py b/test/input/func_noerror_indirect_interface.py
new file mode 100644
index 000000000..2b240f970
--- /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 000000000..a1a848dec
--- /dev/null
+++ b/test/input/func_noerror_inner_classes.py
@@ -0,0 +1,33 @@
+# pylint: disable-msg=R0903
+"""Backend Base Classes for the schwelm user DB"""
+
+__revision__ = "alpha"
+
+class Aaa(object):
+ """docstring"""
+ def __init__(self):
+ self.__setattr__('a','b')
+ pass
+
+ def one_public(self):
+ """docstring"""
+ pass
+
+ def another_public(self):
+ """docstring"""
+ pass
+
+class Bbb(Aaa):
+ """docstring"""
+ pass
+
+class Ccc(Aaa):
+ """docstring"""
+
+ class Ddd(Aaa):
+ """docstring"""
+ pass
+
+ class Eee(Ddd):
+ """docstring"""
+ pass
diff --git a/test/input/func_noerror_mcs_attr_access.py b/test/input/func_noerror_mcs_attr_access.py
new file mode 100644
index 000000000..c42c0619e
--- /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 000000000..96cd36654
--- /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 000000000..8c9ed4016
--- /dev/null
+++ b/test/input/func_noerror_new_style_class.py
@@ -0,0 +1,45 @@
+"""check builtin data descriptors such as mode and name attributes
+on a file are correctly handler
+
+bug notified by Pierre Rouleau on 2005-04-24
+"""
+
+__revision__ = None
+
+class File(file):
+ """ Testing new-style class inheritance from file"""
+
+ #
+ def __init__(self, name, mode="r", buffering=-1, verbose=False):
+ """Constructor"""
+
+ self.was_modified = False
+ self.verbose = verbose
+ super(File, self).__init__(name, mode, buffering)
+ if self.verbose:
+ print "File %s is opened. The mode is: %s" % (self.name,
+self.mode)
+
+ #
+ def write(self, a_string):
+ """ Write a string to the file."""
+
+ super(File, self).write(a_string)
+ self.was_modified = True
+
+ #
+ def writelines(self, sequence):
+ """ Write a sequence of strings to the file. """
+
+ super(File, self).writelines(sequence)
+ self.was_modified = True
+
+ #
+ def close(self) :
+ """Close the file."""
+
+ if self.verbose:
+ print "Closing file %s" % self.name
+
+ super(File, self).close()
+ self.was_modified = False
diff --git a/test/input/func_noerror_object_as_class_attribute.py b/test/input/func_noerror_object_as_class_attribute.py
new file mode 100644
index 000000000..6b442ea52
--- /dev/null
+++ b/test/input/func_noerror_object_as_class_attribute.py
@@ -0,0 +1,19 @@
+# pylint: disable-msg=R0903
+"""Test case for the problem described below :
+ - A class extends 'object'
+ - This class defines its own __init__()
+ * pylint will therefore check that baseclasses' init()
+ are called
+ - If this class defines an 'object' attribute, then pylint
+ will use this new definition when trying to retrieve
+ object.__init__()
+"""
+
+__revision__ = None
+
+class Statement(object):
+ """ ... """
+ def __init__(self):
+ pass
+ object = None
+
diff --git a/test/input/func_noerror_socket_member.py b/test/input/func_noerror_socket_member.py
new file mode 100644
index 000000000..8c79ff579
--- /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 000000000..618496109
--- /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 000000000..e5a44f26c
--- /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 000000000..f4c4232a4
--- /dev/null
+++ b/test/input/func_noerror_w0232.py
@@ -0,0 +1,10 @@
+# pylint: disable-msg=R0903,R0923
+"""check interface and exception without __init__ doesn't print warnings
+"""
+__revision__ = ''
+
+class Interface:
+ """interface without docstring"""
+
+class MyError(Exception):
+ """exception without docstring"""
diff --git a/test/input/func_nonascii_noencoding.py b/test/input/func_nonascii_noencoding.py
new file mode 100644
index 000000000..1ba3578de
--- /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 000000000..d30d2706f
--- /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 000000000..59d4100c5
--- /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 000000000..4c119299c
--- /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 000000000..abb4d06ea
--- /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 000000000..b9f2de2ba
--- /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 000000000..da7dfcf22
--- /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 000000000..8dacf1e44
--- /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 000000000..fb1e2b648
--- /dev/null
+++ b/test/input/func_reqattrs.py
@@ -0,0 +1 @@
+"""docstring"""
diff --git a/test/input/func_scope_regrtest.py b/test/input/func_scope_regrtest.py
new file mode 100644
index 000000000..98dd8be49
--- /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 000000000..43fa08752
--- /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 000000000..76a401874
--- /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 000000000..82130e347
--- /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 000000000..dc5029247
--- /dev/null
+++ b/test/input/func_typecheck_getattr.py
@@ -0,0 +1,60 @@
+# pylint: disable-msg=
+"""check getattr if inference succeed"""
+
+__revision__ = None
+
+class Provider:
+ """provide some attributes and method"""
+ cattr = 4
+ def __init__(self):
+ self.attr = 4
+ def method(self, val):
+ """impressive method"""
+ return self.attr * val
+ def hophop(self):
+ """hop method"""
+ print 'hop hop hop', self
+
+
+class Client:
+ """use provider class"""
+
+ def __init__(self):
+ self._prov = Provider()
+ self._prov_attr = Provider.cattr
+ self._prov_attr2 = Provider.cattribute
+
+ def use_method(self):
+ """use provider's method"""
+ self._prov.hophop()
+ self._prov.hophophop()
+
+ def use_attr(self):
+ """use provider's attr"""
+ print self._prov.attr
+ print self._prov.attribute
+
+ def debug(self):
+ """print debug information"""
+ print self.__class__.__name__
+ print self.__doc__
+ print self.__dict__
+ print self.__module__
+
+ def test_bt_types(self):
+ """test access to unexistant member of builtin types"""
+ lis = []
+ lis.apppend(self)
+ dic = {}
+ dic.set(self)
+ tup = ()
+ tup.append(self)
+ string = 'toto'
+ print string.loower()
+ uni = u'toto'
+ print uni.loower()
+ integer = 1
+ print integer.whatever
+
+print object.__init__
+print property.__init__
diff --git a/test/input/func_typecheck_non_callable_call.py b/test/input/func_typecheck_non_callable_call.py
new file mode 100644
index 000000000..75b122413
--- /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 000000000..316626058
--- /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 000000000..31deabdfb
--- /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 000000000..1cabce718
--- /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 000000000..b0db336d4
--- /dev/null
+++ b/test/input/func_use_for_or_listcomp_var.py
@@ -0,0 +1,21 @@
+"""test a warning is triggered when using for a lists comprehension variable"""
+
+__revision__ = 'yo'
+
+TEST_LC = [C for C in __revision__ if C.isalpha()]
+print C # WARN
+C = 4
+print C # this one shouldn't trigger any warning
+
+B = [B for B in __revision__ if B.isalpha()]
+print B # nor this one
+
+for var1, var2 in TEST_LC:
+ var1 = var2 + 4
+print var1 # WARN
+
+for note in __revision__:
+ note.something()
+for line in __revision__:
+ for note in line:
+ A = note.anotherthing()
diff --git a/test/input/func_w0101.py b/test/input/func_w0101.py
new file mode 100644
index 000000000..fe543aabe
--- /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 000000000..b096fffac
--- /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 000000000..9417cb55c
--- /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 000000000..bddf00123
--- /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 000000000..430cfd4f3
--- /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 000000000..ba7a679d2
--- /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 000000000..c9d93115c
--- /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 000000000..f8ad440a3
--- /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 000000000..5cd01ae34
--- /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 000000000..178c2521e
--- /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 000000000..c0304ec3c
--- /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 000000000..4c5544669
--- /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 000000000..5054d9072
--- /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 000000000..4d36d40da
--- /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 000000000..277306ac2
--- /dev/null
+++ b/test/input/func_w0205.py
@@ -0,0 +1,24 @@
+"""check different signatures"""
+
+__revision__ = 0
+
+class Abcd:
+ '''dummy'''
+ def __init__(self):
+ self.aarg = False
+ def abcd(self, aaa, bbbb=None):
+ """hehehe"""
+ print self, aaa, bbbb
+ def args(self):
+ """gaarrrggll"""
+ self.aarg = True
+
+class Cdef(Abcd):
+ """dummy"""
+ def __init__(self, aaa):
+ Abcd.__init__(self)
+ self.aaa = aaa
+
+ def abcd(self, aaa=1, bbbb=None):
+ """hehehe"""
+ print self, aaa, bbbb
diff --git a/test/input/func_w0223.py b/test/input/func_w0223.py
new file mode 100644
index 000000000..369686218
--- /dev/null
+++ b/test/input/func_w0223.py
@@ -0,0 +1,27 @@
+# pylint: disable-msg=R0903,R0922
+"""test overriding of abstract method
+"""
+
+__revision__ = '$Id: func_w0223.py,v 1.2 2004-09-29 08:35:13 syt Exp $'
+
+class Abstract:
+ """abstract class
+ """
+ def aaaa(self):
+ """should be overriden in concrete class"""
+ raise NotImplementedError()
+
+
+ def bbbb(self):
+ """should be overriden in concrete class"""
+ raise NotImplementedError()
+
+ def __init__(self):
+ pass
+
+class Concret(Abstract):
+ """concret class"""
+
+ def aaaa(self):
+ """overidden form Abstract"""
+ print self
diff --git a/test/input/func_w0231.py b/test/input/func_w0231.py
new file mode 100644
index 000000000..3f592ec78
--- /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 000000000..763ccf0aa
--- /dev/null
+++ b/test/input/func_w0233.py
@@ -0,0 +1,19 @@
+# pylint: disable-msg=R0903
+"""test for call to __init__ from a non ancestor class
+"""
+
+__revision__ = '$Id: func_w0233.py,v 1.2 2004-09-29 08:35:13 syt Exp $'
+
+class AAAA:
+ """ancestor 1"""
+
+ def __init__(self):
+ print 'init', self
+ BBBB.__init__(self)
+
+class BBBB:
+ """ancestor 2"""
+
+ def __init__(self):
+ print 'init', self
+
diff --git a/test/input/func_w0302.py b/test/input/func_w0302.py
new file mode 100644
index 000000000..a78f479a6
--- /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 000000000..06a811023
--- /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 000000000..7e3452335
--- /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 000000000..ff078efc5
--- /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 000000000..6b7a64b64
--- /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 000000000..53752db45
--- /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 000000000..72fb79502
--- /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 000000000..fd38e6a58
--- /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 000000000..745c615cb
--- /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 000000000..e20508f5d
--- /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 000000000..2c3796699
--- /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 000000000..43b551c4f
--- /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 000000000..0725335e3
--- /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 000000000..ea9d1b2c0
--- /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 000000000..9c1b7273c
--- /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 000000000..38a6417e0
--- /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 000000000..4040540b5
--- /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 000000000..e8c643284
--- /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 000000000..898e2b8f0
--- /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 000000000..cd386ff55
--- /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 000000000..267fa2c90
--- /dev/null
+++ b/test/input/func_wrong_encoding.py
@@ -0,0 +1,6 @@
+# -*- coding: UTF-8 -*-
+""" check correct wrong encoding declaration
+"""
+
+__revision__ = 'éééé'
+
diff --git a/test/input/indirect1.py b/test/input/indirect1.py
new file mode 100644
index 000000000..eac6242b8
--- /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 000000000..6eefece60
--- /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 000000000..dac085398
--- /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 000000000..8aeda06d3
--- /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 000000000..bc881871c
--- /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 000000000..56f9844fd
--- /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 000000000..1fbf88060
--- /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 000000000..cd386ff55
--- /dev/null
+++ b/test/input/w0801_same.py
@@ -0,0 +1,11 @@
+"""test code similarities
+by defaut docstring are not considered
+"""
+__revision__ = 'id'
+A = 2
+B = 3
+C = A + B
+# need more than X lines to trigger the message
+C *= 2
+A -= B
+# all this should be detected
diff --git a/test/messages/builtin_module.txt b/test/messages/builtin_module.txt
new file mode 100644
index 000000000..88293186d
--- /dev/null
+++ b/test/messages/builtin_module.txt
@@ -0,0 +1 @@
+F: 0: ignored builtin module sys
diff --git a/test/messages/func___future___import_not_first_stmt.txt b/test/messages/func___future___import_not_first_stmt.txt
new file mode 100644
index 000000000..ff96b5549
--- /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 000000000..51aeeaa3d
--- /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 000000000..895c315fb
--- /dev/null
+++ b/test/messages/func_attrs_definition_order.txt
@@ -0,0 +1 @@
+E: 9:Aaaa.__init__: Access to member '_var2' before its definition line 10
diff --git a/test/messages/func_bad_assigment_to_exception_var.txt b/test/messages/func_bad_assigment_to_exception_var.txt
new file mode 100644
index 000000000..9b9bc027a
--- /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 000000000..b50fd8225
--- /dev/null
+++ b/test/messages/func_base_stmt_without_effect.txt
@@ -0,0 +1,3 @@
+W: 9: Statement seems to have no effect
+W: 11: Statement seems to have no effect
+W: 15: Statement seems to have no effect
diff --git a/test/messages/func_block_disable_msg.txt b/test/messages/func_block_disable_msg.txt
new file mode 100644
index 000000000..0019f0373
--- /dev/null
+++ b/test/messages/func_block_disable_msg.txt
@@ -0,0 +1,9 @@
+E: 26:Foo.meth3: Instance of 'Foo' has no 'blop' member
+E: 36:Foo.meth4: Instance of 'Foo' has no 'blip' member
+E: 46:Foo.meth5: Instance of 'Foo' has no 'blip' member
+E: 61:Foo.meth6: Instance of 'Foo' has no 'blip' member
+E: 72:Foo.meth7: Instance of 'Foo' has no 'blip' member
+E: 75:Foo.meth7: Instance of 'Foo' has no 'blip' member
+E: 77:Foo.meth7: Instance of 'Foo' has no 'blip' member
+E: 83:Foo.meth8: Instance of 'Foo' has no 'blip' member
+W: 11:Foo.meth1: Unused argument 'arg'
diff --git a/test/messages/func_class_members.txt b/test/messages/func_class_members.txt
new file mode 100644
index 000000000..ba2c639a7
--- /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 000000000..d3a318309
--- /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 000000000..21d32e022
--- /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 000000000..d37939207
--- /dev/null
+++ b/test/messages/func_docstring.txt
@@ -0,0 +1,4 @@
+C: 0: Missing docstring
+C: 5:function1: Missing docstring
+C: 17:AAAA: Missing docstring
+C: 33:AAAA.method1: Missing docstring
diff --git a/test/messages/func_dotted_ancestor.txt b/test/messages/func_dotted_ancestor.txt
new file mode 100644
index 000000000..84247b86a
--- /dev/null
+++ b/test/messages/func_dotted_ancestor.txt
@@ -0,0 +1 @@
+R: 8:Aaaa: Not enough public methods (0/2)
diff --git a/test/messages/func_e0011.txt b/test/messages/func_e0011.txt
new file mode 100644
index 000000000..55f07b1d4
--- /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 000000000..a6d1b698c
--- /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 000000000..53d007d4a
--- /dev/null
+++ b/test/messages/func_e0101.txt
@@ -0,0 +1 @@
+E: 10:MyClass.__init__: Explicit return in __init__
diff --git a/test/messages/func_e0203.txt b/test/messages/func_e0203.txt
new file mode 100644
index 000000000..869cf22d8
--- /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 000000000..8e05efea5
--- /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 000000000..1adf408c8
--- /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 000000000..c15f841b7
--- /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 000000000..e3fb5f5b7
--- /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 000000000..321c7316b
--- /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 000000000..2a8190d4a
--- /dev/null
+++ b/test/messages/func_empty_module.txt
@@ -0,0 +1,2 @@
+C: 0: Missing docstring
+C: 0: Missing required attribute "__revision__"
diff --git a/test/messages/func_exceptions_raise_type_error.txt b/test/messages/func_exceptions_raise_type_error.txt
new file mode 100644
index 000000000..8ac57782e
--- /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 000000000..87c315d27
--- /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 000000000..1ae5ea469
--- /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 000000000..2544ce8f4
--- /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 000000000..c87d01dcd
--- /dev/null
+++ b/test/messages/func_format.txt
@@ -0,0 +1,25 @@
+C: 6: Operator not preceded by a space
+notpreceded= 1
+ ^
+C: 7: Operator not followed by a space
+notfollowed =1
+ ^
+C: 8: Operator not followed by a space
+notfollowed <=1
+ ^^
+C: 19: Comma not followed by a space
+aaaa,bbbb = 1,2
+ ^^
+C: 24: More than one statement on a single line
+C: 26: Comma not followed by a space
+ aaaa,bbbb = 1,2
+ ^^
+C: 27: Comma not followed by a space
+ aaaa,bbbb = bbbb,aaaa
+ ^^
+C: 29: Comma not followed by a space
+bbbb = (1,2,3)
+ ^^
+C: 51:other: Operator not preceded by a space
+ funky= funky+2
+ ^
diff --git a/test/messages/func_globals.txt b/test/messages/func_globals.txt
new file mode 100644
index 000000000..40963f288
--- /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 000000000..315f701f1
--- /dev/null
+++ b/test/messages/func_i0010.txt
@@ -0,0 +1 @@
+I: 1: Unable to consider inline option 'disable-all'
diff --git a/test/messages/func_i0011.txt b/test/messages/func_i0011.txt
new file mode 100644
index 000000000..e4d29751e
--- /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 000000000..0bb035c1d
--- /dev/null
+++ b/test/messages/func_i0012.txt
@@ -0,0 +1,2 @@
+I: 1: Locally enabling 'W0404'
+
diff --git a/test/messages/func_indent.txt b/test/messages/func_indent.txt
new file mode 100644
index 000000000..aa3645f49
--- /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 000000000..44ef6f314
--- /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 000000000..546acddca
--- /dev/null
+++ b/test/messages/func_interfaces.txt
@@ -0,0 +1,5 @@
+E: 46:MissingMethod: Missing method 'truc' from IMachin interface
+E: 77:InterfaceCantBeFound: Undefined variable 'undefined'
+F: 77:InterfaceCantBeFound: failed to resolve interfaces implemented by InterfaceCantBeFound (undefined)
+F: 90:InterfaceCantBeFound2: failed to resolve interfaces implemented by InterfaceCantBeFound2 ((BadArgument.__implements__) + (Correct2.__implements__))
+W: 71:BadArgument.troc: Arguments number differs from IMachin interface method
diff --git a/test/messages/func_method_could_be_function.txt b/test/messages/func_method_could_be_function.txt
new file mode 100644
index 000000000..1def89e7f
--- /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 000000000..861871f44
--- /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 000000000..da5ee0234
--- /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 000000000..aab21024b
--- /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 000000000..62b0344f2
--- /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 000000000..be6b8d10c
--- /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 000000000..d92e3bf25
--- /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 000000000..f0ef26145
--- /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 000000000..d0cdf78c2
--- /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 000000000..dd68a3c62
--- /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 000000000..d20460e22
--- /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 000000000..5dcb66925
--- /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 000000000..6ab8888cc
--- /dev/null
+++ b/test/messages/func_r0903.txt
@@ -0,0 +1 @@
+R: 4:Aaaa: Not enough public methods (1/2)
diff --git a/test/messages/func_r0904.txt b/test/messages/func_r0904.txt
new file mode 100644
index 000000000..76baf721a
--- /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 000000000..7e9a44286
--- /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 000000000..70319ee80
--- /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 000000000..11ee61d4b
--- /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 000000000..7e99095a4
--- /dev/null
+++ b/test/messages/func_reqattrs.txt
@@ -0,0 +1 @@
+C: 0: Missing required attribute "__revision__"
diff --git a/test/messages/func_scope_regrtest.txt b/test/messages/func_scope_regrtest.txt
new file mode 100644
index 000000000..27ae2079d
--- /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 000000000..deee535f2
--- /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 000000000..29b16c695
--- /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 000000000..96ad43e4c
--- /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 000000000..6aa34d873
--- /dev/null
+++ b/test/messages/func_typecheck_getattr.txt
@@ -0,0 +1,9 @@
+E: 25:Client.__init__: Class 'Provider' has no 'cattribute' member
+E: 30:Client.use_method: Instance of 'Provider' has no 'hophophop' member
+E: 35:Client.use_attr: Instance of 'Provider' has no 'attribute' member
+E: 47:Client.test_bt_types: Instance of 'list' has no 'apppend' member
+E: 49:Client.test_bt_types: Instance of 'dict' has no 'set' member
+E: 51:Client.test_bt_types: Instance of 'tuple' has no 'append' member
+E: 53:Client.test_bt_types: Instance of 'str' has no 'loower' member
+E: 55:Client.test_bt_types: Instance of 'unicode' has no 'loower' member
+E: 57:Client.test_bt_types: Instance of 'int' has no 'whatever' member \ No newline at end of file
diff --git a/test/messages/func_typecheck_non_callable_call.txt b/test/messages/func_typecheck_non_callable_call.txt
new file mode 100644
index 000000000..0218074ae
--- /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 000000000..07666f774
--- /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 000000000..648af39fc
--- /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 000000000..bb25be0d9
--- /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 000000000..14d61ad65
--- /dev/null
+++ b/test/messages/func_use_for_or_listcomp_var.txt
@@ -0,0 +1,3 @@
+W: 6: Using possibly undefined loop variable 'C'
+W: 15: Using possibly undefined loop variable 'var1'
+
diff --git a/test/messages/func_w0101.txt b/test/messages/func_w0101.txt
new file mode 100644
index 000000000..c42ec2ce9
--- /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 000000000..40b619065
--- /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 000000000..0d6da4250
--- /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 000000000..71f6f6229
--- /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 000000000..d664dd499
--- /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 000000000..4685d382c
--- /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 000000000..28f48aab0
--- /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 000000000..b2d794bf3
--- /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 000000000..19b2da5d0
--- /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 000000000..1522cac30
--- /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 000000000..f2929ae83
--- /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 000000000..dae33d9f9
--- /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 000000000..ddf2d0ba6
--- /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 000000000..d10e5bc89
--- /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 000000000..5edbd0a06
--- /dev/null
+++ b/test/messages/func_w0205.txt
@@ -0,0 +1,2 @@
+W: 22:Cdef.abcd: Signature differs from overriden method
+
diff --git a/test/messages/func_w0223.txt b/test/messages/func_w0223.txt
new file mode 100644
index 000000000..ece889b23
--- /dev/null
+++ b/test/messages/func_w0223.txt
@@ -0,0 +1,2 @@
+W: 22:Concret: Method 'bbbb' is abstract in class 'Abstract' but is not overriden
+
diff --git a/test/messages/func_w0231.txt b/test/messages/func_w0231.txt
new file mode 100644
index 000000000..84e176d87
--- /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 000000000..9857aa688
--- /dev/null
+++ b/test/messages/func_w0233.txt
@@ -0,0 +1 @@
+W: 12:AAAA.__init__: __init__ method from a non direct base class 'BBBB' is called
diff --git a/test/messages/func_w0302.txt b/test/messages/func_w0302.txt
new file mode 100644
index 000000000..ae8a99f68
--- /dev/null
+++ b/test/messages/func_w0302.txt
@@ -0,0 +1,2 @@
+W: 0: Too many lines in module (1017)
+
diff --git a/test/messages/func_w0312.txt b/test/messages/func_w0312.txt
new file mode 100644
index 000000000..917e8d0e2
--- /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 000000000..8134a320e
--- /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 000000000..b8984ad05
--- /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 000000000..9463727f6
--- /dev/null
+++ b/test/messages/func_w0401.txt
@@ -0,0 +1,2 @@
+R: 0: Cyclic import (input.func_w0401 -> input.w0401_cycle)
+W: 6: Redefining built-in 'input'
diff --git a/test/messages/func_w0402.txt b/test/messages/func_w0402.txt
new file mode 100644
index 000000000..cf06fc44f
--- /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 000000000..ef511f79d
--- /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 000000000..311056d5b
--- /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 000000000..9555dfd1c
--- /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 000000000..a1440b92b
--- /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 000000000..10952da05
--- /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 000000000..b2d9cbb83
--- /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 000000000..b658b540a
--- /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 000000000..719134726
--- /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 000000000..677443bec
--- /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 000000000..ab6d20a6b
--- /dev/null
+++ b/test/messages/func_w0702.txt
@@ -0,0 +1 @@
+W: 10: No exception's type specified
diff --git a/test/messages/func_w0703.txt b/test/messages/func_w0703.txt
new file mode 100644
index 000000000..e37197af4
--- /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 000000000..1eca7949e
--- /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 000000000..303e73985
--- /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 000000000..c6ba23121
--- /dev/null
+++ b/test/messages/func_w0801.txt
@@ -0,0 +1,11 @@
+R: 0: Similar lines in 2 files
+==input.func_w0801:3
+==input.w0801_same:3
+__revision__ = 'id'
+A = 2
+B = 3
+C = A + B
+# need more than X lines to trigger the message
+C *= 2
+A -= B
+# all this should be detected
diff --git a/test/messages/func_wrong_encoding.txt b/test/messages/func_wrong_encoding.txt
new file mode 100644
index 000000000..10123a189
--- /dev/null
+++ b/test/messages/func_wrong_encoding.txt
@@ -0,0 +1 @@
+E: 1: Wrong encoding specified (UTF-8)
diff --git a/test/messages/nonexistant.txt b/test/messages/nonexistant.txt
new file mode 100644
index 000000000..0ccfb570a
--- /dev/null
+++ b/test/messages/nonexistant.txt
@@ -0,0 +1 @@
+F: 0: No module named nonexistant
diff --git a/test/messages/nonexistant.txt2 b/test/messages/nonexistant.txt2
new file mode 100644
index 000000000..ffe19c546
--- /dev/null
+++ b/test/messages/nonexistant.txt2
@@ -0,0 +1 @@
+F: 0: No module named input/nonexistant
diff --git a/test/regrtest.py b/test/regrtest.py
new file mode 100644
index 000000000..927ab48bc
--- /dev/null
+++ b/test/regrtest.py
@@ -0,0 +1,131 @@
+# Copyright (c) 2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""non regression tests for pylint, which requires a too specific configuration
+to be incorporated in the automatic functionnal test framework
+"""
+__revision__ = '$Id: regrtest.py,v 1.12 2006-03-03 09:25:34 syt Exp $'
+
+import sys
+import os
+import unittest
+from os.path import abspath, join
+
+from logilab.common import testlib
+
+from utils import TestReporter
+
+from pylint.lint import PyLinter
+from pylint import checkers
+
+test_reporter = TestReporter()
+linter = PyLinter()
+linter.set_reporter(test_reporter)
+linter.disable_message_category('I')
+linter.config.persistent = 0
+linter.quiet = 1
+checkers.initialize(linter)
+
+sys.path.insert(1, abspath('regrtest_data'))
+
+class NonRegrTC(testlib.TestCase):
+ def setUp(self):
+ """call reporter.finalize() to cleanup
+ pending messages if a test finished badly
+ """
+ linter.reporter.finalize()
+
+ def test_package___path___manipulation(self):
+ linter.check('package.__init__')
+ got = linter.reporter.finalize().strip()
+ self.failUnlessEqual(got, '')
+
+ def test_package___init___precedence(self):
+ linter.check('precedence_test')
+ got = linter.reporter.finalize().strip()
+ self.failUnlessEqual(got, '')
+
+ def test_check_package___init__(self):
+ for variation in ('package.__init__', 'regrtest_data/package/__init__.py'):
+ linter.check(variation)
+ got = linter.reporter.finalize().strip()
+ checked = linter.stats['by_module'].keys()
+ self.failUnlessEqual(checked, ['package.__init__'],
+ '%s: %s' % (variation, checked))
+ cwd = os.getcwd()
+ os.chdir('regrtest_data/package')
+ sys.path.insert(0, '')
+ try:
+ for variation in ('__init__', '__init__.py'):
+ linter.check(variation)
+ got = linter.reporter.finalize().strip()
+ checked = linter.stats['by_module'].keys()
+ self.failUnlessEqual(checked, ['__init__'],
+ '%s: %s' % (variation, checked))
+ finally:
+ sys.path.pop(0)
+ os.chdir(cwd)
+
+ def test_gtk_import(self):
+ try:
+ import gtk
+ except ImportError:
+ self.skip('test skipped: gtk is not available')
+ except RuntimeError: # RuntimeError when missing display
+ pass
+ linter.check('regrtest_data/pygtk_import.py')
+ got = linter.reporter.finalize().strip()
+ self.failUnlessEqual(got, '')
+
+
+ def test_numarray_import(self):
+ try:
+ import numarray
+ except ImportError:
+ self.skip('test skipped: numarray is not available')
+ linter.check('regrtest_data/numarray_import.py')
+ got = linter.reporter.finalize().strip()
+ self.failUnlessEqual(got, '')
+
+ def test_socketerror_import(self):
+ linter.check('regrtest_data/socketerror_import.py')
+ got = linter.reporter.finalize().strip()
+ self.failUnlessEqual(got, '')
+
+ def test_class__doc__usage(self):
+ linter.check('regrtest_data/classdoc_usage.py')
+ got = linter.reporter.finalize().strip()
+ self.failUnlessEqual(got, '')
+
+ def test_package_import_relative_subpackage_no_attribute_error(self):
+ linter.check('import_package_subpackage_module')
+ got = linter.reporter.finalize().strip()
+ self.failUnlessEqual(got, '')
+
+ def test_module_global_crash(self):
+ linter.check('regrtest_data/module_global.py')
+ got = linter.reporter.finalize().strip()
+ self.failUnlessEqual(got, '')
+
+ def test_descriptor_crash(self):
+ for fname in os.listdir('regrtest_data'):
+ if fname.endswith('_crash.py'):
+ linter.check(join('regrtest_data', fname))
+ linter.reporter.finalize().strip()
+
+
+if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
+ unittest_main()
diff --git a/test/regrtest_data/application_crash.py b/test/regrtest_data/application_crash.py
new file mode 100644
index 000000000..6e6044aab
--- /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 000000000..ae8b9fe3f
--- /dev/null
+++ b/test/regrtest_data/classdoc_usage.py
@@ -0,0 +1,17 @@
+"""ds"""
+
+__revision__ = None
+
+class SomeClass:
+ """cds"""
+ doc = __doc__
+
+ def __init__(self):
+ """only to make pylint happier"""
+
+ def please(self):
+ """public method 1/2"""
+
+ def besilent(self):
+ """public method 2/2"""
+
diff --git a/test/regrtest_data/descriptor_crash.py b/test/regrtest_data/descriptor_crash.py
new file mode 100644
index 000000000..4b3adccde
--- /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 000000000..b5fb219d1
--- /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 000000000..54fd46d33
--- /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 000000000..3f0be8b7b
--- /dev/null
+++ b/test/regrtest_data/numarray_import.py
@@ -0,0 +1,7 @@
+"""#10077"""
+
+__revision__ = 1
+
+from numarray import zeros
+
+zeros()
diff --git a/test/regrtest_data/package/AudioTime.py b/test/regrtest_data/package/AudioTime.py
new file mode 100644
index 000000000..a1fde9650
--- /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 000000000..9e1ebfa68
--- /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 000000000..dc4782e6c
--- /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 000000000..4b7244ba0
--- /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 000000000..087b5cf4d
--- /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 000000000..4ac95606f
--- /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 000000000..37310cfa6
--- /dev/null
+++ b/test/regrtest_data/socketerror_import.py
@@ -0,0 +1,6 @@
+"""ds"""
+
+__revision__ = '$Id: socketerror_import.py,v 1.2 2005-12-28 14:58:22 syt Exp $'
+
+from socket import error
+print error
diff --git a/test/runtests.py b/test/runtests.py
new file mode 100644
index 000000000..67d66367b
--- /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 000000000..1954466c1
--- /dev/null
+++ b/test/smoketest.py
@@ -0,0 +1,74 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__revision__ = "$Id: smoketest.py,v 1.6 2005-04-15 10:40:24 syt Exp $"
+
+import unittest
+import sys
+from cStringIO import StringIO
+
+from pylint.lint import Run
+from pylint.reporters.text import TextReporter, TextReporter2, ColorizedTextReporter
+from pylint.reporters.html import HTMLReporter
+
+
+class LintSmokeTest(unittest.TestCase):
+
+ def test1(self):
+ """make pylint checking itself"""
+ Run(['--include-ids=y', 'pylint'], reporter=TextReporter(StringIO()), quiet=1)
+
+ def test2(self):
+ """make pylint checking itself"""
+ Run(['pylint.lint'], reporter=TextReporter2(StringIO()), quiet=1)
+
+ def test3(self):
+ """make pylint checking itself"""
+ Run(['pylint.checkers'], reporter=HTMLReporter(StringIO()), quiet=1)
+
+ def test4(self):
+ """make pylint checking itself"""
+ Run(['pylint.checkers'], reporter=ColorizedTextReporter(StringIO()), quiet=1)
+
+ def test_generate_config_option(self):
+ """make pylint checking itself"""
+ sys.stdout = StringIO()
+ try:
+ self.assertRaises(SystemExit, Run,
+ ['--generate-rcfile'],
+ reporter=HTMLReporter(StringIO()),
+ quiet=1)
+ finally:
+ sys.stdout = sys.__stdout__
+
+ def test_help_message_option(self):
+ """make pylint checking itself"""
+ sys.stdout = StringIO()
+ try:
+ self.assertRaises(SystemExit, Run,
+ ['--help-msg', 'W0101'],
+ reporter=HTMLReporter(StringIO()),
+ quiet=1)
+ self.assertRaises(SystemExit, Run,
+ ['--help-msg', 'WX101'],
+ reporter=HTMLReporter(StringIO()),
+ quiet=1)
+ finally:
+ sys.stdout = sys.__stdout__
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/test_encoding.py b/test/test_encoding.py
new file mode 100644
index 000000000..ca5b53fcf
--- /dev/null
+++ b/test/test_encoding.py
@@ -0,0 +1,62 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+Check source code is ascii only or has an encoding declaration (PEP 263)
+"""
+
+__revision__ = '$Id: test_encoding.py,v 1.6 2005-11-02 09:22:04 syt Exp $'
+
+import unittest
+import sys
+from pylint.checkers.misc import guess_encoding
+
+class TestGuessEncoding(unittest.TestCase):
+
+ def testEmacs(self):
+ e = guess_encoding('# -*- coding: UTF-8 -*-')
+ self.failUnlessEqual(e, 'UTF-8')
+ e = guess_encoding('# -*- coding:UTF-8 -*-')
+ self.failUnlessEqual(e, 'UTF-8')
+ e = guess_encoding('''
+ ### -*- coding: ISO-8859-1 -*-
+ ''')
+ self.failUnlessEqual(e, 'ISO-8859-1')
+ e = guess_encoding('''
+
+ ### -*- coding: ISO-8859-1 -*-
+ ''')
+ self.failUnlessEqual(e, None)
+
+ def testVim(self):
+ e = guess_encoding('# vim:fileencoding=UTF-8')
+ self.failUnlessEqual(e, 'UTF-8')
+ e = guess_encoding('''
+ ### vim:fileencoding=ISO-8859-1
+ ''')
+ self.failUnlessEqual(e, 'ISO-8859-1')
+ e = guess_encoding('''
+
+ ### vim:fileencoding= ISO-8859-1
+ ''')
+ self.failUnlessEqual(e, None)
+
+ def testUTF8(self):
+ e = guess_encoding('\xef\xbb\xbf any UTF-8 data')
+ self.failUnlessEqual(e, 'UTF-8')
+ e = guess_encoding(' any UTF-8 data \xef\xbb\xbf')
+ self.failUnlessEqual(e, None)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/test_format.py b/test/test_format.py
new file mode 100644
index 000000000..94a499fa8
--- /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 000000000..dd6446ead
--- /dev/null
+++ b/test/test_import_graph.py
@@ -0,0 +1,63 @@
+import sys
+import os
+import unittest
+from os.path import exists
+from cStringIO import StringIO
+
+from pylint.checkers import initialize, imports
+from pylint.lint import PyLinter
+
+from utils import TestReporter
+
+class DependenciesGraphTC(unittest.TestCase):
+ """test the imports graph function"""
+
+ dest = 'dependencies_graph.dot'
+ def tearDown(self):
+ os.remove(self.dest)
+
+ def test_dependencies_graph(self):
+ imports.dependencies_graph(self.dest, {'labas': ['hoho', 'yep'],
+ 'hoho': ['yep']})
+ self.assertEquals(open(self.dest).read().strip(),
+ '''
+digraph g {
+rankdir="LR" URL="." concentrate=false
+edge[fontsize="10" ]
+node[width="0" height="0" fontsize="12" fontcolor="black"]
+"hoho" [ label="hoho" ];
+"yep" [ label="yep" ];
+"labas" [ label="labas" ];
+"yep" -> "hoho" [ ] ;
+"hoho" -> "labas" [ ] ;
+"yep" -> "labas" [ ] ;
+}
+'''.strip())
+
+class ImportCheckerTC(unittest.TestCase):
+ def setUp(self):
+ self.linter = l = PyLinter(reporter=TestReporter())
+ initialize(l)
+ l.disable_all_checkers()
+
+ def test_checker_dep_graphs(self):
+ l = self.linter
+ l.global_set_option('persistent', False)
+ l.global_set_option('enable-imports', True)
+ l.global_set_option('import-graph', 'import.dot')
+ l.global_set_option('ext-import-graph', 'ext_import.dot')
+ l.global_set_option('int-import-graph', 'int_import.dot')
+ try:
+ l.check('input')
+ self.assert_(exists('import.dot'))
+ self.assert_(exists('ext_import.dot'))
+ self.assert_(exists('int_import.dot'))
+ finally:
+ for fname in ('import.dot', 'ext_import.dot', 'int_import.dot'):
+ try:
+ os.remove(fname)
+ except:
+ pass
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/test_similar.py b/test/test_similar.py
new file mode 100644
index 000000000..457fafaed
--- /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 000000000..380b4cd7f
--- /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 000000000..a71edee3c
--- /dev/null
+++ b/test/unittest_lint.py
@@ -0,0 +1,268 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+ http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__revision__ = '$Id: unittest_lint.py,v 1.16 2006-04-19 09:17:40 syt Exp $'
+
+import unittest
+import sys
+import os
+import tempfile
+from os.path import join
+from cStringIO import StringIO
+
+from pylint.config import get_note_message
+from pylint.lint import PyLinter, Run, sort_checkers, UnknownMessage
+from pylint.utils import sort_msgs
+from pylint import checkers
+
+class SortMessagesTC(unittest.TestCase):
+
+ def test(self):
+ l = ['E0501', 'E0503', 'F0002', 'I0201', 'W0540',
+ 'R0202', 'F0203', 'R0220', 'W0321', 'I0001']
+ self.assertEquals(sort_msgs(l), ['I0001', 'I0201',
+ 'R0202', 'R0220',
+ 'W0321', 'W0540',
+ 'E0501', 'E0503',
+ 'F0002', 'F0203'])
+
+try:
+ optimized = True
+ raise AssertionError
+except AssertionError:
+ optimized = False
+
+class GetNoteMessageTC(unittest.TestCase):
+ def test(self):
+ msg = None
+ for note in range(-1, 11):
+ note_msg = get_note_message(note)
+ self.assertNotEquals(msg, note_msg)
+ msg = note_msg
+ if optimized:
+ self.assertRaises(AssertionError, get_note_message, 11)
+
+class RunTC(unittest.TestCase):
+
+ def _test_run(self, args, exit_code=1, no_exit_fail=True):
+ sys.stdout = StringIO()
+ sys.sterr = StringIO()
+ try:
+ try:
+ Run(args, quiet=1)
+ except SystemExit, ex:
+ self.assertEquals(ex.code, exit_code)
+ else:
+ if no_exit_fail:
+ self.fail()
+ finally:
+ sys.stdout = sys.__stdout__
+ sys.stderr = sys.__stderr__
+
+ def test_no_args(self):
+ self._test_run([], 1)
+
+ def test_no_ext_file(self):
+ self._test_run([join('input', 'noext')], no_exit_fail=False)
+
+
+class PyLinterTC(unittest.TestCase):
+
+ def setUp(self):
+ self.linter = PyLinter()
+ self.linter.disable_message_category('I')
+ self.linter.config.persistent = 0
+ # register checkers
+ checkers.initialize(self.linter)
+
+ def test_disable_all(self):
+ self.linter.disable_all_checkers()
+ checkers = sort_checkers(self.linter._checkers, enabled_only=0)
+ self.assert_(len(checkers) > 1)
+ checkers = sort_checkers(self.linter._checkers, enabled_only=1)
+ self.assertEquals(checkers, [self.linter])
+
+ def test_message_help(self):
+ msg = self.linter.get_message_help('F0001')
+ expected = 'F0001:\n Used when an error occured preventing the analyzing of a module (unable to\n find it for instance). This message belongs to the master checker.'
+ self.assertEquals(' '.join(msg.splitlines()), ' '.join(expected.splitlines()))
+ self.assertRaises(UnknownMessage, self.linter.get_message_help, 'YB12')
+
+ def test_enable_message(self):
+ linter = self.linter
+ linter.open()
+ linter.set_current_module('toto')
+ self.assert_(linter.is_message_enabled('W0101'))
+ self.assert_(linter.is_message_enabled('W0102'))
+ linter.disable_message('W0101', scope='package')
+ linter.disable_message('W0102', scope='module', line=1)
+ self.assert_(not linter.is_message_enabled('W0101'))
+ self.assert_(not linter.is_message_enabled('W0102', 1))
+ linter.set_current_module('tutu')
+ self.assert_(not linter.is_message_enabled('W0101'))
+ self.assert_(linter.is_message_enabled('W0102'))
+ linter.enable_message('W0101', scope='package')
+ linter.enable_message('W0102', scope='module', line=1)
+ self.assert_(linter.is_message_enabled('W0101'))
+ self.assert_(linter.is_message_enabled('W0102', 1))
+
+ def test_enable_message_category(self):
+ linter = self.linter
+ linter.open()
+ linter.set_current_module('toto')
+ self.assert_(linter.is_message_enabled('W0101'))
+ self.assert_(linter.is_message_enabled('R0102'))
+ linter.disable_message_category('W', scope='package')
+ linter.disable_message_category('REFACTOR', scope='module')
+ self.assert_(not linter.is_message_enabled('W0101'))
+ self.assert_(not linter.is_message_enabled('R0102'))
+ linter.set_current_module('tutu')
+ self.assert_(not linter.is_message_enabled('W0101'))
+ self.assert_(linter.is_message_enabled('R0102'))
+ linter.enable_message_category('WARNING', scope='package')
+ linter.enable_message_category('R', scope='module')
+ self.assert_(linter.is_message_enabled('W0101'))
+ self.assert_(linter.is_message_enabled('R0102'))
+
+ def test_enable_message_block(self):
+ linter = self.linter
+ linter.open()
+ filepath = join('input', 'func_block_disable_msg.py')
+ linter.set_current_module('func_block_disable_msg')
+ linter.process_module(open(filepath))
+ orig_state = linter._module_msgs_state.copy()
+ linter._module_msgs_state = {}
+ linter.collect_block_lines(linter.get_astng(filepath, 'func_block_disable_msg'), orig_state)
+ # global (module level)
+ self.assert_(linter.is_message_enabled('W0613'))
+ self.assert_(linter.is_message_enabled('E1101'))
+ # meth1
+ self.assert_(linter.is_message_enabled('W0613', 13))
+ # meth2
+ self.assert_(not linter.is_message_enabled('W0613', 18))
+ # meth3
+ self.assert_(not linter.is_message_enabled('E1101', 24))
+ self.assert_(linter.is_message_enabled('E1101', 26))
+ # meth4
+ self.assert_(not linter.is_message_enabled('E1101', 32))
+ self.assert_(linter.is_message_enabled('E1101', 36))
+ # meth5
+ self.assert_(not linter.is_message_enabled('E1101', 42))
+ self.assert_(not linter.is_message_enabled('E1101', 43))
+ self.assert_(linter.is_message_enabled('E1101', 46))
+ self.assert_(not linter.is_message_enabled('E1101', 49))
+ self.assert_(not linter.is_message_enabled('E1101', 51))
+ # meth6
+ self.assert_(not linter.is_message_enabled('E1101', 57))
+ self.assert_(linter.is_message_enabled('E1101', 61))
+ self.assert_(not linter.is_message_enabled('E1101', 64))
+ self.assert_(not linter.is_message_enabled('E1101', 66))
+
+ self.assert_(linter.is_message_enabled('E0602', 57))
+ self.assert_(linter.is_message_enabled('E0602', 61))
+ self.assert_(not linter.is_message_enabled('E0602', 62))
+ self.assert_(linter.is_message_enabled('E0602', 64))
+ self.assert_(linter.is_message_enabled('E0602', 66))
+ # meth7
+ self.assert_(not linter.is_message_enabled('E1101', 70))
+ self.assert_(linter.is_message_enabled('E1101', 72))
+ self.assert_(linter.is_message_enabled('E1101', 75))
+ self.assert_(linter.is_message_enabled('E1101', 77))
+
+ def test_list_messages(self):
+ sys.stdout = StringIO()
+ try:
+ # just invoke it, don't check the output
+ self.linter.list_messages()
+ finally:
+ sys.stdout = sys.__stdout__
+
+ def test_lint_ext_module_with_file_output(self):
+ self.linter.config.files_output = True
+ try:
+ self.linter.check('StringIO')
+ self.assert_(os.path.exists('pylint_StringIO.txt'))
+ self.assert_(os.path.exists('pylint_global.txt'))
+ finally:
+ try:
+ os.remove('pylint_StringIO.txt')
+ os.remove('pylint_global.txt')
+ except:
+ pass
+
+ def test_enable_report(self):
+ self.assertEquals(self.linter.is_report_enabled('R0001'), True)
+ self.linter.disable_report('R0001')
+ self.assertEquals(self.linter.is_report_enabled('R0001'), False)
+ self.linter.enable_report('R0001')
+ self.assertEquals(self.linter.is_report_enabled('R0001'), True)
+
+ def test_set_option_1(self):
+ linter = self.linter
+ linter.set_option('disable-msg', 'C0111,W0142')
+ self.assert_(not linter.is_message_enabled('C0111'))
+ self.assert_(not linter.is_message_enabled('W0142'))
+ self.assert_(linter.is_message_enabled('W0113'))
+
+ def test_set_option_2(self):
+ linter = self.linter
+ linter.set_option('disable-msg', ('C0111', 'W0142') )
+ self.assert_(not linter.is_message_enabled('C0111'))
+ self.assert_(not linter.is_message_enabled('W0142'))
+ self.assert_(linter.is_message_enabled('W0113'))
+
+
+from pylint import config
+
+class ConfigTC(unittest.TestCase):
+
+ def test_pylint_home(self):
+ uhome = os.path.expanduser('~')
+ if uhome == '~':
+ expected = '.pylint.d'
+ else:
+ expected = os.path.join(uhome, '.pylint.d')
+ self.assertEquals(config.PYLINT_HOME, expected)
+
+ try:
+ pylintd = join(tempfile.gettempdir(), '.pylint.d')
+ os.environ['PYLINTHOME'] = pylintd
+ try:
+ reload(config)
+ self.assertEquals(config.PYLINT_HOME, pylintd)
+ finally:
+ try:
+ os.remove(pylintd)
+ except:
+ pass
+ finally:
+ del os.environ['PYLINTHOME']
+
+ def test_pylintrc(self):
+ try:
+ self.assertEquals(config.PYLINTRC, None)
+ os.environ['PYLINTRC'] = join(tempfile.gettempdir(), '.pylintrc')
+ reload(config)
+ self.assertEquals(config.PYLINTRC, None)
+ os.environ['PYLINTRC'] = '.'
+ reload(config)
+ self.assertEquals(config.PYLINTRC, None)
+ finally:
+ del os.environ['PYLINTRC']
+ reload(config)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/utils.py b/test/utils.py
new file mode 100644
index 000000000..06eb76e06
--- /dev/null
+++ b/test/utils.py
@@ -0,0 +1,66 @@
+"""some pylint test utilities
+"""
+from glob import glob
+from os.path import join, abspath, dirname, basename
+from cStringIO import StringIO
+
+from pylint.interfaces import IReporter
+from pylint.reporters import BaseReporter
+
+PREFIX = abspath(dirname(__file__))
+
+def fix_path():
+ import sys
+ sys.path.insert(0, PREFIX)
+
+def get_tests_info(prefix=None, suffix=None):
+ pattern = '*'
+ if prefix:
+ pattern = prefix + pattern
+ if suffix:
+ pattern = pattern + suffix
+ result = []
+ for file in glob(join(PREFIX, "input", pattern)):
+ infile = basename(file)
+ outfile = join(PREFIX, "messages", infile.replace(suffix, '.txt'))
+ result.append((infile, outfile))
+
+ return result
+
+
+TITLE_UNDERLINES = ['', '=', '-', '.']
+
+class TestReporter(BaseReporter):
+ """ store plain text messages
+ """
+
+ __implements____ = IReporter
+
+ def __init__(self):
+ self.message_ids = {}
+ self.reset()
+
+ def reset(self):
+ self.out = StringIO()
+ self.messages = []
+
+ def add_message(self, msg_id, location, msg):
+ """manage message of different type and in the context of path """
+ fpath, module, object, line = location
+ self.message_ids[msg_id] = 1
+ if object:
+ object = ':%s' % object
+ sigle = msg_id[0]
+ self.messages.append('%s:%3s%s: %s' % (sigle, line, object, msg))
+
+ def finalize(self):
+ self.messages.sort()
+ for msg in self.messages:
+ print >>self.out, msg
+ result = self.out.getvalue()
+ self.reset()
+ return result
+
+ def display_results(self, layout):
+ """ignore layouts"""
+
diff --git a/utils.py b/utils.py
new file mode 100644
index 000000000..5b3921268
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,353 @@
+# Copyright (c) 2003-2005 Sylvain Thenault (thenault@gmail.com).
+# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""some various utilities and helper classes, most of them used in the
+main pylint class
+"""
+
+__revision__ = "$Id: utils.py,v 1.13 2006-04-19 09:17:40 syt Exp $"
+
+from os import linesep
+
+from logilab.astng import Module
+from logilab.common.textutils import normalize_text
+from logilab.common.ureports import Section
+
+from pylint.checkers import EmptyReport
+
+class UnknownMessage(Exception):
+ """raised when a unregistered message id is encountered"""
+
+
+MSG_TYPES = {
+ 'I' : 'info',
+ 'C' : 'convention',
+ 'R' : 'refactor',
+ 'W' : 'warning',
+ 'E' : 'error',
+ 'F' : 'fatal'
+ }
+MSG_CATEGORIES = MSG_TYPES.keys()
+
+
+def sort_checkers(checkers, enabled_only=True):
+ """return a list of enabled checker sorted by priority"""
+ if enabled_only:
+ checkers = [(-checker.priority, checker) for checker in checkers
+ if checker.is_enabled()]
+ else:
+ checkers = [(-checker.priority, checker) for checker in checkers]
+ checkers.sort()
+ return [item[1] for item in checkers]
+
+def sort_msgs(msg_ids):
+ """sort message identifiers according to their category first"""
+ msg_order = ['I', 'C', 'R', 'W', 'E', 'F']
+ def cmp_func(msgid1, msgid2):
+ """comparison function for two message identifiers"""
+ if msgid1[0] != msgid2[0]:
+ return cmp(msg_order.index(msgid1[0]), msg_order.index(msgid2[0]))
+ else:
+ return cmp(msgid1, msgid2)
+ msg_ids.sort(cmp_func)
+ return msg_ids
+
+def get_module_and_frameid(node):
+ """return the module name and the frame id in the module"""
+ frame = node.frame()
+ module, obj = '', []
+ while frame:
+ if isinstance(frame, Module):
+ module = frame.name
+ else:
+ obj.append(getattr(frame, 'name', '<lambda>'))
+ try:
+ frame = frame.parent.frame()
+ except AttributeError:
+ frame = None
+ obj.reverse()
+ return module, '.'.join(obj)
+
+
+class MessagesHandlerMixIn:
+ """a mix-in class containing all the messages related methods for the main
+ lint class
+ """
+
+ def __init__(self):
+ # dictionary of registered messages
+ self._messages = {}
+ self._messages_help = {}
+ self._msgs_state = {}
+ self._module_msgs_state = None
+ self._msg_cats_state = {}
+ self._module_msg_cats_state = None
+
+ def register_messages(self, checker):
+ """register a dictionary of messages
+
+ Keys are message ids, values are a 2-uple with the message type and the
+ message itself
+
+ message ids should be a string of len 4, where the to first characters
+ are the checker id and the two last the message id in this checker
+ """
+ msgs_dict = checker.msgs
+ chk_id = None
+ for msg_id, (msg, msg_help) in msgs_dict.items():
+ # avoid duplicate / malformed ids
+ assert not self._messages.has_key(msg_id), \
+ 'Message id %r is already defined' % msg_id
+ assert len(msg_id) == 5, 'Invalid message id %s' % msg_id
+ assert chk_id is None or chk_id == msg_id[1:3], \
+ 'Inconsistent checker part in message id %r' %msg_id
+ assert msg_id[0] in MSG_CATEGORIES, \
+ 'Bad message type %s in %r' % (msg_id[0], msg_id)
+ chk_id = msg_id[1:3]
+ if checker is not None:
+ add = ' This message belongs to the %s checker.' % checker.name
+ msg_help += add
+ self._messages_help[msg_id] = msg_help
+ self._messages[msg_id] = msg
+
+ def get_message_help(self, msg_id):
+ """return the help string for the given message id"""
+ msg_id = self.check_message_id(msg_id)
+ msg = self._messages_help[msg_id]
+ msg = normalize_text(' '.join(msg.split()), indent=' ')
+ return '%s:\n%s' % (msg_id, msg)
+
+ def disable_message(self, msg_id, scope='package', line=None):
+ """don't output message of the given id"""
+ assert scope in ('package', 'module')
+ msg_id = self.check_message_id(msg_id)
+ if scope == 'module':
+ assert line > 0
+ if msg_id != 'I0011':
+ self.add_message('I0011', line=line, args=msg_id)
+ #self._module_msgs_state[msg_id] = False
+ try:
+ self._module_msgs_state[msg_id][line] = False
+ except KeyError:
+ self._module_msgs_state[msg_id] = {line: False}
+
+ else:
+ msgs = self._msgs_state
+ msgs[msg_id] = False
+ # sync configuration object
+ self.config.disable_msg = [mid for mid, val in msgs.items()
+ if not val]
+
+ def enable_message(self, msg_id, scope='package', line=None):
+ """reenable message of the given id"""
+ assert scope in ('package', 'module')
+ msg_id = self.check_message_id(msg_id)
+ if scope == 'module':
+ assert line > 0
+ self.add_message('I0012', line=line, args=msg_id)
+ try:
+ self._module_msgs_state[msg_id][line] = True
+ except KeyError:
+ self._module_msgs_state[msg_id] = {line: True}
+ else:
+ msgs = self._msgs_state
+ msgs[msg_id] = True
+ # sync configuration object
+ self.config.enable_msg = [mid for mid, val in msgs.items() if val]
+
+ def disable_message_category(self, msg_cat_id, scope='package', line=None):
+ """don't output message in the given category"""
+ assert scope in ('package', 'module')
+ msg_cat_id = msg_cat_id[0].upper()
+ if scope == 'module':
+ self.add_message('I0011', line=line, args=msg_cat_id)
+ self._module_msg_cats_state[msg_cat_id] = False
+ else:
+ self._msg_cats_state[msg_cat_id] = False
+
+ def enable_message_category(self, msg_cat_id, scope='package', line=None):
+ """reenable message of the given category"""
+ assert scope in ('package', 'module')
+ msg_cat_id = msg_cat_id[0].upper()
+ if scope == 'module':
+ self.add_message('I0012', line=line, args=msg_cat_id)
+ self._module_msg_cats_state[msg_cat_id] = True
+ else:
+ self._msg_cats_state[msg_cat_id] = True
+
+ def check_message_id(self, msg_id):
+ """raise UnknownMessage if the message id is not defined"""
+ msg_id = msg_id.upper()
+ if not self._messages.has_key(msg_id):
+ raise UnknownMessage('No such message id %s' % msg_id)
+ return msg_id
+
+ def is_message_enabled(self, msg_id, line=None):
+ """return true if the message associated to the given message id is
+ enabled
+ """
+ try:
+ if not self._module_msg_cats_state[msg_id[0]]:
+ return False
+ except (KeyError, TypeError):
+ if not self._msg_cats_state.get(msg_id[0], True):
+ return False
+ if line is None:
+ return self._msgs_state.get(msg_id, True)
+ try:
+ return self._module_msgs_state[msg_id][line]
+ except (KeyError, TypeError):
+ return self._msgs_state.get(msg_id, True)
+
+ def add_message(self, msg_id, line=None, node=None, args=None):
+ """add the message corresponding to the given id.
+
+ If provided, msg is expanded using args
+
+ astng checkers should provide the node argument, raw checkers should
+ provide the line argument.
+ """
+ if line is None and node is not None:
+ line = node.lineno or node.statement().lineno
+ #if not isinstance(node, Module):
+ # assert line > 0, node.__class__
+ # should this message be displayed
+ if not self.is_message_enabled(msg_id, line):
+ return
+ # update stats
+ msg_cat = MSG_TYPES[msg_id[0]]
+ self.stats[msg_cat] += 1
+ self.stats['by_module'][self.current_name][msg_cat] += 1
+ try:
+ self.stats['by_msg'][msg_id] += 1
+ except KeyError:
+ self.stats['by_msg'][msg_id] = 1
+ msg = self._messages[msg_id]
+ # expand message ?
+ if args:
+ msg %= args
+ # get module and object
+ if node is None:
+ module, obj = self.current_name, ''
+ path = self.current_file
+ else:
+ module, obj = get_module_and_frameid(node)
+ path = node.root().file
+ # add the message
+ self.reporter.add_message(msg_id, (path, module, obj, line or 0), msg)
+
+ def help_message(self, msgids):
+ """display help messages for the given message identifiers"""
+ for msg_id in msgids:
+ try:
+ print self.get_message_help(msg_id)
+ print
+ except UnknownMessage, ex:
+ print ex
+ print
+ continue
+
+ def list_messages(self):
+ """list available messages"""
+ for checker in sort_checkers(self._checkers.keys()):
+ print checker.name.capitalize()
+ print '-' * len(checker.name)
+ print
+ if checker.__doc__: # __doc__ is None with -OO
+ print 'Description'
+ print '~~~~~~~~~~~'
+ print linesep.join([line.strip()
+ for line in checker.__doc__.splitlines()])
+ print
+ if not checker.msgs:
+ continue
+ print 'Messages'
+ print '~~~~~~~~'
+ for msg_id in sort_msgs(checker.msgs.keys()):
+ print self.get_message_help(msg_id)
+ print
+ print
+ print
+
+
+class ReportsHandlerMixIn:
+ """a mix-in class containing all the reports and stats manipulation
+ related methods for the main lint class
+ """
+ def __init__(self):
+ self._reports = {}
+ self._reports_state = {}
+
+ def register_report(self, r_id, r_title, r_cb, checker):
+ """register a report
+
+ r_id is the unique identifier for the report
+ r_title the report's title
+ r_cb the method to call to make the report
+ checker is the checker defining the report
+ """
+ r_id = r_id.upper()
+ self._reports.setdefault(checker, []).append( (r_id, r_title, r_cb) )
+
+ def enable_report(self, r_id):
+ """disable the report of the given id"""
+ r_id = r_id.upper()
+ self._reports_state[r_id] = True
+
+ def disable_report(self, r_id):
+ """disable the report of the given id"""
+ r_id = r_id.upper()
+ self._reports_state[r_id] = False
+
+ def is_report_enabled(self, r_id):
+ """return true if the report associated to the given identifier is
+ enabled
+ """
+ return self._reports_state.get(r_id, True)
+
+ def make_reports(self, stats, old_stats):
+ """render registered reports"""
+ if self.config.files_output:
+ filename = 'pylint_global.' + self.reporter.extension
+ self.reporter.set_output(open(filename, 'w'))
+ sect = Section('Report',
+ '%s statements analysed.'% (self.stats['statement']))
+ checkers = sort_checkers(self._reports.keys())
+ checkers.reverse()
+ for checker in checkers:
+ for r_id, r_title, r_cb in self._reports[checker]:
+ if not self.is_report_enabled(r_id):
+ continue
+ report_sect = Section(r_title)
+ try:
+ r_cb(report_sect, stats, old_stats)
+ except EmptyReport:
+ continue
+ report_sect.report_id = r_id
+ sect.append(report_sect)
+ self.reporter.display_results(sect)
+
+ def add_stats(self, **kwargs):
+ """add some stats entries to the statistic dictionary
+ raise an AssertionError if there is a key conflict
+ """
+ for key, value in kwargs.items():
+ if key[-1] == '_':
+ key = key[:-1]
+ assert not self.stats.has_key(key)
+ self.stats[key] = value
+ return self.stats
+