From 5b84a66d751dcf48d523ac4a7ff29c9195c3092c Mon Sep 17 00:00:00 2001 From: Hannes Doyle Date: Thu, 14 Apr 2016 16:02:04 +0200 Subject: [PATCH 1/2] Updated with new astroid and pylint. --- pymode/libs/astroid/__init__.py | 15 +- pymode/libs/astroid/__pkginfo__.py | 4 +- pymode/libs/astroid/arguments.py | 233 ++ pymode/libs/astroid/as_string.py | 115 +- pymode/libs/astroid/bases.py | 290 ++- ...nference.py => brain_builtin_inference.py} | 143 +- pymode/libs/astroid/brain/brain_dateutil.py | 15 + .../astroid/brain/{py2gi.py => brain_gi.py} | 56 +- .../{py2mechanize.py => brain_mechanize.py} | 0 .../brain/{pynose.py => brain_nose.py} | 5 +- pymode/libs/astroid/brain/brain_numpy.py | 62 + .../brain/{py2pytest.py => brain_pytest.py} | 62 +- pymode/libs/astroid/brain/brain_qt.py | 44 + .../brain/{pysix_moves.py => brain_six.py} | 29 +- pymode/libs/astroid/brain/brain_stdlib.py | 460 ++++ pymode/libs/astroid/brain/py2qt4.py | 22 - pymode/libs/astroid/brain/py2stdlib.py | 334 --- pymode/libs/astroid/builder.py | 185 +- pymode/libs/astroid/context.py | 81 + pymode/libs/astroid/decorators.py | 75 + pymode/libs/astroid/exceptions.py | 20 + pymode/libs/astroid/inference.py | 320 ++- pymode/libs/astroid/inspector.py | 273 --- pymode/libs/astroid/manager.py | 214 +- pymode/libs/astroid/mixins.py | 51 +- pymode/libs/astroid/modutils.py | 151 +- pymode/libs/astroid/node_classes.py | 399 +-- pymode/libs/astroid/nodes.py | 57 +- pymode/libs/astroid/objects.py | 186 ++ pymode/libs/astroid/protocols.py | 259 +- pymode/libs/astroid/raw_building.py | 40 +- pymode/libs/astroid/rebuilder.py | 677 +++--- pymode/libs/astroid/scoped_nodes.py | 1070 +++++---- pymode/libs/astroid/test_utils.py | 25 +- pymode/libs/astroid/tests/__init__.py | 0 pymode/libs/astroid/tests/resources.py | 72 + .../python2/data/MyPyPa-0.1.0-py2.5.egg | Bin 0 -> 1222 bytes .../python2/data/MyPyPa-0.1.0-py2.5.zip | Bin 0 -> 1222 bytes .../testdata/python2/data/SSL1/Connection1.py | 14 + .../testdata/python2/data/SSL1/__init__.py | 1 + .../tests/testdata/python2/data/__init__.py | 1 + .../testdata/python2/data/absimp/__init__.py | 5 + .../data/absimp/sidepackage/__init__.py | 3 + .../testdata/python2/data/absimp/string.py | 3 + .../tests/testdata/python2/data/absimport.py | 3 + .../tests/testdata/python2/data/all.py | 9 + .../testdata/python2/data/appl/__init__.py | 3 + .../python2/data/appl/myConnection.py | 12 + .../python2/data/clientmodule_test.py | 32 + .../testdata/python2/data/descriptor_crash.py | 11 + .../tests/testdata/python2/data/email.py | 1 + .../python2/data/find_test/__init__.py | 0 .../testdata/python2/data/find_test/module.py | 0 .../python2/data/find_test/module2.py | 0 .../python2/data/find_test/noendingnewline.py | 0 .../python2/data/find_test/nonregr.py | 0 .../tests/testdata/python2/data/format.py | 34 + .../testdata/python2/data/joined_strings.py | 1051 ++++++++ .../testdata/python2/data/lmfp/__init__.py | 2 + .../tests/testdata/python2/data/lmfp/foo.py | 6 + .../tests/testdata/python2/data/module.py | 89 + .../python2/data/module1abs/__init__.py | 4 + .../testdata/python2/data/module1abs/core.py | 1 + .../tests/testdata/python2/data/module2.py | 143 ++ .../testdata/python2/data/noendingnewline.py | 36 + .../tests/testdata/python2/data/nonregr.py | 57 + .../tests/testdata/python2/data/notall.py | 7 + .../testdata/python2/data/package/__init__.py | 4 + .../python2/data/package/absimport.py | 6 + .../testdata/python2/data/package/hello.py | 2 + .../import_package_subpackage_module.py | 49 + .../data/package/subpackage/__init__.py | 1 + .../python2/data/package/subpackage/module.py | 1 + .../tests/testdata/python2/data/recursion.py | 3 + .../python2/data/suppliermodule_test.py | 13 + .../python2/data/unicode_package/__init__.py | 1 + .../data/unicode_package/core/__init__.py | 0 .../python3/data/MyPyPa-0.1.0-py2.5.egg | Bin 0 -> 1222 bytes .../python3/data/MyPyPa-0.1.0-py2.5.zip | Bin 0 -> 1222 bytes .../testdata/python3/data/SSL1/Connection1.py | 14 + .../testdata/python3/data/SSL1/__init__.py | 1 + .../tests/testdata/python3/data/__init__.py | 1 + .../testdata/python3/data/absimp/__init__.py | 5 + .../data/absimp/sidepackage/__init__.py | 3 + .../testdata/python3/data/absimp/string.py | 3 + .../tests/testdata/python3/data/absimport.py | 3 + .../tests/testdata/python3/data/all.py | 9 + .../testdata/python3/data/appl/__init__.py | 3 + .../python3/data/appl/myConnection.py | 11 + .../python3/data/clientmodule_test.py | 32 + .../testdata/python3/data/descriptor_crash.py | 11 + .../tests/testdata/python3/data/email.py | 1 + .../python3/data/find_test/__init__.py | 0 .../testdata/python3/data/find_test/module.py | 0 .../python3/data/find_test/module2.py | 0 .../python3/data/find_test/noendingnewline.py | 0 .../python3/data/find_test/nonregr.py | 0 .../tests/testdata/python3/data/format.py | 34 + .../testdata/python3/data/joined_strings.py | 1051 ++++++++ .../testdata/python3/data/lmfp/__init__.py | 2 + .../tests/testdata/python3/data/lmfp/foo.py | 6 + .../tests/testdata/python3/data/module.py | 88 + .../python3/data/module1abs/__init__.py | 4 + .../testdata/python3/data/module1abs/core.py | 1 + .../tests/testdata/python3/data/module2.py | 143 ++ .../testdata/python3/data/noendingnewline.py | 36 + .../tests/testdata/python3/data/nonregr.py | 57 + .../tests/testdata/python3/data/notall.py | 8 + .../testdata/python3/data/package/__init__.py | 4 + .../python3/data/package/absimport.py | 6 + .../testdata/python3/data/package/hello.py | 2 + .../import_package_subpackage_module.py | 49 + .../data/package/subpackage/__init__.py | 1 + .../python3/data/package/subpackage/module.py | 1 + .../tests/testdata/python3/data/recursion.py | 3 + .../python3/data/suppliermodule_test.py | 13 + .../python3/data/unicode_package/__init__.py | 1 + .../data/unicode_package/core/__init__.py | 0 pymode/libs/astroid/tests/unittest_brain.py | 501 ++++ pymode/libs/astroid/tests/unittest_builder.py | 774 ++++++ .../libs/astroid/tests/unittest_inference.py | 2130 +++++++++++++++++ pymode/libs/astroid/tests/unittest_lookup.py | 352 +++ pymode/libs/astroid/tests/unittest_manager.py | 216 ++ .../libs/astroid/tests/unittest_modutils.py | 269 +++ pymode/libs/astroid/tests/unittest_nodes.py | 764 ++++++ pymode/libs/astroid/tests/unittest_objects.py | 530 ++++ .../libs/astroid/tests/unittest_peephole.py | 121 + .../libs/astroid/tests/unittest_protocols.py | 176 ++ pymode/libs/astroid/tests/unittest_python3.py | 254 ++ .../astroid/tests/unittest_raw_building.py | 85 + .../libs/astroid/tests/unittest_regrtest.py | 336 +++ .../astroid/tests/unittest_scoped_nodes.py | 1571 ++++++++++++ .../libs/astroid/tests/unittest_transforms.py | 245 ++ pymode/libs/astroid/tests/unittest_utils.py | 124 + pymode/libs/astroid/transforms.py | 96 + pymode/libs/astroid/util.py | 89 + pymode/libs/astroid/utils.py | 239 -- pymode/libs/pylint/__pkginfo__.py | 16 +- pymode/libs/pylint/checkers/__init__.py | 3 +- pymode/libs/pylint/checkers/async.py | 82 + pymode/libs/pylint/checkers/base.py | 1012 +++++++- pymode/libs/pylint/checkers/classes.py | 546 +++-- .../libs/pylint/checkers/design_analysis.py | 85 +- pymode/libs/pylint/checkers/exceptions.py | 131 +- pymode/libs/pylint/checkers/format.py | 85 +- pymode/libs/pylint/checkers/imports.py | 387 ++- pymode/libs/pylint/checkers/logging.py | 18 +- pymode/libs/pylint/checkers/misc.py | 3 +- pymode/libs/pylint/checkers/newstyle.py | 93 +- pymode/libs/pylint/checkers/python3.py | 52 +- pymode/libs/pylint/checkers/raw_metrics.py | 6 +- pymode/libs/pylint/checkers/similar.py | 9 +- pymode/libs/pylint/checkers/spelling.py | 34 +- pymode/libs/pylint/checkers/stdlib.py | 176 +- pymode/libs/pylint/checkers/strings.py | 73 +- pymode/libs/pylint/checkers/typecheck.py | 623 +++-- pymode/libs/pylint/checkers/utils.py | 461 ++-- pymode/libs/pylint/checkers/variables.py | 536 +++-- pymode/libs/pylint/config.py | 809 ++++++- pymode/libs/pylint/epylint.py | 11 +- pymode/libs/pylint/extensions/__init__.py | 0 pymode/libs/pylint/extensions/check_docs.py | 311 +++ pymode/libs/pylint/extensions/check_elif.py | 62 + pymode/libs/pylint/graph.py | 179 ++ pymode/libs/pylint/gui.py | 4 +- pymode/libs/pylint/interfaces.py | 24 +- pymode/libs/pylint/lint.py | 261 +- pymode/libs/pylint/pyreverse/diadefslib.py | 23 +- pymode/libs/pylint/pyreverse/diagrams.py | 19 +- pymode/libs/pylint/pyreverse/inspector.py | 372 +++ pymode/libs/pylint/pyreverse/main.py | 37 +- pymode/libs/pylint/pyreverse/utils.py | 82 +- pymode/libs/pylint/pyreverse/vcgutils.py | 198 ++ pymode/libs/pylint/pyreverse/writer.py | 7 +- pymode/libs/pylint/reporters/__init__.py | 52 +- pymode/libs/pylint/reporters/guireporter.py | 2 +- pymode/libs/pylint/reporters/html.py | 13 +- pymode/libs/pylint/reporters/json.py | 14 +- pymode/libs/pylint/reporters/text.py | 97 +- .../pylint/reporters/ureports/__init__.py | 106 + .../pylint/reporters/ureports/html_writer.py | 93 + .../libs/pylint/reporters/ureports/nodes.py | 181 ++ .../pylint/reporters/ureports/text_writer.py | 99 + pymode/libs/pylint/test/data/__init__.py | 0 pymode/libs/pylint/test/data/ascript | 2 + .../libs/pylint/test/data/classes_No_Name.dot | 12 + .../pylint/test/data/clientmodule_test.py | 29 + .../pylint/test/data/packages_No_Name.dot | 8 + .../pylint/test/data/suppliermodule_test.py | 10 + .../libs/pylint/test/extensions/__init__.py | 0 .../libs/pylint/test/extensions/data/elif.py | 26 + .../pylint/test/extensions/test_check_docs.py | 666 ++++++ .../test/extensions/test_elseif_used.py | 47 + .../libs/pylint/test/functional/__init__.py | 0 .../test/functional/abstract_abc_methods.py | 17 + .../abstract_class_instantiated_in_class.py | 20 + .../abstract_class_instantiated_py2.py | 83 + .../abstract_class_instantiated_py2.rc | 2 + .../abstract_class_instantiated_py2.txt | 4 + .../abstract_class_instantiated_py3.py | 111 + .../abstract_class_instantiated_py3.rc | 2 + .../abstract_class_instantiated_py3.txt | 4 + .../abstract_class_instantiated_py34.py | 19 + .../abstract_class_instantiated_py34.rc | 2 + .../abstract_class_instantiated_py34.txt | 1 + .../test/functional/abstract_method_py2.py | 90 + .../test/functional/abstract_method_py2.rc | 2 + .../test/functional/abstract_method_py2.txt | 16 + .../test/functional/abstract_method_py3.py | 88 + .../test/functional/abstract_method_py3.rc | 2 + .../test/functional/abstract_method_py3.txt | 16 + .../access_member_before_definition.py | 40 + .../access_member_before_definition.txt | 2 + .../test/functional/access_to__name__.py | 21 + .../test/functional/access_to__name__.txt | 3 + .../functional/access_to_protected_members.py | 44 + .../access_to_protected_members.txt | 5 + .../anomalous_unicode_escape_py2.py | 20 + .../anomalous_unicode_escape_py2.rc | 2 + .../anomalous_unicode_escape_py2.txt | 3 + .../anomalous_unicode_escape_py3.py | 19 + .../anomalous_unicode_escape_py3.rc | 2 + .../anomalous_unicode_escape_py3.txt | 3 + .../libs/pylint/test/functional/arguments.py | 167 ++ .../libs/pylint/test/functional/arguments.txt | 26 + .../test/functional/arguments_differ.py | 138 ++ .../test/functional/arguments_differ.txt | 3 + .../pylint/test/functional/assert_on_tuple.py | 11 + .../test/functional/assert_on_tuple.txt | 2 + .../test/functional/assigning_non_slot.py | 133 + .../test/functional/assigning_non_slot.txt | 4 + .../pylint/test/functional/async_functions.py | 65 + .../pylint/test/functional/async_functions.rc | 2 + .../test/functional/async_functions.txt | 10 + .../attribute_defined_outside_init.py | 62 + .../attribute_defined_outside_init.txt | 2 + .../pylint/test/functional/bad_builtin.py | 4 + .../pylint/test/functional/bad_builtin.txt | 2 + .../test/functional/bad_continuation.py | 191 ++ .../test/functional/bad_continuation.txt | 63 + .../test/functional/bad_exception_context.py | 24 + .../test/functional/bad_exception_context.rc | 2 + .../test/functional/bad_exception_context.txt | 3 + .../pylint/test/functional/bad_indentation.py | 20 + .../test/functional/bad_indentation.txt | 2 + .../test/functional/bad_inline_option.py | 5 + .../test/functional/bad_inline_option.rc | 2 + .../test/functional/bad_inline_option.txt | 1 + .../pylint/test/functional/bad_open_mode.py | 37 + .../pylint/test/functional/bad_open_mode.rc | 2 + .../pylint/test/functional/bad_open_mode.txt | 14 + .../test/functional/bad_open_mode_py3.py | 23 + .../test/functional/bad_open_mode_py3.rc | 2 + .../test/functional/bad_open_mode_py3.txt | 6 + .../test/functional/bad_reversed_sequence.py | 71 + .../test/functional/bad_reversed_sequence.txt | 8 + .../functional/bad_staticmethod_argument.py | 16 + .../functional/bad_staticmethod_argument.txt | 2 + .../pylint/test/functional/bad_whitespace.py | 3 + .../pylint/test/functional/bad_whitespace.txt | 7 + .../pylint/test/functional/bare_except.py | 6 + .../pylint/test/functional/bare_except.txt | 1 + .../test/functional/blacklisted_name.py | 4 + .../test/functional/blacklisted_name.txt | 1 + .../test/functional/boolean_datetime.py | 30 + .../test/functional/boolean_datetime.rc | 2 + .../test/functional/boolean_datetime.txt | 8 + .../test/functional/cellvar_escaping_loop.py | 130 + .../test/functional/cellvar_escaping_loop.txt | 8 + .../test/functional/class_members_py27.py | 65 + .../test/functional/class_members_py27.rc | 3 + .../test/functional/class_members_py27.txt | 6 + .../test/functional/class_members_py30.py | 63 + .../test/functional/class_members_py30.rc | 2 + .../test/functional/class_members_py30.txt | 7 + .../pylint/test/functional/class_scope.py | 22 + .../pylint/test/functional/class_scope.txt | 4 + .../test/functional/confidence_filter.py | 15 + .../test/functional/confidence_filter.rc | 3 + .../test/functional/confidence_filter.txt | 1 + .../functional/confusing_with_statement.py | 27 + .../functional/confusing_with_statement.txt | 1 + .../functional/consider_using_enumerate.py | 43 + .../functional/consider_using_enumerate.txt | 1 + .../test/functional/continue_in_finally.py | 24 + .../test/functional/continue_in_finally.txt | 1 + .../functional/crash_missing_module_type.py | 18 + .../functional/crash_missing_module_type.txt | 0 .../pylint/test/functional/ctor_arguments.py | 104 + .../pylint/test/functional/ctor_arguments.txt | 24 + .../functional/dangerous_default_value.py | 79 + .../functional/dangerous_default_value.rc | 2 + .../functional/dangerous_default_value.txt | 14 + .../dangerous_default_value_py30.py | 79 + .../dangerous_default_value_py30.rc | 2 + .../dangerous_default_value_py30.txt | 14 + .../defined_and_used_on_same_line.py | 29 + .../test/functional/deprecated_lambda.py | 23 + .../test/functional/deprecated_lambda.rc | 2 + .../test/functional/deprecated_lambda.txt | 2 + .../test/functional/deprecated_methods_py2.py | 9 + .../test/functional/deprecated_methods_py2.rc | 2 + .../functional/deprecated_methods_py2.txt | 4 + .../test/functional/deprecated_methods_py3.py | 35 + .../test/functional/deprecated_methods_py3.rc | 2 + .../functional/deprecated_methods_py3.txt | 13 + .../test/functional/deprecated_module_py2.py | 8 + .../test/functional/deprecated_module_py2.rc | 3 + .../test/functional/deprecated_module_py2.txt | 4 + .../test/functional/deprecated_module_py3.py | 5 + .../test/functional/deprecated_module_py3.rc | 2 + .../test/functional/deprecated_module_py3.txt | 1 + .../deprecated_module_uninstalled.py | 5 + .../deprecated_module_uninstalled.rc | 2 + .../deprecated_module_uninstalled.txt | 2 + .../libs/pylint/test/functional/docstrings.py | 83 + .../pylint/test/functional/docstrings.txt | 8 + .../functional/duplicate_argument_name.py | 11 + .../functional/duplicate_argument_name.txt | 3 + .../pylint/test/functional/duplicate_bases.py | 15 + .../test/functional/duplicate_bases.txt | 1 + .../functional/duplicate_dict_literal_key.py | 14 + .../functional/duplicate_dict_literal_key.txt | 1 + .../test/functional/duplicate_except.py | 12 + .../test/functional/duplicate_except.txt | 1 + .../libs/pylint/test/functional/eval_used.py | 11 + .../libs/pylint/test/functional/eval_used.txt | 4 + .../test/functional/exception_is_binary_op.py | 12 + .../functional/exception_is_binary_op.txt | 4 + .../pylint/test/functional/exec_used_py2.py | 10 + .../pylint/test/functional/exec_used_py2.rc | 2 + .../pylint/test/functional/exec_used_py2.txt | 4 + .../pylint/test/functional/exec_used_py3.py | 10 + .../pylint/test/functional/exec_used_py3.rc | 2 + .../pylint/test/functional/exec_used_py3.txt | 4 + pymode/libs/pylint/test/functional/fixme.py | 18 + pymode/libs/pylint/test/functional/fixme.txt | 5 + .../pylint/test/functional/formatting.txt | 0 .../test/functional/function_redefined.py | 74 + .../test/functional/function_redefined.txt | 5 + .../pylint/test/functional/future_import.py | 3 + .../functional/future_unicode_literals.py | 6 + .../functional/future_unicode_literals.rc | 2 + .../functional/future_unicode_literals.txt | 1 + .../test/functional/generated_members.py | 8 + .../test/functional/generated_members.rc | 5 + .../test/functional/genexpr_variable_scope.py | 5 + .../functional/genexpr_variable_scope.txt | 1 + pymode/libs/pylint/test/functional/globals.py | 25 + .../libs/pylint/test/functional/globals.txt | 6 + .../pylint/test/functional/import_error.py | 27 + .../pylint/test/functional/import_error.txt | 3 + .../test/functional/inconsistent_mro.py | 9 + .../test/functional/inconsistent_mro.txt | 1 + .../test/functional/indexing_exception.py | 15 + .../test/functional/indexing_exception.rc | 5 + .../test/functional/indexing_exception.txt | 3 + .../test/functional/inherit_non_class.py | 81 + .../test/functional/inherit_non_class.txt | 9 + .../test/functional/init_is_generator.py | 5 + .../test/functional/init_is_generator.txt | 1 + .../pylint/test/functional/init_not_called.py | 64 + .../test/functional/init_not_called.txt | 6 + .../test/functional/invalid_all_object.py | 6 + .../test/functional/invalid_all_object.txt | 3 + .../test/functional/invalid_encoded_data.py | 5 + .../test/functional/invalid_encoded_data.rc | 3 + .../test/functional/invalid_encoded_data.txt | 1 + .../functional/invalid_exceptions_caught.py | 109 + .../functional/invalid_exceptions_caught.txt | 12 + .../functional/invalid_exceptions_raised.py | 83 + .../functional/invalid_exceptions_raised.txt | 12 + .../pylint/test/functional/invalid_name.py | 31 + .../pylint/test/functional/invalid_name.txt | 3 + .../test/functional/invalid_sequence_index.py | 216 ++ .../functional/invalid_sequence_index.txt | 20 + .../test/functional/invalid_slice_index.py | 60 + .../test/functional/invalid_slice_index.txt | 5 + .../invalid_star_assignment_target.py | 4 + .../invalid_star_assignment_target.rc | 2 + .../invalid_star_assignment_target.txt | 1 + .../functional/invalid_unary_operand_type.py | 51 + .../functional/invalid_unary_operand_type.rc | 2 + .../functional/invalid_unary_operand_type.txt | 14 + .../test/functional/iterable_context.py | 179 ++ .../test/functional/iterable_context.txt | 10 + .../test/functional/iterable_context_py2.py | 18 + .../test/functional/iterable_context_py2.rc | 3 + .../test/functional/iterable_context_py2.txt | 1 + .../test/functional/iterable_context_py3.py | 18 + .../test/functional/iterable_context_py3.rc | 3 + .../test/functional/iterable_context_py3.txt | 1 + .../pylint/test/functional/line_endings.py | 4 + .../pylint/test/functional/line_endings.rc | 2 + .../pylint/test/functional/line_endings.txt | 2 + .../pylint/test/functional/line_too_long.py | 26 + .../pylint/test/functional/line_too_long.txt | 4 + .../logging_format_interpolation.py | 25 + .../logging_format_interpolation.txt | 4 + .../test/functional/logging_not_lazy.py | 17 + .../test/functional/logging_not_lazy.txt | 3 + .../test/functional/long_lines_with_utf8.py | 7 + .../test/functional/long_lines_with_utf8.txt | 1 + .../pylint/test/functional/mapping_context.py | 97 + .../test/functional/mapping_context.txt | 2 + .../test/functional/mapping_context_py2.py | 19 + .../test/functional/mapping_context_py2.rc | 3 + .../test/functional/mapping_context_py2.txt | 1 + .../test/functional/mapping_context_py3.py | 19 + .../test/functional/mapping_context_py3.rc | 3 + .../test/functional/mapping_context_py3.txt | 1 + .../pylint/test/functional/member_checks.py | 171 ++ .../pylint/test/functional/member_checks.txt | 15 + .../test/functional/membership_protocol.py | 123 + .../test/functional/membership_protocol.txt | 7 + .../functional/membership_protocol_py2.py | 36 + .../functional/membership_protocol_py2.rc | 3 + .../functional/membership_protocol_py2.txt | 3 + .../functional/membership_protocol_py3.py | 36 + .../functional/membership_protocol_py3.rc | 3 + .../functional/membership_protocol_py3.txt | 3 + .../pylint/test/functional/method_hidden.py | 16 + .../pylint/test/functional/method_hidden.txt | 1 + .../test/functional/misplaced_bare_raise.py | 75 + .../test/functional/misplaced_bare_raise.txt | 7 + .../misplaced_comparison_constant.py | 36 + .../misplaced_comparison_constant.txt | 6 + .../test/functional/misplaced_future.py | 6 + .../test/functional/misplaced_future.txt | 1 + .../test/functional/missing_docstring.py | 54 + .../test/functional/missing_docstring.txt | 3 + .../test/functional/missing_final_newline.py | 4 + .../test/functional/missing_final_newline.txt | 1 + .../test/functional/missing_self_argument.py | 20 + .../test/functional/missing_self_argument.txt | 6 + .../test/functional/mixed_indentation.py | 10 + .../test/functional/mixed_indentation.txt | 2 + .../test/functional/multiple_imports.py | 2 + .../test/functional/multiple_imports.txt | 1 + .../pylint/test/functional/name_styles.py | 119 + .../pylint/test/functional/name_styles.rc | 5 + .../pylint/test/functional/name_styles.txt | 17 + .../functional/namedtuple_member_inference.py | 22 + .../namedtuple_member_inference.txt | 2 + .../pylint/test/functional/names_in__all__.py | 49 + .../test/functional/names_in__all__.txt | 6 + .../test/functional/newstyle__slots__.py | 17 + .../test/functional/newstyle__slots__.txt | 2 + .../test/functional/newstyle_properties.py | 53 + .../test/functional/newstyle_properties.txt | 2 + .../functional/no_classmethod_decorator.py | 35 + .../functional/no_classmethod_decorator.txt | 3 + .../test/functional/no_name_in_module.py | 66 + .../test/functional/no_name_in_module.txt | 16 + .../pylint/test/functional/no_self_use.py | 78 + .../pylint/test/functional/no_self_use.txt | 1 + .../pylint/test/functional/no_self_use_py3.py | 12 + .../pylint/test/functional/no_self_use_py3.rc | 2 + .../test/functional/no_self_use_py3.txt | 1 + .../functional/no_staticmethod_decorator.py | 35 + .../functional/no_staticmethod_decorator.txt | 3 + .../test/functional/non_iterator_returned.py | 95 + .../test/functional/non_iterator_returned.txt | 4 + .../test/functional/nonexistent_operator.py | 15 + .../test/functional/nonexistent_operator.txt | 6 + .../test/functional/nonlocal_and_global.py | 12 + .../test/functional/nonlocal_and_global.rc | 2 + .../test/functional/nonlocal_and_global.txt | 1 + .../functional/nonlocal_without_binding.py | 30 + .../functional/nonlocal_without_binding.rc | 2 + .../functional/nonlocal_without_binding.txt | 3 + .../functional/not_async_context_manager.py | 71 + .../functional/not_async_context_manager.rc | 2 + .../functional/not_async_context_manager.txt | 5 + .../pylint/test/functional/not_callable.py | 112 + .../pylint/test/functional/not_callable.txt | 8 + .../test/functional/not_context_manager.py | 135 ++ .../test/functional/not_context_manager.txt | 5 + .../pylint/test/functional/not_in_loop.py | 54 + .../pylint/test/functional/not_in_loop.txt | 8 + .../test/functional/old_division_manually.py | 2 + .../test/functional/old_division_manually.rc | 6 + .../test/functional/old_style_class_py27.py | 18 + .../test/functional/old_style_class_py27.rc | 2 + .../test/functional/old_style_class_py27.txt | 2 + .../test/functional/pygtk_enum_crash.py | 11 + .../test/functional/pygtk_enum_crash.rc | 2 + .../pylint/test/functional/pygtk_import.py | 14 + .../pylint/test/functional/pygtk_import.rc | 2 + .../functional/raising_non_exception_py3.py | 13 + .../functional/raising_non_exception_py3.rc | 2 + .../functional/raising_non_exception_py3.txt | 1 + .../test/functional/redefined_builtin.py | 10 + .../test/functional/redefined_builtin.txt | 2 + .../functional/redefined_variable_type.py | 55 + .../functional/redefined_variable_type.txt | 6 + .../functional/redundant_unittest_assert.py | 38 + .../functional/redundant_unittest_assert.txt | 6 + .../libs/pylint/test/functional/reimported.py | 7 + .../pylint/test/functional/reimported.txt | 3 + .../test/functional/repeated_keyword.py | 13 + .../test/functional/repeated_keyword.txt | 1 + .../pylint/test/functional/return_in_init.py | 25 + .../pylint/test/functional/return_in_init.txt | 1 + .../functional/return_outside_function.py | 2 + .../functional/return_outside_function.txt | 1 + .../functional/simplifiable_if_statement.py | 120 + .../functional/simplifiable_if_statement.txt | 4 + .../test/functional/singleton_comparison.py | 11 + .../test/functional/singleton_comparison.txt | 5 + .../pylint/test/functional/slots_checks.py | 61 + .../pylint/test/functional/slots_checks.txt | 4 + .../test/functional/socketerror_import.py | 6 + .../star_needs_assignment_target.py | 4 + .../star_needs_assignment_target.rc | 2 + .../star_needs_assignment_target.txt | 1 + .../star_needs_assignment_target_py35.py | 15 + .../star_needs_assignment_target_py35.rc | 2 + .../star_needs_assignment_target_py35.txt | 1 + .../functional/statement_without_effect.py | 65 + .../functional/statement_without_effect.txt | 60 + .../test/functional/string_formatting.py | 183 ++ .../test/functional/string_formatting.txt | 40 + .../functional/string_formatting_disable.py | 1 + .../functional/string_formatting_disable.rc | 3 + .../functional/string_formatting_disable.txt | 1 + .../string_formatting_failed_inference.py | 4 + .../test/functional/string_formatting_py27.py | 23 + .../test/functional/string_formatting_py27.rc | 3 + .../functional/string_formatting_py27.txt | 15 + .../pylint/test/functional/super_checks.py | 114 + .../pylint/test/functional/super_checks.txt | 19 + .../test/functional/superfluous_parens.py | 19 + .../test/functional/superfluous_parens.txt | 5 + .../functional/suspicious_str_strip_call.py | 9 + .../functional/suspicious_str_strip_call.rc | 2 + .../functional/suspicious_str_strip_call.txt | 3 + .../suspicious_str_strip_call_py3.py | 9 + .../suspicious_str_strip_call_py3.rc | 2 + .../suspicious_str_strip_call_py3.txt | 3 + .../pylint/test/functional/syntax_error.py | 1 + .../pylint/test/functional/syntax_error.rc | 2 + .../pylint/test/functional/syntax_error.txt | 1 + .../test/functional/syntax_error_jython.py | 1 + .../test/functional/syntax_error_jython.rc | 2 + .../test/functional/syntax_error_jython.txt | 1 + .../pylint/test/functional/tokenize_error.py | 6 + .../pylint/test/functional/tokenize_error.rc | 2 + .../pylint/test/functional/tokenize_error.txt | 1 + .../test/functional/tokenize_error_jython.py | 7 + .../test/functional/tokenize_error_jython.rc | 2 + .../test/functional/tokenize_error_jython.txt | 1 + .../test/functional/too_few_public_methods.py | 29 + .../functional/too_few_public_methods.txt | 1 + .../test/functional/too_many_ancestors.py | 25 + .../test/functional/too_many_ancestors.txt | 2 + .../test/functional/too_many_arguments.py | 4 + .../test/functional/too_many_arguments.txt | 1 + .../too_many_boolean_expressions.py | 20 + .../too_many_boolean_expressions.txt | 4 + .../test/functional/too_many_branches.py | 69 + .../test/functional/too_many_branches.txt | 1 + .../too_many_instance_attributes.py | 26 + .../too_many_instance_attributes.txt | 1 + .../pylint/test/functional/too_many_lines.py | 1015 ++++++++ .../pylint/test/functional/too_many_lines.txt | 1 + .../functional/too_many_lines_disabled.py | 1018 ++++++++ .../pylint/test/functional/too_many_locals.py | 60 + .../test/functional/too_many_locals.txt | 3 + .../test/functional/too_many_nested_blocks.py | 95 + .../functional/too_many_nested_blocks.txt | 2 + .../functional/too_many_public_methods.py | 79 + .../functional/too_many_public_methods.txt | 1 + .../functional/too_many_return_statements.py | 24 + .../functional/too_many_return_statements.txt | 1 + .../functional/too_many_star_expressions.py | 4 + .../functional/too_many_star_expressions.rc | 2 + .../functional/too_many_star_expressions.txt | 1 + .../test/functional/too_many_statements.py | 60 + .../test/functional/too_many_statements.txt | 1 + .../test/functional/trailing_whitespaces.py | 11 + .../test/functional/trailing_whitespaces.txt | 3 + .../functional/unbalanced_tuple_unpacking.py | 108 + .../functional/unbalanced_tuple_unpacking.txt | 6 + .../unbalanced_tuple_unpacking_py30.py | 11 + .../unbalanced_tuple_unpacking_py30.rc | 2 + .../test/functional/undefined_variable.py | 177 ++ .../test/functional/undefined_variable.txt | 24 + .../functional/undefined_variable_py30.py | 78 + .../functional/undefined_variable_py30.rc | 2 + .../functional/undefined_variable_py30.txt | 8 + .../unexpected_special_method_signature.py | 120 + .../unexpected_special_method_signature.txt | 16 + .../test/functional/ungrouped_imports.py | 20 + .../test/functional/ungrouped_imports.txt | 5 + .../test/functional/unidiomatic_typecheck.py | 76 + .../test/functional/unidiomatic_typecheck.txt | 18 + .../test/functional/uninferable_all_object.py | 9 + .../functional/unknown_encoding_jython.py | 7 + .../functional/unknown_encoding_jython.rc | 2 + .../functional/unknown_encoding_jython.txt | 1 + .../test/functional/unknown_encoding_py29.py | 7 + .../test/functional/unknown_encoding_py29.rc | 3 + .../test/functional/unknown_encoding_py29.txt | 1 + .../test/functional/unknown_encoding_pypy.py | 7 + .../test/functional/unknown_encoding_pypy.rc | 3 + .../test/functional/unknown_encoding_pypy.txt | 1 + .../test/functional/unnecessary_lambda.py | 51 + .../test/functional/unnecessary_lambda.txt | 7 + .../test/functional/unnecessary_pass.py | 7 + .../test/functional/unnecessary_pass.txt | 1 + .../pylint/test/functional/unneeded_not.py | 64 + .../pylint/test/functional/unneeded_not.txt | 14 + .../test/functional/unpacked_exceptions.py | 11 + .../test/functional/unpacked_exceptions.rc | 5 + .../test/functional/unpacked_exceptions.txt | 4 + .../libs/pylint/test/functional/unpacking.py | 11 + .../functional/unpacking_generalizations.py | 29 + .../functional/unpacking_generalizations.rc | 2 + .../functional/unpacking_generalizations.txt | 6 + .../test/functional/unpacking_non_sequence.py | 119 + .../functional/unpacking_non_sequence.txt | 10 + .../pylint/test/functional/unreachable.py | 22 + .../pylint/test/functional/unreachable.txt | 4 + .../functional/unrecognized_inline_option.py | 3 + .../functional/unrecognized_inline_option.txt | 1 + .../test/functional/unsubscriptable_value.py | 115 + .../test/functional/unsubscriptable_value.txt | 14 + .../functional/unsubscriptable_value_35.py | 5 + .../functional/unsubscriptable_value_35.rc | 2 + .../unsupported_binary_operation.py | 59 + .../unsupported_binary_operation.rc | 2 + .../unsupported_binary_operation.txt | 21 + .../pylint/test/functional/unused_import.py | 18 + .../pylint/test/functional/unused_import.txt | 8 + .../used_before_assignment_nonlocal.py | 47 + .../used_before_assignment_nonlocal.rc | 2 + .../used_before_assignment_nonlocal.txt | 6 + .../test/functional/useless_else_on_loop.py | 55 + .../test/functional/useless_else_on_loop.txt | 5 + .../test/functional/using_constant_test.py | 140 ++ .../test/functional/using_constant_test.txt | 22 + .../pylint/test/functional/wildcard_import.py | 5 + .../test/functional/wildcard_import.txt | 2 + .../functional/with_used_before_assign.py | 11 + .../functional/with_used_before_assign.txt | 2 + .../test/functional/with_using_generator.py | 14 + .../test/functional/with_using_generator.txt | 1 + .../test/functional/wrong_import_order.py | 15 + .../test/functional/wrong_import_order.txt | 3 + .../test/functional/wrong_import_position.py | 33 + .../test/functional/wrong_import_position.txt | 3 + .../functional/yield_from_iterable_py33.py | 7 + .../functional/yield_from_iterable_py33.rc | 2 + .../functional/yield_from_iterable_py33.txt | 1 + .../functional/yield_from_outside_func.py | 4 + .../functional/yield_from_outside_func.rc | 2 + .../functional/yield_from_outside_func.txt | 1 + .../functional/yield_inside_async_function.py | 12 + .../functional/yield_inside_async_function.rc | 2 + .../yield_inside_async_function.txt | 2 + .../test/functional/yield_outside_func.py | 4 + .../test/functional/yield_outside_func.txt | 1 + pymode/libs/pylint/test/input/__init__.py | 1 + .../test/input/func_3k_removed_stuff_py_30.py | 13 + .../func_bad_assigment_to_exception_var.py | 30 + .../test/input/func_bad_cont_dictcomp_py27.py | 38 + .../test/input/func_block_disable_msg.py | 1026 ++++++++ .../func_break_or_return_in_try_finally.py | 44 + .../libs/pylint/test/input/func_bug113231.py | 24 + .../test/input/func_disable_linebased.py | 14 + .../pylint/test/input/func_dotted_ancestor.py | 11 + pymode/libs/pylint/test/input/func_e0012.py | 5 + pymode/libs/pylint/test/input/func_e0203.py | 19 + pymode/libs/pylint/test/input/func_e0204.py | 19 + pymode/libs/pylint/test/input/func_e0601.py | 9 + pymode/libs/pylint/test/input/func_e0604.py | 13 + pymode/libs/pylint/test/input/func_e12xx.py | 28 + pymode/libs/pylint/test/input/func_e13xx.py | 21 + .../pylint/test/input/func_excess_escapes.py | 30 + .../libs/pylint/test/input/func_first_arg.py | 42 + pymode/libs/pylint/test/input/func_i0011.py | 5 + pymode/libs/pylint/test/input/func_i0012.py | 5 + pymode/libs/pylint/test/input/func_i0013.py | 8 + pymode/libs/pylint/test/input/func_i0014.py | 8 + pymode/libs/pylint/test/input/func_i0020.py | 8 + pymode/libs/pylint/test/input/func_i0022.py | 22 + .../libs/pylint/test/input/func_init_vars.py | 45 + .../libs/pylint/test/input/func_kwoa_py30.py | 12 + .../func_logging_not_lazy_with_logger.py | 13 + .../input/func_loopvar_in_dict_comp_py27.py | 8 + .../pylint/test/input/func_module___dict__.py | 9 + .../func_nameerror_on_string_substitution.py | 8 + .../test/input/func_no_dummy_redefined.py | 14 + ...ror___init___return_from_inner_function.py | 13 + ...r_access_attr_before_def_false_positive.py | 98 + .../test/input/func_noerror_base_init_vars.py | 35 + .../input/func_noerror_builtin_module_test.py | 11 + .../input/func_noerror_class_attributes.py | 17 + ...oerror_classes_meth_could_be_a_function.py | 34 + ...noerror_classes_protected_member_access.py | 26 + .../test/input/func_noerror_crash_127416.py | 20 + .../input/func_noerror_decorator_scope.py | 19 + ...noerror_e1101_9588_base_attr_aug_assign.py | 39 + .../test/input/func_noerror_encoding.py | 6 + .../test/input/func_noerror_except_pass.py | 12 + .../test/input/func_noerror_exception.py | 7 + ...func_noerror_external_classmethod_crash.py | 21 + .../input/func_noerror_function_as_method.py | 18 + .../func_noerror_genexp_in_class_scope.py | 9 + .../test/input/func_noerror_inner_classes.py | 33 + .../func_noerror_lambda_use_before_assign.py | 8 + .../test/input/func_noerror_long_utf8_line.py | 9 + .../input/func_noerror_mcs_attr_access.py | 20 + .../func_noerror_new_style_class_py_30.py | 45 + .../func_noerror_no_warning_docstring.py | 42 + .../pylint/test/input/func_noerror_nonregr.py | 13 + .../func_noerror_object_as_class_attribute.py | 19 + .../input/func_noerror_overloaded_operator.py | 21 + .../func_noerror_overriden_method_varargs.py | 19 + .../func_noerror_property_affectation_py26.py | 24 + .../input/func_noerror_raise_return_self.py | 15 + .../test/input/func_noerror_static_method.py | 28 + .../input/func_noerror_yield_assign_py25.py | 21 + .../input/func_noerror_yield_return_mix.py | 7 + .../input/func_nonregr___file___global.py | 8 + .../test/input/func_return_yield_mix_py_33.py | 16 + .../input/func_too_many_returns_yields.py | 42 + .../func_typecheck_callfunc_assigment.py | 63 + .../test/input/func_unused_import_py30.py | 21 + .../input/func_unused_overridden_argument.py | 31 + .../input/func_use_for_or_listcomp_var.py | 27 + ...riables_unused_name_from_wilcard_import.py | 3 + .../pylint/test/input/func_w0122_py_30.py | 13 + pymode/libs/pylint/test/input/func_w0205.py | 24 + pymode/libs/pylint/test/input/func_w0233.py | 50 + .../pylint/test/input/func_w0332_py_30.py | 4 + pymode/libs/pylint/test/input/func_w0401.py | 9 + .../test/input/func_w0401_package/__init__.py | 2 + .../func_w0401_package/all_the_things.py | 9 + .../test/input/func_w0401_package/thing1.py | 3 + .../test/input/func_w0401_package/thing2.py | 7 + pymode/libs/pylint/test/input/func_w0404.py | 27 + pymode/libs/pylint/test/input/func_w0405.py | 31 + pymode/libs/pylint/test/input/func_w0406.py | 10 + pymode/libs/pylint/test/input/func_w0611.py | 25 + pymode/libs/pylint/test/input/func_w0612.py | 37 + pymode/libs/pylint/test/input/func_w0613.py | 42 + .../libs/pylint/test/input/func_w0623_py30.py | 16 + .../pylint/test/input/func_w0623_py_30.py | 67 + pymode/libs/pylint/test/input/func_w0631.py | 20 + pymode/libs/pylint/test/input/func_w0703.py | 9 + pymode/libs/pylint/test/input/func_w0705.py | 46 + pymode/libs/pylint/test/input/func_w0801.py | 11 + .../test/input/func_with_without_as_py25.py | 12 + .../input/ignore_except_pass_by_default.py | 6 + pymode/libs/pylint/test/input/noext | 4 + pymode/libs/pylint/test/input/similar1 | 22 + pymode/libs/pylint/test/input/similar2 | 22 + pymode/libs/pylint/test/input/syntax_error.py | 2 + pymode/libs/pylint/test/input/w0401_cycle.py | 9 + pymode/libs/pylint/test/input/w0801_same.py | 11 + .../pylint/test/messages/builtin_module.txt | 1 + .../messages/func_3k_removed_stuff_py_30.txt | 4 + .../func_bad_assigment_to_exception_var.txt | 5 + .../messages/func_bad_cont_dictcomp_py27.txt | 6 + .../test/messages/func_block_disable_msg.txt | 13 + .../func_break_or_return_in_try_finally.txt | 3 + .../pylint/test/messages/func_bug113231.txt | 2 + .../test/messages/func_disable_linebased.txt | 2 + .../messages/func_disable_linebased_py30.txt | 2 + .../test/messages/func_dotted_ancestor.txt | 1 + .../libs/pylint/test/messages/func_e0012.txt | 1 + .../libs/pylint/test/messages/func_e0203.txt | 2 + .../libs/pylint/test/messages/func_e0204.txt | 3 + .../libs/pylint/test/messages/func_e0601.txt | 1 + .../libs/pylint/test/messages/func_e0604.txt | 1 + .../libs/pylint/test/messages/func_e12xx.txt | 6 + .../libs/pylint/test/messages/func_e13xx.txt | 13 + .../pylint/test/messages/func_e13xx_py30.txt | 11 + .../test/messages/func_excess_escapes.txt | 9 + .../pylint/test/messages/func_first_arg.txt | 9 + .../libs/pylint/test/messages/func_i0011.txt | 2 + .../libs/pylint/test/messages/func_i0012.txt | 1 + .../libs/pylint/test/messages/func_i0013.txt | 1 + .../libs/pylint/test/messages/func_i0014.txt | 2 + .../libs/pylint/test/messages/func_i0020.txt | 2 + .../libs/pylint/test/messages/func_i0022.txt | 21 + .../pylint/test/messages/func_init_vars.txt | 1 + .../pylint/test/messages/func_kwoa_py30.txt | 5 + .../func_logging_not_lazy_with_logger.txt | 4 + .../func_loopvar_in_dict_comp_py27.txt | 1 + .../test/messages/func_module___dict__.txt | 1 + .../func_nameerror_on_string_substitution.txt | 2 + .../test/messages/func_no_dummy_redefined.txt | 2 + .../messages/func_nonregr___file___global.txt | 2 + .../pylint/test/messages/func_raw_escapes.txt | 3 + .../messages/func_return_yield_mix_py_33.txt | 2 + .../messages/func_too_many_returns_yields.txt | 1 + .../test/messages/func_toolonglines_py30.txt | 4 + .../func_typecheck_callfunc_assigment.txt | 3 + .../messages/func_typecheck_getattr_py30.txt | 9 + .../func_typecheck_non_callable_call.txt | 8 + .../messages/func_unicode_literal_py26.txt | 0 .../messages/func_unicode_literal_py274.txt | 1 + .../test/messages/func_unused_import_py30.txt | 1 + .../func_unused_overridden_argument.txt | 1 + .../func_use_for_or_listcomp_var_py29.txt | 3 + .../func_use_for_or_listcomp_var_py30.txt | 3 + ...iables_unused_name_from_wilcard_import.txt | 4 + .../pylint/test/messages/func_w0122_py_30.txt | 5 + .../libs/pylint/test/messages/func_w0205.txt | 2 + .../libs/pylint/test/messages/func_w0233.txt | 6 + .../libs/pylint/test/messages/func_w0312.txt | 2 + .../pylint/test/messages/func_w0332_py_30.txt | 1 + .../libs/pylint/test/messages/func_w0401.txt | 3 + .../test/messages/func_w0401_package.txt | 1 + .../libs/pylint/test/messages/func_w0404.txt | 5 + .../libs/pylint/test/messages/func_w0405.txt | 4 + .../libs/pylint/test/messages/func_w0406.txt | 1 + .../libs/pylint/test/messages/func_w0611.txt | 1 + .../libs/pylint/test/messages/func_w0612.txt | 5 + .../libs/pylint/test/messages/func_w0613.txt | 5 + .../libs/pylint/test/messages/func_w0622.txt | 2 + .../libs/pylint/test/messages/func_w0623.txt | 11 + .../pylint/test/messages/func_w0623_py30.txt | 2 + .../pylint/test/messages/func_w0623_py_30.txt | 12 + .../libs/pylint/test/messages/func_w0631.txt | 1 + .../libs/pylint/test/messages/func_w0703.txt | 1 + .../libs/pylint/test/messages/func_w0705.txt | 11 + .../libs/pylint/test/messages/func_w0801.txt | 11 + .../messages/func_with_without_as_py25.txt | 3 + .../test/regrtest_data/absimp/__init__.py | 5 + .../test/regrtest_data/absimp/string.py | 7 + .../test/regrtest_data/application_crash.py | 12 + .../regrtest_data/bad_package/__init__.py | 2 + .../test/regrtest_data/bad_package/wrong.py | 5 + .../test/regrtest_data/classdoc_usage.py | 17 + .../test/regrtest_data/decimal_inference.py | 10 + .../test/regrtest_data/descriptor_crash.py | 20 + .../test/regrtest_data/dummy/__init__.py | 3 + .../test/regrtest_data/dummy/another.py | 4 + .../pylint/test/regrtest_data/dummy/dummy.py | 2 + .../test/regrtest_data/html_crash_420.py | 5 + .../test/regrtest_data/import_assign.py | 5 + .../import_package_subpackage_module.py | 49 + .../test/regrtest_data/import_something.py | 4 + .../test/regrtest_data/module_global.py | 7 + .../test/regrtest_data/no_stdout_encoding.py | 5 + .../test/regrtest_data/numarray_import.py | 7 + .../pylint/test/regrtest_data/numarray_inf.py | 5 + .../test/regrtest_data/package/AudioTime.py | 3 + .../test/regrtest_data/package/__init__.py | 15 + .../package/subpackage/__init__.py | 1 + .../package/subpackage/module.py | 1 + .../regrtest_data/package_all/__init__.py | 3 + .../regrtest_data/package_all/notmissing.py | 2 + .../test/regrtest_data/precedence_test.py | 21 + .../test/regrtest_data/py3k_error_flag.py | 11 + .../special_attr_scope_lookup_crash.py | 3 + .../pylint/test/regrtest_data/syntax_error.py | 1 + .../try_finally_disable_msg_crash.py | 5 + .../regrtest_data/wrong_import_position.py | 11 + pymode/libs/pylint/test/test_func.py | 85 + pymode/libs/pylint/test/test_functional.py | 369 +++ pymode/libs/pylint/test/test_import_graph.py | 68 + pymode/libs/pylint/test/test_regr.py | 149 ++ pymode/libs/pylint/test/test_self.py | 352 +++ .../libs/pylint/test/unittest_checker_base.py | 305 +++ .../pylint/test/unittest_checker_classes.py | 84 + .../test/unittest_checker_exceptions.py | 69 + .../pylint/test/unittest_checker_format.py | 262 ++ .../pylint/test/unittest_checker_imports.py | 75 + .../pylint/test/unittest_checker_logging.py | 47 + .../libs/pylint/test/unittest_checker_misc.py | 49 + .../pylint/test/unittest_checker_python3.py | 441 ++++ .../pylint/test/unittest_checker_similar.py | 142 ++ .../pylint/test/unittest_checker_spelling.py | 93 + .../pylint/test/unittest_checker_stdlib.py | 65 + .../pylint/test/unittest_checker_strings.py | 41 + .../pylint/test/unittest_checker_typecheck.py | 94 + .../pylint/test/unittest_checker_variables.py | 109 + .../pylint/test/unittest_checkers_utils.py | 128 + pymode/libs/pylint/test/unittest_lint.py | 691 ++++++ .../pylint/test/unittest_pyreverse_diadefs.py | 171 ++ .../test/unittest_pyreverse_inspector.py | 136 ++ .../pylint/test/unittest_pyreverse_writer.py | 134 ++ .../pylint/test/unittest_reporters_json.py | 62 + pymode/libs/pylint/test/unittest_reporting.py | 194 ++ pymode/libs/pylint/test/unittest_utils.py | 94 + pymode/libs/pylint/testutils.py | 42 +- pymode/libs/pylint/utils.py | 312 ++- 892 files changed, 42268 insertions(+), 4185 deletions(-) create mode 100644 pymode/libs/astroid/arguments.py rename pymode/libs/astroid/brain/{builtin_inference.py => brain_builtin_inference.py} (63%) create mode 100644 pymode/libs/astroid/brain/brain_dateutil.py rename pymode/libs/astroid/brain/{py2gi.py => brain_gi.py} (72%) rename pymode/libs/astroid/brain/{py2mechanize.py => brain_mechanize.py} (100%) rename pymode/libs/astroid/brain/{pynose.py => brain_nose.py} (92%) create mode 100644 pymode/libs/astroid/brain/brain_numpy.py rename pymode/libs/astroid/brain/{py2pytest.py => brain_pytest.py} (92%) create mode 100644 pymode/libs/astroid/brain/brain_qt.py rename pymode/libs/astroid/brain/{pysix_moves.py => brain_six.py} (91%) create mode 100644 pymode/libs/astroid/brain/brain_stdlib.py delete mode 100644 pymode/libs/astroid/brain/py2qt4.py delete mode 100644 pymode/libs/astroid/brain/py2stdlib.py create mode 100644 pymode/libs/astroid/context.py create mode 100644 pymode/libs/astroid/decorators.py delete mode 100644 pymode/libs/astroid/inspector.py create mode 100644 pymode/libs/astroid/objects.py create mode 100644 pymode/libs/astroid/tests/__init__.py create mode 100644 pymode/libs/astroid/tests/resources.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.egg create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.zip create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/SSL1/Connection1.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/SSL1/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/absimp/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/absimp/sidepackage/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/absimp/string.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/absimport.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/all.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/appl/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/appl/myConnection.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/clientmodule_test.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/descriptor_crash.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/email.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/find_test/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/find_test/module.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/find_test/module2.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/find_test/noendingnewline.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/find_test/nonregr.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/format.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/joined_strings.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/lmfp/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/lmfp/foo.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/module.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/module1abs/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/module1abs/core.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/module2.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/noendingnewline.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/nonregr.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/notall.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/package/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/package/absimport.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/package/hello.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/package/import_package_subpackage_module.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/package/subpackage/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/package/subpackage/module.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/recursion.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/suppliermodule_test.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/unicode_package/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python2/data/unicode_package/core/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/MyPyPa-0.1.0-py2.5.egg create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/MyPyPa-0.1.0-py2.5.zip create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/SSL1/Connection1.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/SSL1/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/absimp/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/absimp/sidepackage/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/absimp/string.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/absimport.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/all.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/appl/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/appl/myConnection.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/clientmodule_test.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/descriptor_crash.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/email.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/find_test/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/find_test/module.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/find_test/module2.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/find_test/noendingnewline.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/find_test/nonregr.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/format.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/joined_strings.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/lmfp/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/lmfp/foo.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/module.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/module1abs/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/module1abs/core.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/module2.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/noendingnewline.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/nonregr.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/notall.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/package/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/package/absimport.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/package/hello.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/package/import_package_subpackage_module.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/package/subpackage/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/package/subpackage/module.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/recursion.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/suppliermodule_test.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/unicode_package/__init__.py create mode 100644 pymode/libs/astroid/tests/testdata/python3/data/unicode_package/core/__init__.py create mode 100644 pymode/libs/astroid/tests/unittest_brain.py create mode 100644 pymode/libs/astroid/tests/unittest_builder.py create mode 100644 pymode/libs/astroid/tests/unittest_inference.py create mode 100644 pymode/libs/astroid/tests/unittest_lookup.py create mode 100644 pymode/libs/astroid/tests/unittest_manager.py create mode 100644 pymode/libs/astroid/tests/unittest_modutils.py create mode 100644 pymode/libs/astroid/tests/unittest_nodes.py create mode 100644 pymode/libs/astroid/tests/unittest_objects.py create mode 100644 pymode/libs/astroid/tests/unittest_peephole.py create mode 100644 pymode/libs/astroid/tests/unittest_protocols.py create mode 100644 pymode/libs/astroid/tests/unittest_python3.py create mode 100644 pymode/libs/astroid/tests/unittest_raw_building.py create mode 100644 pymode/libs/astroid/tests/unittest_regrtest.py create mode 100644 pymode/libs/astroid/tests/unittest_scoped_nodes.py create mode 100644 pymode/libs/astroid/tests/unittest_transforms.py create mode 100644 pymode/libs/astroid/tests/unittest_utils.py create mode 100644 pymode/libs/astroid/transforms.py create mode 100644 pymode/libs/astroid/util.py delete mode 100644 pymode/libs/astroid/utils.py create mode 100644 pymode/libs/pylint/checkers/async.py create mode 100644 pymode/libs/pylint/extensions/__init__.py create mode 100644 pymode/libs/pylint/extensions/check_docs.py create mode 100644 pymode/libs/pylint/extensions/check_elif.py create mode 100644 pymode/libs/pylint/graph.py create mode 100644 pymode/libs/pylint/pyreverse/inspector.py create mode 100644 pymode/libs/pylint/pyreverse/vcgutils.py create mode 100644 pymode/libs/pylint/reporters/ureports/__init__.py create mode 100644 pymode/libs/pylint/reporters/ureports/html_writer.py create mode 100644 pymode/libs/pylint/reporters/ureports/nodes.py create mode 100644 pymode/libs/pylint/reporters/ureports/text_writer.py create mode 100644 pymode/libs/pylint/test/data/__init__.py create mode 100644 pymode/libs/pylint/test/data/ascript create mode 100644 pymode/libs/pylint/test/data/classes_No_Name.dot create mode 100644 pymode/libs/pylint/test/data/clientmodule_test.py create mode 100644 pymode/libs/pylint/test/data/packages_No_Name.dot create mode 100644 pymode/libs/pylint/test/data/suppliermodule_test.py create mode 100644 pymode/libs/pylint/test/extensions/__init__.py create mode 100644 pymode/libs/pylint/test/extensions/data/elif.py create mode 100644 pymode/libs/pylint/test/extensions/test_check_docs.py create mode 100644 pymode/libs/pylint/test/extensions/test_elseif_used.py create mode 100644 pymode/libs/pylint/test/functional/__init__.py create mode 100644 pymode/libs/pylint/test/functional/abstract_abc_methods.py create mode 100644 pymode/libs/pylint/test/functional/abstract_class_instantiated_in_class.py create mode 100644 pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.py create mode 100644 pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.rc create mode 100644 pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.txt create mode 100644 pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.py create mode 100644 pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.rc create mode 100644 pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.txt create mode 100644 pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.py create mode 100644 pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.rc create mode 100644 pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.txt create mode 100644 pymode/libs/pylint/test/functional/abstract_method_py2.py create mode 100644 pymode/libs/pylint/test/functional/abstract_method_py2.rc create mode 100644 pymode/libs/pylint/test/functional/abstract_method_py2.txt create mode 100644 pymode/libs/pylint/test/functional/abstract_method_py3.py create mode 100644 pymode/libs/pylint/test/functional/abstract_method_py3.rc create mode 100644 pymode/libs/pylint/test/functional/abstract_method_py3.txt create mode 100644 pymode/libs/pylint/test/functional/access_member_before_definition.py create mode 100644 pymode/libs/pylint/test/functional/access_member_before_definition.txt create mode 100644 pymode/libs/pylint/test/functional/access_to__name__.py create mode 100644 pymode/libs/pylint/test/functional/access_to__name__.txt create mode 100644 pymode/libs/pylint/test/functional/access_to_protected_members.py create mode 100644 pymode/libs/pylint/test/functional/access_to_protected_members.txt create mode 100644 pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.py create mode 100644 pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.rc create mode 100644 pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.txt create mode 100644 pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.py create mode 100644 pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.rc create mode 100644 pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.txt create mode 100644 pymode/libs/pylint/test/functional/arguments.py create mode 100644 pymode/libs/pylint/test/functional/arguments.txt create mode 100644 pymode/libs/pylint/test/functional/arguments_differ.py create mode 100644 pymode/libs/pylint/test/functional/arguments_differ.txt create mode 100644 pymode/libs/pylint/test/functional/assert_on_tuple.py create mode 100644 pymode/libs/pylint/test/functional/assert_on_tuple.txt create mode 100644 pymode/libs/pylint/test/functional/assigning_non_slot.py create mode 100644 pymode/libs/pylint/test/functional/assigning_non_slot.txt create mode 100644 pymode/libs/pylint/test/functional/async_functions.py create mode 100644 pymode/libs/pylint/test/functional/async_functions.rc create mode 100644 pymode/libs/pylint/test/functional/async_functions.txt create mode 100644 pymode/libs/pylint/test/functional/attribute_defined_outside_init.py create mode 100644 pymode/libs/pylint/test/functional/attribute_defined_outside_init.txt create mode 100644 pymode/libs/pylint/test/functional/bad_builtin.py create mode 100644 pymode/libs/pylint/test/functional/bad_builtin.txt create mode 100644 pymode/libs/pylint/test/functional/bad_continuation.py create mode 100644 pymode/libs/pylint/test/functional/bad_continuation.txt create mode 100644 pymode/libs/pylint/test/functional/bad_exception_context.py create mode 100644 pymode/libs/pylint/test/functional/bad_exception_context.rc create mode 100644 pymode/libs/pylint/test/functional/bad_exception_context.txt create mode 100644 pymode/libs/pylint/test/functional/bad_indentation.py create mode 100644 pymode/libs/pylint/test/functional/bad_indentation.txt create mode 100644 pymode/libs/pylint/test/functional/bad_inline_option.py create mode 100644 pymode/libs/pylint/test/functional/bad_inline_option.rc create mode 100644 pymode/libs/pylint/test/functional/bad_inline_option.txt create mode 100644 pymode/libs/pylint/test/functional/bad_open_mode.py create mode 100644 pymode/libs/pylint/test/functional/bad_open_mode.rc create mode 100644 pymode/libs/pylint/test/functional/bad_open_mode.txt create mode 100644 pymode/libs/pylint/test/functional/bad_open_mode_py3.py create mode 100644 pymode/libs/pylint/test/functional/bad_open_mode_py3.rc create mode 100644 pymode/libs/pylint/test/functional/bad_open_mode_py3.txt create mode 100644 pymode/libs/pylint/test/functional/bad_reversed_sequence.py create mode 100644 pymode/libs/pylint/test/functional/bad_reversed_sequence.txt create mode 100644 pymode/libs/pylint/test/functional/bad_staticmethod_argument.py create mode 100644 pymode/libs/pylint/test/functional/bad_staticmethod_argument.txt create mode 100644 pymode/libs/pylint/test/functional/bad_whitespace.py create mode 100644 pymode/libs/pylint/test/functional/bad_whitespace.txt create mode 100644 pymode/libs/pylint/test/functional/bare_except.py create mode 100644 pymode/libs/pylint/test/functional/bare_except.txt create mode 100644 pymode/libs/pylint/test/functional/blacklisted_name.py create mode 100644 pymode/libs/pylint/test/functional/blacklisted_name.txt create mode 100644 pymode/libs/pylint/test/functional/boolean_datetime.py create mode 100644 pymode/libs/pylint/test/functional/boolean_datetime.rc create mode 100644 pymode/libs/pylint/test/functional/boolean_datetime.txt create mode 100644 pymode/libs/pylint/test/functional/cellvar_escaping_loop.py create mode 100644 pymode/libs/pylint/test/functional/cellvar_escaping_loop.txt create mode 100644 pymode/libs/pylint/test/functional/class_members_py27.py create mode 100644 pymode/libs/pylint/test/functional/class_members_py27.rc create mode 100644 pymode/libs/pylint/test/functional/class_members_py27.txt create mode 100644 pymode/libs/pylint/test/functional/class_members_py30.py create mode 100644 pymode/libs/pylint/test/functional/class_members_py30.rc create mode 100644 pymode/libs/pylint/test/functional/class_members_py30.txt create mode 100644 pymode/libs/pylint/test/functional/class_scope.py create mode 100644 pymode/libs/pylint/test/functional/class_scope.txt create mode 100644 pymode/libs/pylint/test/functional/confidence_filter.py create mode 100644 pymode/libs/pylint/test/functional/confidence_filter.rc create mode 100644 pymode/libs/pylint/test/functional/confidence_filter.txt create mode 100644 pymode/libs/pylint/test/functional/confusing_with_statement.py create mode 100644 pymode/libs/pylint/test/functional/confusing_with_statement.txt create mode 100644 pymode/libs/pylint/test/functional/consider_using_enumerate.py create mode 100644 pymode/libs/pylint/test/functional/consider_using_enumerate.txt create mode 100644 pymode/libs/pylint/test/functional/continue_in_finally.py create mode 100644 pymode/libs/pylint/test/functional/continue_in_finally.txt create mode 100644 pymode/libs/pylint/test/functional/crash_missing_module_type.py create mode 100644 pymode/libs/pylint/test/functional/crash_missing_module_type.txt create mode 100644 pymode/libs/pylint/test/functional/ctor_arguments.py create mode 100644 pymode/libs/pylint/test/functional/ctor_arguments.txt create mode 100644 pymode/libs/pylint/test/functional/dangerous_default_value.py create mode 100644 pymode/libs/pylint/test/functional/dangerous_default_value.rc create mode 100644 pymode/libs/pylint/test/functional/dangerous_default_value.txt create mode 100644 pymode/libs/pylint/test/functional/dangerous_default_value_py30.py create mode 100644 pymode/libs/pylint/test/functional/dangerous_default_value_py30.rc create mode 100644 pymode/libs/pylint/test/functional/dangerous_default_value_py30.txt create mode 100644 pymode/libs/pylint/test/functional/defined_and_used_on_same_line.py create mode 100644 pymode/libs/pylint/test/functional/deprecated_lambda.py create mode 100644 pymode/libs/pylint/test/functional/deprecated_lambda.rc create mode 100644 pymode/libs/pylint/test/functional/deprecated_lambda.txt create mode 100644 pymode/libs/pylint/test/functional/deprecated_methods_py2.py create mode 100644 pymode/libs/pylint/test/functional/deprecated_methods_py2.rc create mode 100644 pymode/libs/pylint/test/functional/deprecated_methods_py2.txt create mode 100644 pymode/libs/pylint/test/functional/deprecated_methods_py3.py create mode 100644 pymode/libs/pylint/test/functional/deprecated_methods_py3.rc create mode 100644 pymode/libs/pylint/test/functional/deprecated_methods_py3.txt create mode 100644 pymode/libs/pylint/test/functional/deprecated_module_py2.py create mode 100644 pymode/libs/pylint/test/functional/deprecated_module_py2.rc create mode 100644 pymode/libs/pylint/test/functional/deprecated_module_py2.txt create mode 100644 pymode/libs/pylint/test/functional/deprecated_module_py3.py create mode 100644 pymode/libs/pylint/test/functional/deprecated_module_py3.rc create mode 100644 pymode/libs/pylint/test/functional/deprecated_module_py3.txt create mode 100644 pymode/libs/pylint/test/functional/deprecated_module_uninstalled.py create mode 100644 pymode/libs/pylint/test/functional/deprecated_module_uninstalled.rc create mode 100644 pymode/libs/pylint/test/functional/deprecated_module_uninstalled.txt create mode 100644 pymode/libs/pylint/test/functional/docstrings.py create mode 100644 pymode/libs/pylint/test/functional/docstrings.txt create mode 100644 pymode/libs/pylint/test/functional/duplicate_argument_name.py create mode 100644 pymode/libs/pylint/test/functional/duplicate_argument_name.txt create mode 100644 pymode/libs/pylint/test/functional/duplicate_bases.py create mode 100644 pymode/libs/pylint/test/functional/duplicate_bases.txt create mode 100644 pymode/libs/pylint/test/functional/duplicate_dict_literal_key.py create mode 100644 pymode/libs/pylint/test/functional/duplicate_dict_literal_key.txt create mode 100644 pymode/libs/pylint/test/functional/duplicate_except.py create mode 100644 pymode/libs/pylint/test/functional/duplicate_except.txt create mode 100644 pymode/libs/pylint/test/functional/eval_used.py create mode 100644 pymode/libs/pylint/test/functional/eval_used.txt create mode 100644 pymode/libs/pylint/test/functional/exception_is_binary_op.py create mode 100644 pymode/libs/pylint/test/functional/exception_is_binary_op.txt create mode 100644 pymode/libs/pylint/test/functional/exec_used_py2.py create mode 100644 pymode/libs/pylint/test/functional/exec_used_py2.rc create mode 100644 pymode/libs/pylint/test/functional/exec_used_py2.txt create mode 100644 pymode/libs/pylint/test/functional/exec_used_py3.py create mode 100644 pymode/libs/pylint/test/functional/exec_used_py3.rc create mode 100644 pymode/libs/pylint/test/functional/exec_used_py3.txt create mode 100644 pymode/libs/pylint/test/functional/fixme.py create mode 100644 pymode/libs/pylint/test/functional/fixme.txt create mode 100644 pymode/libs/pylint/test/functional/formatting.txt create mode 100644 pymode/libs/pylint/test/functional/function_redefined.py create mode 100644 pymode/libs/pylint/test/functional/function_redefined.txt create mode 100644 pymode/libs/pylint/test/functional/future_import.py create mode 100644 pymode/libs/pylint/test/functional/future_unicode_literals.py create mode 100644 pymode/libs/pylint/test/functional/future_unicode_literals.rc create mode 100644 pymode/libs/pylint/test/functional/future_unicode_literals.txt create mode 100644 pymode/libs/pylint/test/functional/generated_members.py create mode 100644 pymode/libs/pylint/test/functional/generated_members.rc create mode 100644 pymode/libs/pylint/test/functional/genexpr_variable_scope.py create mode 100644 pymode/libs/pylint/test/functional/genexpr_variable_scope.txt create mode 100644 pymode/libs/pylint/test/functional/globals.py create mode 100644 pymode/libs/pylint/test/functional/globals.txt create mode 100644 pymode/libs/pylint/test/functional/import_error.py create mode 100644 pymode/libs/pylint/test/functional/import_error.txt create mode 100644 pymode/libs/pylint/test/functional/inconsistent_mro.py create mode 100644 pymode/libs/pylint/test/functional/inconsistent_mro.txt create mode 100644 pymode/libs/pylint/test/functional/indexing_exception.py create mode 100644 pymode/libs/pylint/test/functional/indexing_exception.rc create mode 100644 pymode/libs/pylint/test/functional/indexing_exception.txt create mode 100644 pymode/libs/pylint/test/functional/inherit_non_class.py create mode 100644 pymode/libs/pylint/test/functional/inherit_non_class.txt create mode 100644 pymode/libs/pylint/test/functional/init_is_generator.py create mode 100644 pymode/libs/pylint/test/functional/init_is_generator.txt create mode 100644 pymode/libs/pylint/test/functional/init_not_called.py create mode 100644 pymode/libs/pylint/test/functional/init_not_called.txt create mode 100644 pymode/libs/pylint/test/functional/invalid_all_object.py create mode 100644 pymode/libs/pylint/test/functional/invalid_all_object.txt create mode 100644 pymode/libs/pylint/test/functional/invalid_encoded_data.py create mode 100644 pymode/libs/pylint/test/functional/invalid_encoded_data.rc create mode 100644 pymode/libs/pylint/test/functional/invalid_encoded_data.txt create mode 100644 pymode/libs/pylint/test/functional/invalid_exceptions_caught.py create mode 100644 pymode/libs/pylint/test/functional/invalid_exceptions_caught.txt create mode 100644 pymode/libs/pylint/test/functional/invalid_exceptions_raised.py create mode 100644 pymode/libs/pylint/test/functional/invalid_exceptions_raised.txt create mode 100644 pymode/libs/pylint/test/functional/invalid_name.py create mode 100644 pymode/libs/pylint/test/functional/invalid_name.txt create mode 100644 pymode/libs/pylint/test/functional/invalid_sequence_index.py create mode 100644 pymode/libs/pylint/test/functional/invalid_sequence_index.txt create mode 100644 pymode/libs/pylint/test/functional/invalid_slice_index.py create mode 100644 pymode/libs/pylint/test/functional/invalid_slice_index.txt create mode 100644 pymode/libs/pylint/test/functional/invalid_star_assignment_target.py create mode 100644 pymode/libs/pylint/test/functional/invalid_star_assignment_target.rc create mode 100644 pymode/libs/pylint/test/functional/invalid_star_assignment_target.txt create mode 100644 pymode/libs/pylint/test/functional/invalid_unary_operand_type.py create mode 100644 pymode/libs/pylint/test/functional/invalid_unary_operand_type.rc create mode 100644 pymode/libs/pylint/test/functional/invalid_unary_operand_type.txt create mode 100644 pymode/libs/pylint/test/functional/iterable_context.py create mode 100644 pymode/libs/pylint/test/functional/iterable_context.txt create mode 100644 pymode/libs/pylint/test/functional/iterable_context_py2.py create mode 100644 pymode/libs/pylint/test/functional/iterable_context_py2.rc create mode 100644 pymode/libs/pylint/test/functional/iterable_context_py2.txt create mode 100644 pymode/libs/pylint/test/functional/iterable_context_py3.py create mode 100644 pymode/libs/pylint/test/functional/iterable_context_py3.rc create mode 100644 pymode/libs/pylint/test/functional/iterable_context_py3.txt create mode 100644 pymode/libs/pylint/test/functional/line_endings.py create mode 100644 pymode/libs/pylint/test/functional/line_endings.rc create mode 100644 pymode/libs/pylint/test/functional/line_endings.txt create mode 100644 pymode/libs/pylint/test/functional/line_too_long.py create mode 100644 pymode/libs/pylint/test/functional/line_too_long.txt create mode 100644 pymode/libs/pylint/test/functional/logging_format_interpolation.py create mode 100644 pymode/libs/pylint/test/functional/logging_format_interpolation.txt create mode 100644 pymode/libs/pylint/test/functional/logging_not_lazy.py create mode 100644 pymode/libs/pylint/test/functional/logging_not_lazy.txt create mode 100644 pymode/libs/pylint/test/functional/long_lines_with_utf8.py create mode 100644 pymode/libs/pylint/test/functional/long_lines_with_utf8.txt create mode 100644 pymode/libs/pylint/test/functional/mapping_context.py create mode 100644 pymode/libs/pylint/test/functional/mapping_context.txt create mode 100644 pymode/libs/pylint/test/functional/mapping_context_py2.py create mode 100644 pymode/libs/pylint/test/functional/mapping_context_py2.rc create mode 100644 pymode/libs/pylint/test/functional/mapping_context_py2.txt create mode 100644 pymode/libs/pylint/test/functional/mapping_context_py3.py create mode 100644 pymode/libs/pylint/test/functional/mapping_context_py3.rc create mode 100644 pymode/libs/pylint/test/functional/mapping_context_py3.txt create mode 100644 pymode/libs/pylint/test/functional/member_checks.py create mode 100644 pymode/libs/pylint/test/functional/member_checks.txt create mode 100644 pymode/libs/pylint/test/functional/membership_protocol.py create mode 100644 pymode/libs/pylint/test/functional/membership_protocol.txt create mode 100644 pymode/libs/pylint/test/functional/membership_protocol_py2.py create mode 100644 pymode/libs/pylint/test/functional/membership_protocol_py2.rc create mode 100644 pymode/libs/pylint/test/functional/membership_protocol_py2.txt create mode 100644 pymode/libs/pylint/test/functional/membership_protocol_py3.py create mode 100644 pymode/libs/pylint/test/functional/membership_protocol_py3.rc create mode 100644 pymode/libs/pylint/test/functional/membership_protocol_py3.txt create mode 100644 pymode/libs/pylint/test/functional/method_hidden.py create mode 100644 pymode/libs/pylint/test/functional/method_hidden.txt create mode 100644 pymode/libs/pylint/test/functional/misplaced_bare_raise.py create mode 100644 pymode/libs/pylint/test/functional/misplaced_bare_raise.txt create mode 100644 pymode/libs/pylint/test/functional/misplaced_comparison_constant.py create mode 100644 pymode/libs/pylint/test/functional/misplaced_comparison_constant.txt create mode 100644 pymode/libs/pylint/test/functional/misplaced_future.py create mode 100644 pymode/libs/pylint/test/functional/misplaced_future.txt create mode 100644 pymode/libs/pylint/test/functional/missing_docstring.py create mode 100644 pymode/libs/pylint/test/functional/missing_docstring.txt create mode 100644 pymode/libs/pylint/test/functional/missing_final_newline.py create mode 100644 pymode/libs/pylint/test/functional/missing_final_newline.txt create mode 100644 pymode/libs/pylint/test/functional/missing_self_argument.py create mode 100644 pymode/libs/pylint/test/functional/missing_self_argument.txt create mode 100644 pymode/libs/pylint/test/functional/mixed_indentation.py create mode 100644 pymode/libs/pylint/test/functional/mixed_indentation.txt create mode 100644 pymode/libs/pylint/test/functional/multiple_imports.py create mode 100644 pymode/libs/pylint/test/functional/multiple_imports.txt create mode 100644 pymode/libs/pylint/test/functional/name_styles.py create mode 100644 pymode/libs/pylint/test/functional/name_styles.rc create mode 100644 pymode/libs/pylint/test/functional/name_styles.txt create mode 100644 pymode/libs/pylint/test/functional/namedtuple_member_inference.py create mode 100644 pymode/libs/pylint/test/functional/namedtuple_member_inference.txt create mode 100644 pymode/libs/pylint/test/functional/names_in__all__.py create mode 100644 pymode/libs/pylint/test/functional/names_in__all__.txt create mode 100644 pymode/libs/pylint/test/functional/newstyle__slots__.py create mode 100644 pymode/libs/pylint/test/functional/newstyle__slots__.txt create mode 100644 pymode/libs/pylint/test/functional/newstyle_properties.py create mode 100644 pymode/libs/pylint/test/functional/newstyle_properties.txt create mode 100644 pymode/libs/pylint/test/functional/no_classmethod_decorator.py create mode 100644 pymode/libs/pylint/test/functional/no_classmethod_decorator.txt create mode 100644 pymode/libs/pylint/test/functional/no_name_in_module.py create mode 100644 pymode/libs/pylint/test/functional/no_name_in_module.txt create mode 100644 pymode/libs/pylint/test/functional/no_self_use.py create mode 100644 pymode/libs/pylint/test/functional/no_self_use.txt create mode 100644 pymode/libs/pylint/test/functional/no_self_use_py3.py create mode 100644 pymode/libs/pylint/test/functional/no_self_use_py3.rc create mode 100644 pymode/libs/pylint/test/functional/no_self_use_py3.txt create mode 100644 pymode/libs/pylint/test/functional/no_staticmethod_decorator.py create mode 100644 pymode/libs/pylint/test/functional/no_staticmethod_decorator.txt create mode 100644 pymode/libs/pylint/test/functional/non_iterator_returned.py create mode 100644 pymode/libs/pylint/test/functional/non_iterator_returned.txt create mode 100644 pymode/libs/pylint/test/functional/nonexistent_operator.py create mode 100644 pymode/libs/pylint/test/functional/nonexistent_operator.txt create mode 100644 pymode/libs/pylint/test/functional/nonlocal_and_global.py create mode 100644 pymode/libs/pylint/test/functional/nonlocal_and_global.rc create mode 100644 pymode/libs/pylint/test/functional/nonlocal_and_global.txt create mode 100644 pymode/libs/pylint/test/functional/nonlocal_without_binding.py create mode 100644 pymode/libs/pylint/test/functional/nonlocal_without_binding.rc create mode 100644 pymode/libs/pylint/test/functional/nonlocal_without_binding.txt create mode 100644 pymode/libs/pylint/test/functional/not_async_context_manager.py create mode 100644 pymode/libs/pylint/test/functional/not_async_context_manager.rc create mode 100644 pymode/libs/pylint/test/functional/not_async_context_manager.txt create mode 100644 pymode/libs/pylint/test/functional/not_callable.py create mode 100644 pymode/libs/pylint/test/functional/not_callable.txt create mode 100644 pymode/libs/pylint/test/functional/not_context_manager.py create mode 100644 pymode/libs/pylint/test/functional/not_context_manager.txt create mode 100644 pymode/libs/pylint/test/functional/not_in_loop.py create mode 100644 pymode/libs/pylint/test/functional/not_in_loop.txt create mode 100644 pymode/libs/pylint/test/functional/old_division_manually.py create mode 100644 pymode/libs/pylint/test/functional/old_division_manually.rc create mode 100644 pymode/libs/pylint/test/functional/old_style_class_py27.py create mode 100644 pymode/libs/pylint/test/functional/old_style_class_py27.rc create mode 100644 pymode/libs/pylint/test/functional/old_style_class_py27.txt create mode 100644 pymode/libs/pylint/test/functional/pygtk_enum_crash.py create mode 100644 pymode/libs/pylint/test/functional/pygtk_enum_crash.rc create mode 100644 pymode/libs/pylint/test/functional/pygtk_import.py create mode 100644 pymode/libs/pylint/test/functional/pygtk_import.rc create mode 100644 pymode/libs/pylint/test/functional/raising_non_exception_py3.py create mode 100644 pymode/libs/pylint/test/functional/raising_non_exception_py3.rc create mode 100644 pymode/libs/pylint/test/functional/raising_non_exception_py3.txt create mode 100644 pymode/libs/pylint/test/functional/redefined_builtin.py create mode 100644 pymode/libs/pylint/test/functional/redefined_builtin.txt create mode 100644 pymode/libs/pylint/test/functional/redefined_variable_type.py create mode 100644 pymode/libs/pylint/test/functional/redefined_variable_type.txt create mode 100644 pymode/libs/pylint/test/functional/redundant_unittest_assert.py create mode 100644 pymode/libs/pylint/test/functional/redundant_unittest_assert.txt create mode 100644 pymode/libs/pylint/test/functional/reimported.py create mode 100644 pymode/libs/pylint/test/functional/reimported.txt create mode 100644 pymode/libs/pylint/test/functional/repeated_keyword.py create mode 100644 pymode/libs/pylint/test/functional/repeated_keyword.txt create mode 100644 pymode/libs/pylint/test/functional/return_in_init.py create mode 100644 pymode/libs/pylint/test/functional/return_in_init.txt create mode 100644 pymode/libs/pylint/test/functional/return_outside_function.py create mode 100644 pymode/libs/pylint/test/functional/return_outside_function.txt create mode 100644 pymode/libs/pylint/test/functional/simplifiable_if_statement.py create mode 100644 pymode/libs/pylint/test/functional/simplifiable_if_statement.txt create mode 100644 pymode/libs/pylint/test/functional/singleton_comparison.py create mode 100644 pymode/libs/pylint/test/functional/singleton_comparison.txt create mode 100644 pymode/libs/pylint/test/functional/slots_checks.py create mode 100644 pymode/libs/pylint/test/functional/slots_checks.txt create mode 100644 pymode/libs/pylint/test/functional/socketerror_import.py create mode 100644 pymode/libs/pylint/test/functional/star_needs_assignment_target.py create mode 100644 pymode/libs/pylint/test/functional/star_needs_assignment_target.rc create mode 100644 pymode/libs/pylint/test/functional/star_needs_assignment_target.txt create mode 100644 pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.py create mode 100644 pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.rc create mode 100644 pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.txt create mode 100644 pymode/libs/pylint/test/functional/statement_without_effect.py create mode 100644 pymode/libs/pylint/test/functional/statement_without_effect.txt create mode 100644 pymode/libs/pylint/test/functional/string_formatting.py create mode 100644 pymode/libs/pylint/test/functional/string_formatting.txt create mode 100644 pymode/libs/pylint/test/functional/string_formatting_disable.py create mode 100644 pymode/libs/pylint/test/functional/string_formatting_disable.rc create mode 100644 pymode/libs/pylint/test/functional/string_formatting_disable.txt create mode 100644 pymode/libs/pylint/test/functional/string_formatting_failed_inference.py create mode 100644 pymode/libs/pylint/test/functional/string_formatting_py27.py create mode 100644 pymode/libs/pylint/test/functional/string_formatting_py27.rc create mode 100644 pymode/libs/pylint/test/functional/string_formatting_py27.txt create mode 100644 pymode/libs/pylint/test/functional/super_checks.py create mode 100644 pymode/libs/pylint/test/functional/super_checks.txt create mode 100644 pymode/libs/pylint/test/functional/superfluous_parens.py create mode 100644 pymode/libs/pylint/test/functional/superfluous_parens.txt create mode 100644 pymode/libs/pylint/test/functional/suspicious_str_strip_call.py create mode 100644 pymode/libs/pylint/test/functional/suspicious_str_strip_call.rc create mode 100644 pymode/libs/pylint/test/functional/suspicious_str_strip_call.txt create mode 100644 pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.py create mode 100644 pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.rc create mode 100644 pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.txt create mode 100644 pymode/libs/pylint/test/functional/syntax_error.py create mode 100644 pymode/libs/pylint/test/functional/syntax_error.rc create mode 100644 pymode/libs/pylint/test/functional/syntax_error.txt create mode 100644 pymode/libs/pylint/test/functional/syntax_error_jython.py create mode 100644 pymode/libs/pylint/test/functional/syntax_error_jython.rc create mode 100644 pymode/libs/pylint/test/functional/syntax_error_jython.txt create mode 100644 pymode/libs/pylint/test/functional/tokenize_error.py create mode 100644 pymode/libs/pylint/test/functional/tokenize_error.rc create mode 100644 pymode/libs/pylint/test/functional/tokenize_error.txt create mode 100644 pymode/libs/pylint/test/functional/tokenize_error_jython.py create mode 100644 pymode/libs/pylint/test/functional/tokenize_error_jython.rc create mode 100644 pymode/libs/pylint/test/functional/tokenize_error_jython.txt create mode 100644 pymode/libs/pylint/test/functional/too_few_public_methods.py create mode 100644 pymode/libs/pylint/test/functional/too_few_public_methods.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_ancestors.py create mode 100644 pymode/libs/pylint/test/functional/too_many_ancestors.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_arguments.py create mode 100644 pymode/libs/pylint/test/functional/too_many_arguments.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_boolean_expressions.py create mode 100644 pymode/libs/pylint/test/functional/too_many_boolean_expressions.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_branches.py create mode 100644 pymode/libs/pylint/test/functional/too_many_branches.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_instance_attributes.py create mode 100644 pymode/libs/pylint/test/functional/too_many_instance_attributes.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_lines.py create mode 100644 pymode/libs/pylint/test/functional/too_many_lines.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_lines_disabled.py create mode 100644 pymode/libs/pylint/test/functional/too_many_locals.py create mode 100644 pymode/libs/pylint/test/functional/too_many_locals.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_nested_blocks.py create mode 100644 pymode/libs/pylint/test/functional/too_many_nested_blocks.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_public_methods.py create mode 100644 pymode/libs/pylint/test/functional/too_many_public_methods.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_return_statements.py create mode 100644 pymode/libs/pylint/test/functional/too_many_return_statements.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_star_expressions.py create mode 100644 pymode/libs/pylint/test/functional/too_many_star_expressions.rc create mode 100644 pymode/libs/pylint/test/functional/too_many_star_expressions.txt create mode 100644 pymode/libs/pylint/test/functional/too_many_statements.py create mode 100644 pymode/libs/pylint/test/functional/too_many_statements.txt create mode 100644 pymode/libs/pylint/test/functional/trailing_whitespaces.py create mode 100644 pymode/libs/pylint/test/functional/trailing_whitespaces.txt create mode 100644 pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking.py create mode 100644 pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking.txt create mode 100644 pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking_py30.py create mode 100644 pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking_py30.rc create mode 100644 pymode/libs/pylint/test/functional/undefined_variable.py create mode 100644 pymode/libs/pylint/test/functional/undefined_variable.txt create mode 100644 pymode/libs/pylint/test/functional/undefined_variable_py30.py create mode 100644 pymode/libs/pylint/test/functional/undefined_variable_py30.rc create mode 100644 pymode/libs/pylint/test/functional/undefined_variable_py30.txt create mode 100644 pymode/libs/pylint/test/functional/unexpected_special_method_signature.py create mode 100644 pymode/libs/pylint/test/functional/unexpected_special_method_signature.txt create mode 100644 pymode/libs/pylint/test/functional/ungrouped_imports.py create mode 100644 pymode/libs/pylint/test/functional/ungrouped_imports.txt create mode 100644 pymode/libs/pylint/test/functional/unidiomatic_typecheck.py create mode 100644 pymode/libs/pylint/test/functional/unidiomatic_typecheck.txt create mode 100644 pymode/libs/pylint/test/functional/uninferable_all_object.py create mode 100644 pymode/libs/pylint/test/functional/unknown_encoding_jython.py create mode 100644 pymode/libs/pylint/test/functional/unknown_encoding_jython.rc create mode 100644 pymode/libs/pylint/test/functional/unknown_encoding_jython.txt create mode 100644 pymode/libs/pylint/test/functional/unknown_encoding_py29.py create mode 100644 pymode/libs/pylint/test/functional/unknown_encoding_py29.rc create mode 100644 pymode/libs/pylint/test/functional/unknown_encoding_py29.txt create mode 100644 pymode/libs/pylint/test/functional/unknown_encoding_pypy.py create mode 100644 pymode/libs/pylint/test/functional/unknown_encoding_pypy.rc create mode 100644 pymode/libs/pylint/test/functional/unknown_encoding_pypy.txt create mode 100644 pymode/libs/pylint/test/functional/unnecessary_lambda.py create mode 100644 pymode/libs/pylint/test/functional/unnecessary_lambda.txt create mode 100644 pymode/libs/pylint/test/functional/unnecessary_pass.py create mode 100644 pymode/libs/pylint/test/functional/unnecessary_pass.txt create mode 100644 pymode/libs/pylint/test/functional/unneeded_not.py create mode 100644 pymode/libs/pylint/test/functional/unneeded_not.txt create mode 100644 pymode/libs/pylint/test/functional/unpacked_exceptions.py create mode 100644 pymode/libs/pylint/test/functional/unpacked_exceptions.rc create mode 100644 pymode/libs/pylint/test/functional/unpacked_exceptions.txt create mode 100644 pymode/libs/pylint/test/functional/unpacking.py create mode 100644 pymode/libs/pylint/test/functional/unpacking_generalizations.py create mode 100644 pymode/libs/pylint/test/functional/unpacking_generalizations.rc create mode 100644 pymode/libs/pylint/test/functional/unpacking_generalizations.txt create mode 100644 pymode/libs/pylint/test/functional/unpacking_non_sequence.py create mode 100644 pymode/libs/pylint/test/functional/unpacking_non_sequence.txt create mode 100644 pymode/libs/pylint/test/functional/unreachable.py create mode 100644 pymode/libs/pylint/test/functional/unreachable.txt create mode 100644 pymode/libs/pylint/test/functional/unrecognized_inline_option.py create mode 100644 pymode/libs/pylint/test/functional/unrecognized_inline_option.txt create mode 100644 pymode/libs/pylint/test/functional/unsubscriptable_value.py create mode 100644 pymode/libs/pylint/test/functional/unsubscriptable_value.txt create mode 100644 pymode/libs/pylint/test/functional/unsubscriptable_value_35.py create mode 100644 pymode/libs/pylint/test/functional/unsubscriptable_value_35.rc create mode 100644 pymode/libs/pylint/test/functional/unsupported_binary_operation.py create mode 100644 pymode/libs/pylint/test/functional/unsupported_binary_operation.rc create mode 100644 pymode/libs/pylint/test/functional/unsupported_binary_operation.txt create mode 100644 pymode/libs/pylint/test/functional/unused_import.py create mode 100644 pymode/libs/pylint/test/functional/unused_import.txt create mode 100644 pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.py create mode 100644 pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.rc create mode 100644 pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.txt create mode 100644 pymode/libs/pylint/test/functional/useless_else_on_loop.py create mode 100644 pymode/libs/pylint/test/functional/useless_else_on_loop.txt create mode 100644 pymode/libs/pylint/test/functional/using_constant_test.py create mode 100644 pymode/libs/pylint/test/functional/using_constant_test.txt create mode 100644 pymode/libs/pylint/test/functional/wildcard_import.py create mode 100644 pymode/libs/pylint/test/functional/wildcard_import.txt create mode 100644 pymode/libs/pylint/test/functional/with_used_before_assign.py create mode 100644 pymode/libs/pylint/test/functional/with_used_before_assign.txt create mode 100644 pymode/libs/pylint/test/functional/with_using_generator.py create mode 100644 pymode/libs/pylint/test/functional/with_using_generator.txt create mode 100644 pymode/libs/pylint/test/functional/wrong_import_order.py create mode 100644 pymode/libs/pylint/test/functional/wrong_import_order.txt create mode 100644 pymode/libs/pylint/test/functional/wrong_import_position.py create mode 100644 pymode/libs/pylint/test/functional/wrong_import_position.txt create mode 100644 pymode/libs/pylint/test/functional/yield_from_iterable_py33.py create mode 100644 pymode/libs/pylint/test/functional/yield_from_iterable_py33.rc create mode 100644 pymode/libs/pylint/test/functional/yield_from_iterable_py33.txt create mode 100644 pymode/libs/pylint/test/functional/yield_from_outside_func.py create mode 100644 pymode/libs/pylint/test/functional/yield_from_outside_func.rc create mode 100644 pymode/libs/pylint/test/functional/yield_from_outside_func.txt create mode 100644 pymode/libs/pylint/test/functional/yield_inside_async_function.py create mode 100644 pymode/libs/pylint/test/functional/yield_inside_async_function.rc create mode 100644 pymode/libs/pylint/test/functional/yield_inside_async_function.txt create mode 100644 pymode/libs/pylint/test/functional/yield_outside_func.py create mode 100644 pymode/libs/pylint/test/functional/yield_outside_func.txt create mode 100644 pymode/libs/pylint/test/input/__init__.py create mode 100644 pymode/libs/pylint/test/input/func_3k_removed_stuff_py_30.py create mode 100644 pymode/libs/pylint/test/input/func_bad_assigment_to_exception_var.py create mode 100644 pymode/libs/pylint/test/input/func_bad_cont_dictcomp_py27.py create mode 100644 pymode/libs/pylint/test/input/func_block_disable_msg.py create mode 100644 pymode/libs/pylint/test/input/func_break_or_return_in_try_finally.py create mode 100644 pymode/libs/pylint/test/input/func_bug113231.py create mode 100644 pymode/libs/pylint/test/input/func_disable_linebased.py create mode 100644 pymode/libs/pylint/test/input/func_dotted_ancestor.py create mode 100644 pymode/libs/pylint/test/input/func_e0012.py create mode 100644 pymode/libs/pylint/test/input/func_e0203.py create mode 100644 pymode/libs/pylint/test/input/func_e0204.py create mode 100644 pymode/libs/pylint/test/input/func_e0601.py create mode 100644 pymode/libs/pylint/test/input/func_e0604.py create mode 100644 pymode/libs/pylint/test/input/func_e12xx.py create mode 100644 pymode/libs/pylint/test/input/func_e13xx.py create mode 100644 pymode/libs/pylint/test/input/func_excess_escapes.py create mode 100644 pymode/libs/pylint/test/input/func_first_arg.py create mode 100644 pymode/libs/pylint/test/input/func_i0011.py create mode 100644 pymode/libs/pylint/test/input/func_i0012.py create mode 100644 pymode/libs/pylint/test/input/func_i0013.py create mode 100644 pymode/libs/pylint/test/input/func_i0014.py create mode 100644 pymode/libs/pylint/test/input/func_i0020.py create mode 100644 pymode/libs/pylint/test/input/func_i0022.py create mode 100644 pymode/libs/pylint/test/input/func_init_vars.py create mode 100644 pymode/libs/pylint/test/input/func_kwoa_py30.py create mode 100644 pymode/libs/pylint/test/input/func_logging_not_lazy_with_logger.py create mode 100644 pymode/libs/pylint/test/input/func_loopvar_in_dict_comp_py27.py create mode 100644 pymode/libs/pylint/test/input/func_module___dict__.py create mode 100644 pymode/libs/pylint/test/input/func_nameerror_on_string_substitution.py create mode 100644 pymode/libs/pylint/test/input/func_no_dummy_redefined.py create mode 100644 pymode/libs/pylint/test/input/func_noerror___init___return_from_inner_function.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_access_attr_before_def_false_positive.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_base_init_vars.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_builtin_module_test.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_class_attributes.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_classes_meth_could_be_a_function.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_classes_protected_member_access.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_crash_127416.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_decorator_scope.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_e1101_9588_base_attr_aug_assign.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_encoding.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_except_pass.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_exception.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_external_classmethod_crash.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_function_as_method.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_genexp_in_class_scope.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_inner_classes.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_lambda_use_before_assign.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_long_utf8_line.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_mcs_attr_access.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_new_style_class_py_30.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_no_warning_docstring.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_nonregr.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_object_as_class_attribute.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_overloaded_operator.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_overriden_method_varargs.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_property_affectation_py26.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_raise_return_self.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_static_method.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_yield_assign_py25.py create mode 100644 pymode/libs/pylint/test/input/func_noerror_yield_return_mix.py create mode 100644 pymode/libs/pylint/test/input/func_nonregr___file___global.py create mode 100644 pymode/libs/pylint/test/input/func_return_yield_mix_py_33.py create mode 100644 pymode/libs/pylint/test/input/func_too_many_returns_yields.py create mode 100644 pymode/libs/pylint/test/input/func_typecheck_callfunc_assigment.py create mode 100644 pymode/libs/pylint/test/input/func_unused_import_py30.py create mode 100644 pymode/libs/pylint/test/input/func_unused_overridden_argument.py create mode 100644 pymode/libs/pylint/test/input/func_use_for_or_listcomp_var.py create mode 100644 pymode/libs/pylint/test/input/func_variables_unused_name_from_wilcard_import.py create mode 100644 pymode/libs/pylint/test/input/func_w0122_py_30.py create mode 100644 pymode/libs/pylint/test/input/func_w0205.py create mode 100644 pymode/libs/pylint/test/input/func_w0233.py create mode 100644 pymode/libs/pylint/test/input/func_w0332_py_30.py create mode 100644 pymode/libs/pylint/test/input/func_w0401.py create mode 100644 pymode/libs/pylint/test/input/func_w0401_package/__init__.py create mode 100644 pymode/libs/pylint/test/input/func_w0401_package/all_the_things.py create mode 100644 pymode/libs/pylint/test/input/func_w0401_package/thing1.py create mode 100644 pymode/libs/pylint/test/input/func_w0401_package/thing2.py create mode 100644 pymode/libs/pylint/test/input/func_w0404.py create mode 100644 pymode/libs/pylint/test/input/func_w0405.py create mode 100644 pymode/libs/pylint/test/input/func_w0406.py create mode 100644 pymode/libs/pylint/test/input/func_w0611.py create mode 100644 pymode/libs/pylint/test/input/func_w0612.py create mode 100644 pymode/libs/pylint/test/input/func_w0613.py create mode 100644 pymode/libs/pylint/test/input/func_w0623_py30.py create mode 100644 pymode/libs/pylint/test/input/func_w0623_py_30.py create mode 100644 pymode/libs/pylint/test/input/func_w0631.py create mode 100644 pymode/libs/pylint/test/input/func_w0703.py create mode 100644 pymode/libs/pylint/test/input/func_w0705.py create mode 100644 pymode/libs/pylint/test/input/func_w0801.py create mode 100644 pymode/libs/pylint/test/input/func_with_without_as_py25.py create mode 100644 pymode/libs/pylint/test/input/ignore_except_pass_by_default.py create mode 100644 pymode/libs/pylint/test/input/noext create mode 100644 pymode/libs/pylint/test/input/similar1 create mode 100644 pymode/libs/pylint/test/input/similar2 create mode 100644 pymode/libs/pylint/test/input/syntax_error.py create mode 100644 pymode/libs/pylint/test/input/w0401_cycle.py create mode 100644 pymode/libs/pylint/test/input/w0801_same.py create mode 100644 pymode/libs/pylint/test/messages/builtin_module.txt create mode 100644 pymode/libs/pylint/test/messages/func_3k_removed_stuff_py_30.txt create mode 100644 pymode/libs/pylint/test/messages/func_bad_assigment_to_exception_var.txt create mode 100644 pymode/libs/pylint/test/messages/func_bad_cont_dictcomp_py27.txt create mode 100644 pymode/libs/pylint/test/messages/func_block_disable_msg.txt create mode 100644 pymode/libs/pylint/test/messages/func_break_or_return_in_try_finally.txt create mode 100644 pymode/libs/pylint/test/messages/func_bug113231.txt create mode 100644 pymode/libs/pylint/test/messages/func_disable_linebased.txt create mode 100644 pymode/libs/pylint/test/messages/func_disable_linebased_py30.txt create mode 100644 pymode/libs/pylint/test/messages/func_dotted_ancestor.txt create mode 100644 pymode/libs/pylint/test/messages/func_e0012.txt create mode 100644 pymode/libs/pylint/test/messages/func_e0203.txt create mode 100644 pymode/libs/pylint/test/messages/func_e0204.txt create mode 100644 pymode/libs/pylint/test/messages/func_e0601.txt create mode 100644 pymode/libs/pylint/test/messages/func_e0604.txt create mode 100644 pymode/libs/pylint/test/messages/func_e12xx.txt create mode 100644 pymode/libs/pylint/test/messages/func_e13xx.txt create mode 100644 pymode/libs/pylint/test/messages/func_e13xx_py30.txt create mode 100644 pymode/libs/pylint/test/messages/func_excess_escapes.txt create mode 100644 pymode/libs/pylint/test/messages/func_first_arg.txt create mode 100644 pymode/libs/pylint/test/messages/func_i0011.txt create mode 100644 pymode/libs/pylint/test/messages/func_i0012.txt create mode 100644 pymode/libs/pylint/test/messages/func_i0013.txt create mode 100644 pymode/libs/pylint/test/messages/func_i0014.txt create mode 100644 pymode/libs/pylint/test/messages/func_i0020.txt create mode 100644 pymode/libs/pylint/test/messages/func_i0022.txt create mode 100644 pymode/libs/pylint/test/messages/func_init_vars.txt create mode 100644 pymode/libs/pylint/test/messages/func_kwoa_py30.txt create mode 100644 pymode/libs/pylint/test/messages/func_logging_not_lazy_with_logger.txt create mode 100644 pymode/libs/pylint/test/messages/func_loopvar_in_dict_comp_py27.txt create mode 100644 pymode/libs/pylint/test/messages/func_module___dict__.txt create mode 100644 pymode/libs/pylint/test/messages/func_nameerror_on_string_substitution.txt create mode 100644 pymode/libs/pylint/test/messages/func_no_dummy_redefined.txt create mode 100644 pymode/libs/pylint/test/messages/func_nonregr___file___global.txt create mode 100644 pymode/libs/pylint/test/messages/func_raw_escapes.txt create mode 100644 pymode/libs/pylint/test/messages/func_return_yield_mix_py_33.txt create mode 100644 pymode/libs/pylint/test/messages/func_too_many_returns_yields.txt create mode 100644 pymode/libs/pylint/test/messages/func_toolonglines_py30.txt create mode 100644 pymode/libs/pylint/test/messages/func_typecheck_callfunc_assigment.txt create mode 100644 pymode/libs/pylint/test/messages/func_typecheck_getattr_py30.txt create mode 100644 pymode/libs/pylint/test/messages/func_typecheck_non_callable_call.txt create mode 100644 pymode/libs/pylint/test/messages/func_unicode_literal_py26.txt create mode 100644 pymode/libs/pylint/test/messages/func_unicode_literal_py274.txt create mode 100644 pymode/libs/pylint/test/messages/func_unused_import_py30.txt create mode 100644 pymode/libs/pylint/test/messages/func_unused_overridden_argument.txt create mode 100644 pymode/libs/pylint/test/messages/func_use_for_or_listcomp_var_py29.txt create mode 100644 pymode/libs/pylint/test/messages/func_use_for_or_listcomp_var_py30.txt create mode 100644 pymode/libs/pylint/test/messages/func_variables_unused_name_from_wilcard_import.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0122_py_30.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0205.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0233.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0312.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0332_py_30.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0401.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0401_package.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0404.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0405.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0406.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0611.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0612.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0613.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0622.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0623.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0623_py30.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0623_py_30.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0631.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0703.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0705.txt create mode 100644 pymode/libs/pylint/test/messages/func_w0801.txt create mode 100644 pymode/libs/pylint/test/messages/func_with_without_as_py25.txt create mode 100644 pymode/libs/pylint/test/regrtest_data/absimp/__init__.py create mode 100644 pymode/libs/pylint/test/regrtest_data/absimp/string.py create mode 100644 pymode/libs/pylint/test/regrtest_data/application_crash.py create mode 100644 pymode/libs/pylint/test/regrtest_data/bad_package/__init__.py create mode 100644 pymode/libs/pylint/test/regrtest_data/bad_package/wrong.py create mode 100644 pymode/libs/pylint/test/regrtest_data/classdoc_usage.py create mode 100644 pymode/libs/pylint/test/regrtest_data/decimal_inference.py create mode 100644 pymode/libs/pylint/test/regrtest_data/descriptor_crash.py create mode 100644 pymode/libs/pylint/test/regrtest_data/dummy/__init__.py create mode 100644 pymode/libs/pylint/test/regrtest_data/dummy/another.py create mode 100644 pymode/libs/pylint/test/regrtest_data/dummy/dummy.py create mode 100644 pymode/libs/pylint/test/regrtest_data/html_crash_420.py create mode 100644 pymode/libs/pylint/test/regrtest_data/import_assign.py create mode 100644 pymode/libs/pylint/test/regrtest_data/import_package_subpackage_module.py create mode 100644 pymode/libs/pylint/test/regrtest_data/import_something.py create mode 100644 pymode/libs/pylint/test/regrtest_data/module_global.py create mode 100644 pymode/libs/pylint/test/regrtest_data/no_stdout_encoding.py create mode 100644 pymode/libs/pylint/test/regrtest_data/numarray_import.py create mode 100644 pymode/libs/pylint/test/regrtest_data/numarray_inf.py create mode 100644 pymode/libs/pylint/test/regrtest_data/package/AudioTime.py create mode 100644 pymode/libs/pylint/test/regrtest_data/package/__init__.py create mode 100644 pymode/libs/pylint/test/regrtest_data/package/subpackage/__init__.py create mode 100644 pymode/libs/pylint/test/regrtest_data/package/subpackage/module.py create mode 100644 pymode/libs/pylint/test/regrtest_data/package_all/__init__.py create mode 100644 pymode/libs/pylint/test/regrtest_data/package_all/notmissing.py create mode 100644 pymode/libs/pylint/test/regrtest_data/precedence_test.py create mode 100644 pymode/libs/pylint/test/regrtest_data/py3k_error_flag.py create mode 100644 pymode/libs/pylint/test/regrtest_data/special_attr_scope_lookup_crash.py create mode 100644 pymode/libs/pylint/test/regrtest_data/syntax_error.py create mode 100644 pymode/libs/pylint/test/regrtest_data/try_finally_disable_msg_crash.py create mode 100644 pymode/libs/pylint/test/regrtest_data/wrong_import_position.py create mode 100644 pymode/libs/pylint/test/test_func.py create mode 100644 pymode/libs/pylint/test/test_functional.py create mode 100644 pymode/libs/pylint/test/test_import_graph.py create mode 100644 pymode/libs/pylint/test/test_regr.py create mode 100644 pymode/libs/pylint/test/test_self.py create mode 100644 pymode/libs/pylint/test/unittest_checker_base.py create mode 100644 pymode/libs/pylint/test/unittest_checker_classes.py create mode 100644 pymode/libs/pylint/test/unittest_checker_exceptions.py create mode 100644 pymode/libs/pylint/test/unittest_checker_format.py create mode 100644 pymode/libs/pylint/test/unittest_checker_imports.py create mode 100644 pymode/libs/pylint/test/unittest_checker_logging.py create mode 100644 pymode/libs/pylint/test/unittest_checker_misc.py create mode 100644 pymode/libs/pylint/test/unittest_checker_python3.py create mode 100644 pymode/libs/pylint/test/unittest_checker_similar.py create mode 100644 pymode/libs/pylint/test/unittest_checker_spelling.py create mode 100644 pymode/libs/pylint/test/unittest_checker_stdlib.py create mode 100644 pymode/libs/pylint/test/unittest_checker_strings.py create mode 100644 pymode/libs/pylint/test/unittest_checker_typecheck.py create mode 100644 pymode/libs/pylint/test/unittest_checker_variables.py create mode 100644 pymode/libs/pylint/test/unittest_checkers_utils.py create mode 100644 pymode/libs/pylint/test/unittest_lint.py create mode 100644 pymode/libs/pylint/test/unittest_pyreverse_diadefs.py create mode 100644 pymode/libs/pylint/test/unittest_pyreverse_inspector.py create mode 100644 pymode/libs/pylint/test/unittest_pyreverse_writer.py create mode 100644 pymode/libs/pylint/test/unittest_reporters_json.py create mode 100644 pymode/libs/pylint/test/unittest_reporting.py create mode 100644 pymode/libs/pylint/test/unittest_utils.py diff --git a/pymode/libs/astroid/__init__.py b/pymode/libs/astroid/__init__.py index d4fd12c5..175dcb5e 100644 --- a/pymode/libs/astroid/__init__.py +++ b/pymode/libs/astroid/__init__.py @@ -58,13 +58,15 @@ # more stuff available from astroid import raw_building -from astroid.bases import YES, Instance, BoundMethod, UnboundMethod +from astroid.bases import Instance, BoundMethod, UnboundMethod from astroid.node_classes import are_exclusive, unpack_infer from astroid.scoped_nodes import builtin_lookup +from astroid.builder import parse +from astroid.util import YES # make a manager instance (borg) as well as Project and Package classes # accessible from astroid package -from astroid.manager import AstroidManager, Project +from astroid.manager import AstroidManager MANAGER = AstroidManager() del AstroidManager @@ -100,7 +102,7 @@ def inference_tip(infer_function): .. sourcecode:: python - MANAGER.register_transform(CallFunc, inference_tip(infer_named_tuple), + MANAGER.register_transform(Call, inference_tip(infer_named_tuple), predicate) """ def transform(node, infer_function=infer_function): @@ -112,8 +114,11 @@ def transform(node, infer_function=infer_function): def register_module_extender(manager, module_name, get_extension_mod): def transform(node): extension_module = get_extension_mod() - for name, obj in extension_module.locals.items(): - node.locals[name] = obj + for name, objs in extension_module._locals.items(): + node._locals[name] = objs + for obj in objs: + if obj.parent is extension_module: + obj.parent = node manager.register_transform(Module, transform, lambda n: n.name == module_name) diff --git a/pymode/libs/astroid/__pkginfo__.py b/pymode/libs/astroid/__pkginfo__.py index 3fb45aa4..27530609 100644 --- a/pymode/libs/astroid/__pkginfo__.py +++ b/pymode/libs/astroid/__pkginfo__.py @@ -20,10 +20,10 @@ modname = 'astroid' -numversion = (1, 3, 8) +numversion = (1, 4, 5) version = '.'.join([str(num) for num in numversion]) -install_requires = ['logilab-common>=0.63.0', 'six'] +install_requires = ['six', 'lazy_object_proxy', 'wrapt'] license = 'LGPL' diff --git a/pymode/libs/astroid/arguments.py b/pymode/libs/astroid/arguments.py new file mode 100644 index 00000000..f05d48a3 --- /dev/null +++ b/pymode/libs/astroid/arguments.py @@ -0,0 +1,233 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . + +from astroid import bases +from astroid import context as contextmod +from astroid import exceptions +from astroid import nodes +from astroid import util + +import six + + +class CallSite(object): + """Class for understanding arguments passed into a call site + + It needs a call context, which contains the arguments and the + keyword arguments that were passed into a given call site. + In order to infer what an argument represents, call + :meth:`infer_argument` with the corresponding function node + and the argument name. + """ + + def __init__(self, callcontext): + args = callcontext.args + keywords = callcontext.keywords + self.duplicated_keywords = set() + self._unpacked_args = self._unpack_args(args) + self._unpacked_kwargs = self._unpack_keywords(keywords) + + self.positional_arguments = [ + arg for arg in self._unpacked_args + if arg is not util.YES + ] + self.keyword_arguments = { + key: value for key, value in self._unpacked_kwargs.items() + if value is not util.YES + } + + @classmethod + def from_call(cls, call_node): + """Get a CallSite object from the given Call node.""" + callcontext = contextmod.CallContext(call_node.args, + call_node.keywords) + return cls(callcontext) + + def has_invalid_arguments(self): + """Check if in the current CallSite were passed *invalid* arguments + + This can mean multiple things. For instance, if an unpacking + of an invalid object was passed, then this method will return True. + Other cases can be when the arguments can't be inferred by astroid, + for example, by passing objects which aren't known statically. + """ + return len(self.positional_arguments) != len(self._unpacked_args) + + def has_invalid_keywords(self): + """Check if in the current CallSite were passed *invalid* keyword arguments + + For instance, unpacking a dictionary with integer keys is invalid + (**{1:2}), because the keys must be strings, which will make this + method to return True. Other cases where this might return True if + objects which can't be inferred were passed. + """ + return len(self.keyword_arguments) != len(self._unpacked_kwargs) + + def _unpack_keywords(self, keywords): + values = {} + context = contextmod.InferenceContext() + for name, value in keywords: + if name is None: + # Then it's an unpacking operation (**) + try: + inferred = next(value.infer(context=context)) + except exceptions.InferenceError: + values[name] = util.YES + continue + + if not isinstance(inferred, nodes.Dict): + # Not something we can work with. + values[name] = util.YES + continue + + for dict_key, dict_value in inferred.items: + try: + dict_key = next(dict_key.infer(context=context)) + except exceptions.InferenceError: + values[name] = util.YES + continue + if not isinstance(dict_key, nodes.Const): + values[name] = util.YES + continue + if not isinstance(dict_key.value, six.string_types): + values[name] = util.YES + continue + if dict_key.value in values: + # The name is already in the dictionary + values[dict_key.value] = util.YES + self.duplicated_keywords.add(dict_key.value) + continue + values[dict_key.value] = dict_value + else: + values[name] = value + return values + + @staticmethod + def _unpack_args(args): + values = [] + context = contextmod.InferenceContext() + for arg in args: + if isinstance(arg, nodes.Starred): + try: + inferred = next(arg.value.infer(context=context)) + except exceptions.InferenceError: + values.append(util.YES) + continue + + if inferred is util.YES: + values.append(util.YES) + continue + if not hasattr(inferred, 'elts'): + values.append(util.YES) + continue + values.extend(inferred.elts) + else: + values.append(arg) + return values + + def infer_argument(self, funcnode, name, context): + """infer a function argument value according to the call context""" + if name in self.duplicated_keywords: + raise exceptions.InferenceError(name) + + # Look into the keywords first, maybe it's already there. + try: + return self.keyword_arguments[name].infer(context) + except KeyError: + pass + + # Too many arguments given and no variable arguments. + if len(self.positional_arguments) > len(funcnode.args.args): + if not funcnode.args.vararg: + raise exceptions.InferenceError(name) + + positional = self.positional_arguments[:len(funcnode.args.args)] + vararg = self.positional_arguments[len(funcnode.args.args):] + argindex = funcnode.args.find_argname(name)[0] + kwonlyargs = set(arg.name for arg in funcnode.args.kwonlyargs) + kwargs = { + key: value for key, value in self.keyword_arguments.items() + if key not in kwonlyargs + } + # If there are too few positionals compared to + # what the function expects to receive, check to see + # if the missing positional arguments were passed + # as keyword arguments and if so, place them into the + # positional args list. + if len(positional) < len(funcnode.args.args): + for func_arg in funcnode.args.args: + if func_arg.name in kwargs: + arg = kwargs.pop(func_arg.name) + positional.append(arg) + + if argindex is not None: + # 2. first argument of instance/class method + if argindex == 0 and funcnode.type in ('method', 'classmethod'): + if context.boundnode is not None: + boundnode = context.boundnode + else: + # XXX can do better ? + boundnode = funcnode.parent.frame() + if funcnode.type == 'method': + if not isinstance(boundnode, bases.Instance): + boundnode = bases.Instance(boundnode) + return iter((boundnode,)) + if funcnode.type == 'classmethod': + return iter((boundnode,)) + # if we have a method, extract one position + # from the index, so we'll take in account + # the extra parameter represented by `self` or `cls` + if funcnode.type in ('method', 'classmethod'): + argindex -= 1 + # 2. search arg index + try: + return self.positional_arguments[argindex].infer(context) + except IndexError: + pass + + if funcnode.args.kwarg == name: + # It wants all the keywords that were passed into + # the call site. + if self.has_invalid_keywords(): + raise exceptions.InferenceError + kwarg = nodes.Dict() + kwarg.lineno = funcnode.args.lineno + kwarg.col_offset = funcnode.args.col_offset + kwarg.parent = funcnode.args + items = [(nodes.const_factory(key), value) + for key, value in kwargs.items()] + kwarg.items = items + return iter((kwarg, )) + elif funcnode.args.vararg == name: + # It wants all the args that were passed into + # the call site. + if self.has_invalid_arguments(): + raise exceptions.InferenceError + args = nodes.Tuple() + args.lineno = funcnode.args.lineno + args.col_offset = funcnode.args.col_offset + args.parent = funcnode.args + args.elts = vararg + return iter((args, )) + + # Check if it's a default parameter. + try: + return funcnode.args.default_value(name).infer(context) + except exceptions.NoDefault: + pass + raise exceptions.InferenceError(name) diff --git a/pymode/libs/astroid/as_string.py b/pymode/libs/astroid/as_string.py index f627f9e8..2b07200c 100644 --- a/pymode/libs/astroid/as_string.py +++ b/pymode/libs/astroid/as_string.py @@ -22,9 +22,10 @@ * :func:`dump` function return an internal representation of nodes found in the tree, useful for debugging or understanding the tree structure """ - import sys +import six + INDENT = ' ' # 4 spaces ; keep indentation variable @@ -89,9 +90,9 @@ def visit_arguments(self, node): """return an astroid.Function node as string""" return node.format_args() - def visit_assattr(self, node): + def visit_assignattr(self, node): """return an astroid.AssAttr node as string""" - return self.visit_getattr(node) + return self.visit_attribute(node) def visit_assert(self, node): """return an astroid.Assert node as string""" @@ -100,7 +101,7 @@ def visit_assert(self, node): node.fail.accept(self)) return 'assert %s' % node.test.accept(self) - def visit_assname(self, node): + def visit_assignname(self, node): """return an astroid.AssName node as string""" return node.name @@ -113,8 +114,8 @@ def visit_augassign(self, node): """return an astroid.AugAssign node as string""" return '%s %s %s' % (node.target.accept(self), node.op, node.value.accept(self)) - def visit_backquote(self, node): - """return an astroid.Backquote node as string""" + def visit_repr(self, node): + """return an astroid.Repr node as string""" return '`%s`' % node.value.accept(self) def visit_binop(self, node): @@ -130,18 +131,20 @@ def visit_break(self, node): """return an astroid.Break node as string""" return 'break' - def visit_callfunc(self, node): - """return an astroid.CallFunc node as string""" + def visit_call(self, node): + """return an astroid.Call node as string""" expr_str = node.func.accept(self) args = [arg.accept(self) for arg in node.args] - if node.starargs: - args.append('*' + node.starargs.accept(self)) - if node.kwargs: - args.append('**' + node.kwargs.accept(self)) + if node.keywords: + keywords = [kwarg.accept(self) for kwarg in node.keywords] + else: + keywords = [] + + args.extend(keywords) return '%s(%s)' % (expr_str, ', '.join(args)) - def visit_class(self, node): - """return an astroid.Class node as string""" + def visit_classdef(self, node): + """return an astroid.ClassDef node as string""" decorate = node.decorators and node.decorators.accept(self) or '' bases = ', '.join([n.accept(self) for n in node.bases]) if sys.version_info[0] == 2: @@ -186,7 +189,7 @@ def visit_delete(self, node): # XXX check if correct def visit_delattr(self, node): """return an astroid.DelAttr node as string""" - return self.visit_getattr(node) + return self.visit_attribute(node) def visit_delname(self, node): """return an astroid.DelName node as string""" @@ -198,16 +201,27 @@ def visit_decorators(self, node): def visit_dict(self, node): """return an astroid.Dict node as string""" - return '{%s}' % ', '.join(['%s: %s' % (key.accept(self), - value.accept(self)) - for key, value in node.items]) + return '{%s}' % ', '.join(self._visit_dict(node)) + + def _visit_dict(self, node): + for key, value in node.items: + key = key.accept(self) + value = value.accept(self) + if key == '**': + # It can only be a DictUnpack node. + yield key + value + else: + yield '%s: %s' % (key, value) + + def visit_dictunpack(self, node): + return '**' def visit_dictcomp(self, node): """return an astroid.DictComp node as string""" return '{%s: %s %s}' % (node.key.accept(self), node.value.accept(self), ' '.join([n.accept(self) for n in node.generators])) - def visit_discard(self, node): + def visit_expr(self, node): """return an astroid.Discard node as string""" return node.value.accept(self) @@ -258,24 +272,33 @@ def visit_for(self, node): fors = '%s\nelse:\n%s' % (fors, self._stmt_list(node.orelse)) return fors - def visit_from(self, node): - """return an astroid.From node as string""" + def visit_importfrom(self, node): + """return an astroid.ImportFrom node as string""" return 'from %s import %s' % ('.' * (node.level or 0) + node.modname, _import_string(node.names)) - def visit_function(self, node): + def visit_functiondef(self, node): """return an astroid.Function node as string""" decorate = node.decorators and node.decorators.accept(self) or '' docs = node.doc and '\n%s"""%s"""' % (INDENT, node.doc) or '' - return '\n%sdef %s(%s):%s\n%s' % (decorate, node.name, node.args.accept(self), - docs, self._stmt_list(node.body)) - - def visit_genexpr(self, node): - """return an astroid.GenExpr node as string""" + return_annotation = '' + if six.PY3 and node.returns: + return_annotation = '->' + node.returns.as_string() + trailer = return_annotation + ":" + else: + trailer = ":" + def_format = "\n%sdef %s(%s)%s%s\n%s" + return def_format % (decorate, node.name, + node.args.accept(self), + trailer, docs, + self._stmt_list(node.body)) + + def visit_generatorexp(self, node): + """return an astroid.GeneratorExp node as string""" return '(%s %s)' % (node.elt.accept(self), ' '.join([n.accept(self) for n in node.generators])) - def visit_getattr(self, node): + def visit_attribute(self, node): """return an astroid.Getattr node as string""" return '%s.%s' % (node.expr.accept(self), node.attrname) @@ -302,6 +325,8 @@ def visit_import(self, node): def visit_keyword(self, node): """return an astroid.Keyword node as string""" + if node.arg is None: + return '**%s' % node.value.accept(self) return '%s=%s' % (node.arg, node.value.accept(self)) def visit_lambda(self, node): @@ -438,6 +463,22 @@ def visit_yield(self, node): else: return "(%s)" % (expr,) + def visit_starred(self, node): + """return Starred node as string""" + return "*" + node.value.accept(self) + + + # These aren't for real AST nodes, but for inference objects. + + def visit_frozenset(self, node): + return node.parent.accept(self) + + def visit_super(self, node): + return node.parent.accept(self) + + def visit_yes(self, node): + return "Uninferable" + class AsStringVisitor3k(AsStringVisitor): """AsStringVisitor3k overwrites some AsStringVisitor methods""" @@ -466,10 +507,6 @@ def visit_raise(self, node): return 'raise %s' % node.exc.accept(self) return 'raise' - def visit_starred(self, node): - """return Starred node as string""" - return "*" + node.value.accept(self) - def visit_yieldfrom(self, node): """ Return an astroid.YieldFrom node as string. """ yi_val = node.value and (" " + node.value.accept(self)) or "" @@ -479,6 +516,19 @@ def visit_yieldfrom(self, node): else: return "(%s)" % (expr,) + def visit_asyncfunctiondef(self, node): + function = super(AsStringVisitor3k, self).visit_functiondef(node) + return 'async ' + function.strip() + + def visit_await(self, node): + return 'await %s' % node.value.accept(self) + + def visit_asyncwith(self, node): + return 'async %s' % self.visit_with(node) + + def visit_asyncfor(self, node): + return 'async %s' % self.visit_for(node) + def _import_string(names): """return a list of (name, asname) formatted as a string""" @@ -496,4 +546,3 @@ def _import_string(names): # this visitor is stateless, thus it can be reused to_code = AsStringVisitor() - diff --git a/pymode/libs/astroid/bases.py b/pymode/libs/astroid/bases.py index ee8ee1c3..8dfa8126 100644 --- a/pymode/libs/astroid/bases.py +++ b/pymode/libs/astroid/bases.py @@ -18,22 +18,44 @@ """This module contains base classes and functions for the nodes and some inference utils. """ - -__docformat__ = "restructuredtext en" - +import functools import sys -from contextlib import contextmanager +import warnings -from logilab.common.decorators import cachedproperty +import wrapt -from astroid.exceptions import (InferenceError, AstroidError, NotFoundError, - UnresolvableName, UseInferenceDefault) +from astroid import context as contextmod +from astroid import decorators as decoratorsmod +from astroid import exceptions +from astroid import util if sys.version_info >= (3, 0): BUILTINS = 'builtins' else: BUILTINS = '__builtin__' +PROPERTIES = {BUILTINS + '.property', 'abc.abstractproperty'} +# List of possible property names. We use this list in order +# to see if a method is a property or not. This should be +# pretty reliable and fast, the alternative being to check each +# decorator to see if its a real property-like descriptor, which +# can be too complicated. +# Also, these aren't qualified, because each project can +# define them, we shouldn't expect to know every possible +# property-like decorator! +# TODO(cpopa): just implement descriptors already. +POSSIBLE_PROPERTIES = {"cached_property", "cachedproperty", + "lazyproperty", "lazy_property", "reify", + "lazyattribute", "lazy_attribute", + "LazyProperty", "lazy"} + + +def _is_property(meth): + if PROPERTIES.intersection(meth.decoratornames()): + return True + stripped = {name.split(".")[-1] for name in meth.decoratornames() + if name is not util.YES} + return any(name in stripped for name in POSSIBLE_PROPERTIES) class Proxy(object): @@ -56,101 +78,34 @@ def infer(self, context=None): yield self -# Inference ################################################################## - -class InferenceContext(object): - __slots__ = ('path', 'lookupname', 'callcontext', 'boundnode', 'infered') - - def __init__(self, path=None, infered=None): - self.path = path or set() - self.lookupname = None - self.callcontext = None - self.boundnode = None - self.infered = infered or {} - - def push(self, node): - name = self.lookupname - if (node, name) in self.path: - raise StopIteration() - self.path.add((node, name)) - - def clone(self): - # XXX copy lookupname/callcontext ? - clone = InferenceContext(self.path, infered=self.infered) - clone.callcontext = self.callcontext - clone.boundnode = self.boundnode - return clone - - def cache_generator(self, key, generator): - results = [] - for result in generator: - results.append(result) - yield result - - self.infered[key] = tuple(results) - return - - @contextmanager - def restore_path(self): - path = set(self.path) - yield - self.path = path - -def copy_context(context): - if context is not None: - return context.clone() - else: - return InferenceContext() - - def _infer_stmts(stmts, context, frame=None): - """return an iterator on statements inferred by each statement in - """ + """Return an iterator on statements inferred by each statement in *stmts*.""" stmt = None - infered = False + inferred = False if context is not None: name = context.lookupname context = context.clone() else: name = None - context = InferenceContext() + context = contextmod.InferenceContext() + for stmt in stmts: - if stmt is YES: + if stmt is util.YES: yield stmt - infered = True + inferred = True continue context.lookupname = stmt._infer_name(frame, name) try: - for infered in stmt.infer(context): - yield infered - infered = True - except UnresolvableName: + for inferred in stmt.infer(context=context): + yield inferred + inferred = True + except exceptions.UnresolvableName: continue - except InferenceError: - yield YES - infered = True - if not infered: - raise InferenceError(str(stmt)) - - -# special inference objects (e.g. may be returned as nodes by .infer()) ####### - -class _Yes(object): - """a yes object""" - def __repr__(self): - return 'YES' - def __getattribute__(self, name): - if name == 'next': - raise AttributeError('next method should not be called') - if name.startswith('__') and name.endswith('__'): - # to avoid inspection pb - return super(_Yes, self).__getattribute__(name) - return self - def __call__(self, *args, **kwargs): - return self - - -YES = _Yes() + except exceptions.InferenceError: + yield util.YES + inferred = True + if not inferred: + raise exceptions.InferenceError(str(stmt)) class Instance(Proxy): @@ -158,7 +113,7 @@ class Instance(Proxy): def getattr(self, name, context=None, lookupclass=True): try: values = self._proxied.instance_attr(name, context) - except NotFoundError: + except exceptions.NotFoundError: if name == '__class__': return [self._proxied] if lookupclass: @@ -167,23 +122,22 @@ def getattr(self, name, context=None, lookupclass=True): if name in ('__name__', '__bases__', '__mro__', '__subclasses__'): return self._proxied.local_attr(name) return self._proxied.getattr(name, context) - raise NotFoundError(name) + raise exceptions.NotFoundError(name) # since we've no context information, return matching class members as # well if lookupclass: try: return values + self._proxied.getattr(name, context) - except NotFoundError: + except exceptions.NotFoundError: pass return values def igetattr(self, name, context=None): """inferred getattr""" if not context: - context = InferenceContext() + context = contextmod.InferenceContext() try: # avoid recursively inferring the same attr on the same class - context.push((self._proxied, name)) # XXX frame should be self._proxied, or not ? get_attr = self.getattr(name, context, lookupclass=False) @@ -192,38 +146,49 @@ def igetattr(self, name, context=None): context, frame=self, ) - except NotFoundError: + except exceptions.NotFoundError: try: # fallback to class'igetattr since it has some logic to handle # descriptors return self._wrap_attr(self._proxied.igetattr(name, context), context) - except NotFoundError: - raise InferenceError(name) + except exceptions.NotFoundError: + raise exceptions.InferenceError(name) def _wrap_attr(self, attrs, context=None): """wrap bound methods of attrs in a InstanceMethod proxies""" for attr in attrs: if isinstance(attr, UnboundMethod): - if BUILTINS + '.property' in attr.decoratornames(): - for infered in attr.infer_call_result(self, context): - yield infered + if _is_property(attr): + for inferred in attr.infer_call_result(self, context): + yield inferred else: yield BoundMethod(attr, self) + elif hasattr(attr, 'name') and attr.name == '': + # This is a lambda function defined at class level, + # since its scope is the underlying _proxied class. + # Unfortunately, we can't do an isinstance check here, + # because of the circular dependency between astroid.bases + # and astroid.scoped_nodes. + if attr.statement().scope() == self._proxied: + if attr.args.args and attr.args.args[0].name == 'self': + yield BoundMethod(attr, self) + continue + yield attr else: yield attr def infer_call_result(self, caller, context=None): """infer what a class instance is returning when called""" - infered = False + inferred = False for node in self._proxied.igetattr('__call__', context): - if node is YES: + if node is util.YES or not node.callable(): continue for res in node.infer_call_result(caller, context): - infered = True + inferred = True yield res - if not infered: - raise InferenceError() + if not inferred: + raise exceptions.InferenceError() def __repr__(self): return '' % (self._proxied.root().name, @@ -237,7 +202,7 @@ def callable(self): try: self._proxied.getattr('__call__') return True - except NotFoundError: + except exceptions.NotFoundError: return False def pytype(self): @@ -247,6 +212,12 @@ def display_type(self): return 'Instance of' + # TODO(cpopa): this is set in inference.py + # The circular dependency hell goes deeper and deeper. + # pylint: disable=unused-argument + def getitem(self, index, context=None): + pass + class UnboundMethod(Proxy): """a special node representing a method not bound to an instance""" def __repr__(self): @@ -261,12 +232,12 @@ def is_bound(self): def getattr(self, name, context=None): if name == 'im_func': return [self._proxied] - return super(UnboundMethod, self).getattr(name, context) + return self._proxied.getattr(name, context) def igetattr(self, name, context=None): if name == 'im_func': return iter((self._proxied,)) - return super(UnboundMethod, self).igetattr(name, context) + return self._proxied.igetattr(name, context) def infer_call_result(self, caller, context): # If we're unbound method __new__ of builtin object, the result is an @@ -274,7 +245,7 @@ def infer_call_result(self, caller, context): if (self._proxied.name == '__new__' and self._proxied.parent.frame().qname() == '%s.object' % BUILTINS): infer = caller.args[0].infer() if caller.args else [] - return ((x is YES and x or Instance(x)) for x in infer) + return ((x is util.YES and x or Instance(x)) for x in infer) return self._proxied.infer_call_result(caller, context) @@ -287,10 +258,13 @@ def __init__(self, proxy, bound): def is_bound(self): return True - def infer_call_result(self, caller, context): + def infer_call_result(self, caller, context=None): + + if context is None: + context = contextmod.InferenceContext() context = context.clone() context.boundnode = self.bound - return self._proxied.infer_call_result(caller, context) + return super(BoundMethod, self).infer_call_result(caller, context) class Generator(Instance): @@ -318,10 +292,11 @@ def __str__(self): def path_wrapper(func): """return the given infer function wrapped to handle the path""" + @functools.wraps(func) def wrapped(node, context=None, _func=func, **kwargs): """wrapper function handling context""" if context is None: - context = InferenceContext() + context = contextmod.InferenceContext() context.push(node) yielded = set() for res in _func(node, context, **kwargs): @@ -330,30 +305,28 @@ def wrapped(node, context=None, _func=func, **kwargs): ares = res._proxied else: ares = res - if not ares in yielded: + if ares not in yielded: yield res yielded.add(ares) return wrapped -def yes_if_nothing_infered(func): - def wrapper(*args, **kwargs): - infered = False - for node in func(*args, **kwargs): - infered = True - yield node - if not infered: - yield YES - return wrapper - -def raise_if_nothing_infered(func): - def wrapper(*args, **kwargs): - infered = False - for node in func(*args, **kwargs): - infered = True - yield node - if not infered: - raise InferenceError() - return wrapper +@wrapt.decorator +def yes_if_nothing_inferred(func, instance, args, kwargs): + inferred = False + for node in func(*args, **kwargs): + inferred = True + yield node + if not inferred: + yield util.YES + +@wrapt.decorator +def raise_if_nothing_inferred(func, instance, args, kwargs): + inferred = False + for node in func(*args, **kwargs): + inferred = True + yield node + if not inferred: + raise exceptions.InferenceError() # Node ###################################################################### @@ -364,8 +337,8 @@ class NodeNG(object): It represents a node of the new abstract syntax tree. """ is_statement = False - optional_assign = False # True for For (and for Comprehension if py <3.0) - is_function = False # True for Function nodes + optional_assign = False # True for For (and for Comprehension if py <3.0) + is_function = False # True for FunctionDef nodes # attributes below are set by the builder module or by raw factories lineno = None fromlineno = None @@ -389,7 +362,7 @@ def infer(self, context=None, **kwargs): # explicit_inference is not bound, give it self explicitly try: return self._explicit_inference(self, context, **kwargs) - except UseInferenceDefault: + except exceptions.UseInferenceDefault: pass if not context: @@ -397,8 +370,8 @@ def infer(self, context=None, **kwargs): key = (self, context.lookupname, context.callcontext, context.boundnode) - if key in context.infered: - return iter(context.infered[key]) + if key in context.inferred: + return iter(context.inferred[key]) return context.cache_generator(key, self._infer(context, **kwargs)) @@ -438,7 +411,7 @@ def last_child(self): attr = getattr(self, field) if not attr: # None or empty listy / tuple continue - if attr.__class__ in (list, tuple): + if isinstance(attr, (list, tuple)): return attr[-1] else: return attr @@ -460,13 +433,16 @@ def statement(self): return self.parent.statement() def frame(self): - """return the first parent frame node (i.e. Module, Function or Class) + """return the first parent frame node (i.e. Module, FunctionDef or + ClassDef) + """ return self.parent.frame() def scope(self): - """return the first node defining a new scope (i.e. Module, Function, - Class, Lambda but also GenExpr) + """return the first node defining a new scope (i.e. Module, + FunctionDef, ClassDef, Lambda but also GenExpr) + """ return self.parent.scope() @@ -483,11 +459,12 @@ def child_sequence(self, child): if node_or_sequence is child: return [node_or_sequence] # /!\ compiler.ast Nodes have an __iter__ walking over child nodes - if isinstance(node_or_sequence, (tuple, list)) and child in node_or_sequence: + if (isinstance(node_or_sequence, (tuple, list)) + and child in node_or_sequence): return node_or_sequence - else: - msg = 'Could not find %s in %s\'s children' - raise AstroidError(msg % (repr(child), repr(self))) + + msg = 'Could not find %s in %s\'s children' + raise exceptions.AstroidError(msg % (repr(child), repr(self))) def locate_child(self, child): """return a 2-uple (child attribute name, sequence or node)""" @@ -499,7 +476,7 @@ def locate_child(self, child): if isinstance(node_or_sequence, (tuple, list)) and child in node_or_sequence: return field, node_or_sequence msg = 'Could not find %s in %s\'s children' - raise AstroidError(msg % (repr(child), repr(self))) + raise exceptions.AstroidError(msg % (repr(child), repr(self))) # FIXME : should we merge child_sequence and locate_child ? locate_child # is only used in are_exclusive, child_sequence one time in pylint. @@ -532,14 +509,14 @@ def nearest(self, nodes): # these are lazy because they're relatively expensive to compute for every # single node, and they rarely get looked at - @cachedproperty + @decoratorsmod.cachedproperty def fromlineno(self): if self.lineno is None: return self._fixed_source_line() else: return self.lineno - @cachedproperty + @decoratorsmod.cachedproperty def tolineno(self): if not self._astroid_fields: # can't have children @@ -597,20 +574,27 @@ def nodes_of_class(self, klass, skip_klass=None): yield matching def _infer_name(self, frame, name): - # overridden for From, Import, Global, TryExcept and Arguments + # overridden for ImportFrom, Import, Global, TryExcept and Arguments return None def _infer(self, context=None): """we don't know how to resolve a statement by default""" # this method is overridden by most concrete classes - raise InferenceError(self.__class__.__name__) + raise exceptions.InferenceError(self.__class__.__name__) - def infered(self): - '''return list of infered values for a more simple inference usage''' + def inferred(self): + '''return list of inferred values for a more simple inference usage''' return list(self.infer()) + def infered(self): + warnings.warn('%s.infered() is deprecated and slated for removal ' + 'in astroid 2.0, use %s.inferred() instead.' + % (type(self).__name__, type(self).__name__), + PendingDeprecationWarning, stacklevel=2) + return self.inferred() + def instanciate_class(self): - """instanciate a node if it is a Class node, else return self""" + """instanciate a node if it is a ClassDef node, else return self""" return self def has_base(self, node): diff --git a/pymode/libs/astroid/brain/builtin_inference.py b/pymode/libs/astroid/brain/brain_builtin_inference.py similarity index 63% rename from pymode/libs/astroid/brain/builtin_inference.py rename to pymode/libs/astroid/brain/brain_builtin_inference.py index f60e7913..ed78111f 100644 --- a/pymode/libs/astroid/brain/builtin_inference.py +++ b/pymode/libs/astroid/brain/brain_builtin_inference.py @@ -7,9 +7,11 @@ import six from astroid import (MANAGER, UseInferenceDefault, inference_tip, YES, InferenceError, UnresolvableName) +from astroid import arguments from astroid import nodes +from astroid import objects from astroid.builder import AstroidBuilder - +from astroid import util def _extend_str(class_node, rvalue): """function to extend builtin str/unicode class""" @@ -51,7 +53,7 @@ def lstrip(self, chars=None): def rstrip(self, chars=None): return {rvalue} def rjust(self, width, fillchar=None): - return {rvalue} + return {rvalue} def center(self, width, fillchar=None): return {rvalue} def ljust(self, width, fillchar=None): @@ -60,7 +62,7 @@ def ljust(self, width, fillchar=None): code = code.format(rvalue=rvalue) fake = AstroidBuilder(MANAGER).string_build(code)['whatever'] for method in fake.mymethods(): - class_node.locals[method.name] = [method] + class_node._locals[method.name] = [method] method.parent = class_node def extend_builtins(class_transforms): @@ -86,12 +88,17 @@ def register_builtin_transform(transform, builtin_name): def _transform_wrapper(node, context=None): result = transform(node, context=context) if result: - result.parent = node + if not result.parent: + # Let the transformation function determine + # the parent for its result. Otherwise, + # we set it to be the node we transformed from. + result.parent = node + result.lineno = node.lineno result.col_offset = node.col_offset return iter([result]) - MANAGER.register_transform(nodes.CallFunc, + MANAGER.register_transform(nodes.Call, inference_tip(_transform_wrapper), lambda n: (isinstance(n.func, nodes.Name) and n.func.name == builtin_name)) @@ -108,13 +115,13 @@ def _generic_inference(node, context, node_type, transform): transformed = transform(arg) if not transformed: try: - infered = next(arg.infer(context=context)) + inferred = next(arg.infer(context=context)) except (InferenceError, StopIteration): raise UseInferenceDefault() - if infered is YES: + if inferred is util.YES: raise UseInferenceDefault() - transformed = transform(infered) - if not transformed or transformed is YES: + transformed = transform(inferred) + if not transformed or transformed is util.YES: raise UseInferenceDefault() return transformed @@ -172,19 +179,25 @@ def _infer_builtin(node, context, iterables=(nodes.List, nodes.Tuple), build_elts=set) +infer_frozenset = partial( + _infer_builtin, + klass=objects.FrozenSet, + iterables=(nodes.List, nodes.Tuple, nodes.Set), + build_elts=frozenset) + def _get_elts(arg, context): is_iterable = lambda n: isinstance(n, (nodes.List, nodes.Tuple, nodes.Set)) try: - infered = next(arg.infer(context)) + inferred = next(arg.infer(context)) except (InferenceError, UnresolvableName): raise UseInferenceDefault() - if isinstance(infered, nodes.Dict): - items = infered.items - elif is_iterable(infered): + if isinstance(inferred, nodes.Dict): + items = inferred.items + elif is_iterable(inferred): items = [] - for elt in infered.elts: + for elt in inferred.elts: # If an item is not a pair of two items, # then fallback to the default inference. # Also, take in consideration only hashable items, @@ -213,24 +226,28 @@ def infer_dict(node, context=None): * dict(mapping, **kwargs) * dict(**kwargs) - If a case can't be infered, we'll fallback to default inference. + If a case can't be inferred, we'll fallback to default inference. """ - has_keywords = lambda args: all(isinstance(arg, nodes.Keyword) - for arg in args) - if not node.args and not node.kwargs: + call = arguments.CallSite.from_call(node) + if call.has_invalid_arguments() or call.has_invalid_keywords(): + raise UseInferenceDefault + + args = call.positional_arguments + kwargs = list(call.keyword_arguments.items()) + + if not args and not kwargs: # dict() return nodes.Dict() - elif has_keywords(node.args) and node.args: + elif kwargs and not args: # dict(a=1, b=2, c=4) - items = [(nodes.Const(arg.arg), arg.value) for arg in node.args] - elif (len(node.args) >= 2 and - has_keywords(node.args[1:])): + items = [(nodes.Const(key), value) for key, value in kwargs] + elif len(args) == 1 and kwargs: # dict(some_iterable, b=2, c=4) - elts = _get_elts(node.args[0], context) - keys = [(nodes.Const(arg.arg), arg.value) for arg in node.args[1:]] + elts = _get_elts(args[0], context) + keys = [(nodes.Const(key), value) for key, value in kwargs] items = elts + keys - elif len(node.args) == 1: - items = _get_elts(node.args[0], context) + elif len(args) == 1: + items = _get_elts(args[0], context) else: raise UseInferenceDefault() @@ -238,8 +255,82 @@ def infer_dict(node, context=None): empty.items = items return empty + +def _node_class(node): + klass = node.frame() + while klass is not None and not isinstance(klass, nodes.ClassDef): + if klass.parent is None: + klass = None + else: + klass = klass.parent.frame() + return klass + + +def infer_super(node, context=None): + """Understand super calls. + + There are some restrictions for what can be understood: + + * unbounded super (one argument form) is not understood. + + * if the super call is not inside a function (classmethod or method), + then the default inference will be used. + + * if the super arguments can't be infered, the default inference + will be used. + """ + if len(node.args) == 1: + # Ignore unbounded super. + raise UseInferenceDefault + + scope = node.scope() + if not isinstance(scope, nodes.FunctionDef): + # Ignore non-method uses of super. + raise UseInferenceDefault + if scope.type not in ('classmethod', 'method'): + # Not interested in staticmethods. + raise UseInferenceDefault + + cls = _node_class(scope) + if not len(node.args): + mro_pointer = cls + # In we are in a classmethod, the interpreter will fill + # automatically the class as the second argument, not an instance. + if scope.type == 'classmethod': + mro_type = cls + else: + mro_type = cls.instantiate_class() + else: + # TODO(cpopa): support flow control (multiple inference values). + try: + mro_pointer = next(node.args[0].infer(context=context)) + except InferenceError: + raise UseInferenceDefault + try: + mro_type = next(node.args[1].infer(context=context)) + except InferenceError: + raise UseInferenceDefault + + if mro_pointer is YES or mro_type is YES: + # No way we could understand this. + raise UseInferenceDefault + + super_obj = objects.Super(mro_pointer=mro_pointer, + mro_type=mro_type, + self_class=cls, + scope=scope) + super_obj.parent = node + return iter([super_obj]) + + # Builtins inference +MANAGER.register_transform(nodes.Call, + inference_tip(infer_super), + lambda n: (isinstance(n.func, nodes.Name) and + n.func.name == 'super')) + register_builtin_transform(infer_tuple, 'tuple') register_builtin_transform(infer_set, 'set') register_builtin_transform(infer_list, 'list') register_builtin_transform(infer_dict, 'dict') +register_builtin_transform(infer_frozenset, 'frozenset') diff --git a/pymode/libs/astroid/brain/brain_dateutil.py b/pymode/libs/astroid/brain/brain_dateutil.py new file mode 100644 index 00000000..0b45412c --- /dev/null +++ b/pymode/libs/astroid/brain/brain_dateutil.py @@ -0,0 +1,15 @@ +"""Astroid hooks for dateutil""" + +import textwrap + +from astroid import MANAGER, register_module_extender +from astroid.builder import AstroidBuilder + +def dateutil_transform(): + return AstroidBuilder(MANAGER).string_build(textwrap.dedent(''' + import datetime + def parse(timestr, parserinfo=None, **kwargs): + return datetime.datetime() + ''')) + +register_module_extender(MANAGER, 'dateutil.parser', dateutil_transform) diff --git a/pymode/libs/astroid/brain/py2gi.py b/pymode/libs/astroid/brain/brain_gi.py similarity index 72% rename from pymode/libs/astroid/brain/py2gi.py rename to pymode/libs/astroid/brain/brain_gi.py index 6747898d..d9fc1b45 100644 --- a/pymode/libs/astroid/brain/py2gi.py +++ b/pymode/libs/astroid/brain/brain_gi.py @@ -7,8 +7,9 @@ import itertools import sys import re +import warnings -from astroid import MANAGER, AstroidBuildingException +from astroid import MANAGER, AstroidBuildingException, nodes from astroid.builder import AstroidBuilder @@ -46,13 +47,13 @@ def _gi_build_stub(parent): elif (inspect.ismethod(obj) or inspect.ismethoddescriptor(obj)): methods[name] = obj - elif type(obj) in [int, str]: - constants[name] = obj elif (str(obj).startswith(", ) + # Only accept function calls with two constant arguments + if len(node.args) != 2: + return False -MANAGER.register_failed_import_hook(_import_gi_module) + if not all(isinstance(arg, nodes.Const) for arg in node.args): + return False + + func = node.func + if isinstance(func, nodes.Attribute): + if func.attrname != 'require_version': + return False + if isinstance(func.expr, nodes.Name) and func.expr.name == 'gi': + return True + + return False + if isinstance(func, nodes.Name): + return func.name == 'require_version' + + return False + +def _register_require_version(node): + # Load the gi.require_version locally + try: + import gi + gi.require_version(node.args[0].value, node.args[1].value) + except Exception: + pass + + return node + +MANAGER.register_failed_import_hook(_import_gi_module) +MANAGER.register_transform(nodes.Call, _register_require_version, _looks_like_require_version) diff --git a/pymode/libs/astroid/brain/py2mechanize.py b/pymode/libs/astroid/brain/brain_mechanize.py similarity index 100% rename from pymode/libs/astroid/brain/py2mechanize.py rename to pymode/libs/astroid/brain/brain_mechanize.py diff --git a/pymode/libs/astroid/brain/pynose.py b/pymode/libs/astroid/brain/brain_nose.py similarity index 92% rename from pymode/libs/astroid/brain/pynose.py rename to pymode/libs/astroid/brain/brain_nose.py index 67a6fb8f..4b077843 100644 --- a/pymode/libs/astroid/brain/pynose.py +++ b/pymode/libs/astroid/brain/brain_nose.py @@ -48,11 +48,14 @@ class Test(unittest.TestCase): if method.name.startswith('assert') and '_' not in method.name: pep8_name = _pep8(method.name) yield pep8_name, astroid.BoundMethod(method, case) + if method.name == 'assertEqual': + # nose also exports assert_equals. + yield 'assert_equals', astroid.BoundMethod(method, case) def _nose_tools_transform(node): for method_name, method in _nose_tools_functions(): - node.locals[method_name] = [method] + node._locals[method_name] = [method] def _nose_tools_trivial_transform(): diff --git a/pymode/libs/astroid/brain/brain_numpy.py b/pymode/libs/astroid/brain/brain_numpy.py new file mode 100644 index 00000000..75f4f18f --- /dev/null +++ b/pymode/libs/astroid/brain/brain_numpy.py @@ -0,0 +1,62 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) any +# later version. +# +# astroid 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 Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . + +"""Astroid hooks for numpy.""" + +import astroid + + +# TODO(cpopa): drop when understanding augmented assignments + +def numpy_core_transform(): + return astroid.parse(''' + from numpy.core import numeric + from numpy.core import fromnumeric + from numpy.core import defchararray + from numpy.core import records + from numpy.core import function_base + from numpy.core import machar + from numpy.core import getlimits + from numpy.core import shape_base + __all__ = (['char', 'rec', 'memmap', 'chararray'] + numeric.__all__ + + fromnumeric.__all__ + + records.__all__ + + function_base.__all__ + + machar.__all__ + + getlimits.__all__ + + shape_base.__all__) + ''') + + +def numpy_transform(): + return astroid.parse(''' + from numpy import core + from numpy import matrixlib as _mat + from numpy import lib + __all__ = ['add_newdocs', + 'ModuleDeprecationWarning', + 'VisibleDeprecationWarning', 'linalg', 'fft', 'random', + 'ctypeslib', 'ma', + '__version__', 'pkgload', 'PackageLoader', + 'show_config'] + core.__all__ + _mat.__all__ + lib.__all__ + + ''') + + +astroid.register_module_extender(astroid.MANAGER, 'numpy.core', numpy_core_transform) +astroid.register_module_extender(astroid.MANAGER, 'numpy', numpy_transform) diff --git a/pymode/libs/astroid/brain/py2pytest.py b/pymode/libs/astroid/brain/brain_pytest.py similarity index 92% rename from pymode/libs/astroid/brain/py2pytest.py rename to pymode/libs/astroid/brain/brain_pytest.py index e24d449c..4f615c14 100644 --- a/pymode/libs/astroid/brain/py2pytest.py +++ b/pymode/libs/astroid/brain/brain_pytest.py @@ -1,31 +1,31 @@ -"""Astroid hooks for pytest.""" - -from astroid import MANAGER, register_module_extender -from astroid.builder import AstroidBuilder - - -def pytest_transform(): - return AstroidBuilder(MANAGER).string_build(''' - -try: - import _pytest.mark - import _pytest.recwarn - import _pytest.runner - import _pytest.python -except ImportError: - pass -else: - deprecated_call = _pytest.recwarn.deprecated_call - exit = _pytest.runner.exit - fail = _pytest.runner.fail - fixture = _pytest.python.fixture - importorskip = _pytest.runner.importorskip - mark = _pytest.mark.MarkGenerator() - raises = _pytest.python.raises - skip = _pytest.runner.skip - yield_fixture = _pytest.python.yield_fixture - -''') - -register_module_extender(MANAGER, 'pytest', pytest_transform) -register_module_extender(MANAGER, 'py.test', pytest_transform) +"""Astroid hooks for pytest.""" +from __future__ import absolute_import +from astroid import MANAGER, register_module_extender +from astroid.builder import AstroidBuilder + + +def pytest_transform(): + return AstroidBuilder(MANAGER).string_build(''' + +try: + import _pytest.mark + import _pytest.recwarn + import _pytest.runner + import _pytest.python +except ImportError: + pass +else: + deprecated_call = _pytest.recwarn.deprecated_call + exit = _pytest.runner.exit + fail = _pytest.runner.fail + fixture = _pytest.python.fixture + importorskip = _pytest.runner.importorskip + mark = _pytest.mark.MarkGenerator() + raises = _pytest.python.raises + skip = _pytest.runner.skip + yield_fixture = _pytest.python.yield_fixture + +''') + +register_module_extender(MANAGER, 'pytest', pytest_transform) +register_module_extender(MANAGER, 'py.test', pytest_transform) diff --git a/pymode/libs/astroid/brain/brain_qt.py b/pymode/libs/astroid/brain/brain_qt.py new file mode 100644 index 00000000..1a03b2be --- /dev/null +++ b/pymode/libs/astroid/brain/brain_qt.py @@ -0,0 +1,44 @@ +"""Astroid hooks for the PyQT library.""" + +from astroid import MANAGER, register_module_extender +from astroid.builder import AstroidBuilder +from astroid import nodes +from astroid import parse + + +def _looks_like_signal(node, signal_name='pyqtSignal'): + if '__class__' in node._instance_attrs: + cls = node._instance_attrs['__class__'][0] + return cls.name == signal_name + return False + + +def transform_pyqt_signal(node): + module = parse(''' + class pyqtSignal(object): + def connect(self, slot, type=None, no_receiver_check=False): + pass + def disconnect(self, slot): + pass + def emit(self, *args): + pass + ''') + signal_cls = module['pyqtSignal'] + node._instance_attrs['emit'] = signal_cls['emit'] + node._instance_attrs['disconnect'] = signal_cls['disconnect'] + node._instance_attrs['connect'] = signal_cls['connect'] + + +def pyqt4_qtcore_transform(): + return AstroidBuilder(MANAGER).string_build(''' + +def SIGNAL(signal_name): pass + +class QObject(object): + def emit(self, signal): pass +''') + + +register_module_extender(MANAGER, 'PyQt4.QtCore', pyqt4_qtcore_transform) +MANAGER.register_transform(nodes.FunctionDef, transform_pyqt_signal, + _looks_like_signal) \ No newline at end of file diff --git a/pymode/libs/astroid/brain/pysix_moves.py b/pymode/libs/astroid/brain/brain_six.py similarity index 91% rename from pymode/libs/astroid/brain/pysix_moves.py rename to pymode/libs/astroid/brain/brain_six.py index 548d9761..9596a6c8 100644 --- a/pymode/libs/astroid/brain/pysix_moves.py +++ b/pymode/libs/astroid/brain/brain_six.py @@ -23,7 +23,12 @@ from astroid import MANAGER, register_module_extender from astroid.builder import AstroidBuilder -from astroid.exceptions import AstroidBuildingException +from astroid.exceptions import AstroidBuildingException, InferenceError +from astroid import nodes + + +SIX_ADD_METACLASS = 'six.add_metaclass' + def _indent(text, prefix, predicate=None): """Adds 'prefix' to the beginning of selected lines in 'text'. @@ -254,8 +259,30 @@ def _six_fail_hook(modname): module.name = 'six.moves' return module +def transform_six_add_metaclass(node): + """Check if the given class node is decorated with *six.add_metaclass* + + If so, inject its argument as the metaclass of the underlying class. + """ + if not node.decorators: + return + + for decorator in node.decorators.nodes: + if not isinstance(decorator, nodes.Call): + continue + + try: + func = next(decorator.func.infer()) + except InferenceError: + continue + if func.qname() == SIX_ADD_METACLASS and decorator.args: + metaclass = decorator.args[0] + node._metaclass = metaclass + return node + register_module_extender(MANAGER, 'six', six_moves_transform) register_module_extender(MANAGER, 'requests.packages.urllib3.packages.six', six_moves_transform) MANAGER.register_failed_import_hook(_six_fail_hook) +MANAGER.register_transform(nodes.ClassDef, transform_six_add_metaclass) diff --git a/pymode/libs/astroid/brain/brain_stdlib.py b/pymode/libs/astroid/brain/brain_stdlib.py new file mode 100644 index 00000000..ac731887 --- /dev/null +++ b/pymode/libs/astroid/brain/brain_stdlib.py @@ -0,0 +1,460 @@ + +"""Astroid hooks for the Python 2 standard library. + +Currently help understanding of : + +* hashlib.md5 and hashlib.sha1 +""" + +import functools +import sys +from textwrap import dedent + +from astroid import ( + MANAGER, UseInferenceDefault, inference_tip, BoundMethod, + InferenceError, register_module_extender) +from astroid import exceptions +from astroid import nodes +from astroid.builder import AstroidBuilder +from astroid import util + +PY3K = sys.version_info > (3, 0) +PY33 = sys.version_info >= (3, 3) +PY34 = sys.version_info >= (3, 4) + +# general function + +def infer_func_form(node, base_type, context=None, enum=False): + """Specific inference function for namedtuple or Python 3 enum. """ + def infer_first(node): + if node is util.YES: + raise UseInferenceDefault + try: + value = next(node.infer(context=context)) + if value is util.YES: + raise UseInferenceDefault() + else: + return value + except StopIteration: + raise InferenceError() + + # node is a Call node, class name as first argument and generated class + # attributes as second argument + if len(node.args) != 2: + # something weird here, go back to class implementation + raise UseInferenceDefault() + # namedtuple or enums list of attributes can be a list of strings or a + # whitespace-separate string + try: + name = infer_first(node.args[0]).value + names = infer_first(node.args[1]) + try: + attributes = names.value.replace(',', ' ').split() + except AttributeError: + if not enum: + attributes = [infer_first(const).value for const in names.elts] + else: + # Enums supports either iterator of (name, value) pairs + # or mappings. + # TODO: support only list, tuples and mappings. + if hasattr(names, 'items') and isinstance(names.items, list): + attributes = [infer_first(const[0]).value + for const in names.items + if isinstance(const[0], nodes.Const)] + elif hasattr(names, 'elts'): + # Enums can support either ["a", "b", "c"] + # or [("a", 1), ("b", 2), ...], but they can't + # be mixed. + if all(isinstance(const, nodes.Tuple) + for const in names.elts): + attributes = [infer_first(const.elts[0]).value + for const in names.elts + if isinstance(const, nodes.Tuple)] + else: + attributes = [infer_first(const).value + for const in names.elts] + else: + raise AttributeError + if not attributes: + raise AttributeError + except (AttributeError, exceptions.InferenceError): + raise UseInferenceDefault() + # we want to return a Class node instance with proper attributes set + class_node = nodes.ClassDef(name, 'docstring') + class_node.parent = node.parent + # set base class=tuple + class_node.bases.append(base_type) + # XXX add __init__(*attributes) method + for attr in attributes: + fake_node = nodes.EmptyNode() + fake_node.parent = class_node + fake_node.attrname = attr + class_node._instance_attrs[attr] = [fake_node] + return class_node, name, attributes + + +# module specific transformation functions ##################################### + +def hashlib_transform(): + template = ''' + +class %(name)s(object): + def __init__(self, value=''): pass + def digest(self): + return %(digest)s + def copy(self): + return self + def update(self, value): pass + def hexdigest(self): + return '' + @property + def name(self): + return %(name)r + @property + def block_size(self): + return 1 + @property + def digest_size(self): + return 1 +''' + algorithms = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512') + classes = "".join( + template % {'name': hashfunc, 'digest': 'b""' if PY3K else '""'} + for hashfunc in algorithms) + return AstroidBuilder(MANAGER).string_build(classes) + + +def collections_transform(): + return AstroidBuilder(MANAGER).string_build(''' + +class defaultdict(dict): + default_factory = None + def __missing__(self, key): pass + +class deque(object): + maxlen = 0 + def __init__(self, iterable=None, maxlen=None): + self.iterable = iterable + def append(self, x): pass + def appendleft(self, x): pass + def clear(self): pass + def count(self, x): return 0 + def extend(self, iterable): pass + def extendleft(self, iterable): pass + def pop(self): pass + def popleft(self): pass + def remove(self, value): pass + def reverse(self): pass + def rotate(self, n): pass + def __iter__(self): return self + def __reversed__(self): return self.iterable[::-1] + def __getitem__(self, index): pass + def __setitem__(self, index, value): pass + def __delitem__(self, index): pass +''') + + +def pkg_resources_transform(): + return AstroidBuilder(MANAGER).string_build(''' +def require(*requirements): + return pkg_resources.working_set.require(*requirements) + +def run_script(requires, script_name): + return pkg_resources.working_set.run_script(requires, script_name) + +def iter_entry_points(group, name=None): + return pkg_resources.working_set.iter_entry_points(group, name) + +def resource_exists(package_or_requirement, resource_name): + return get_provider(package_or_requirement).has_resource(resource_name) + +def resource_isdir(package_or_requirement, resource_name): + return get_provider(package_or_requirement).resource_isdir( + resource_name) + +def resource_filename(package_or_requirement, resource_name): + return get_provider(package_or_requirement).get_resource_filename( + self, resource_name) + +def resource_stream(package_or_requirement, resource_name): + return get_provider(package_or_requirement).get_resource_stream( + self, resource_name) + +def resource_string(package_or_requirement, resource_name): + return get_provider(package_or_requirement).get_resource_string( + self, resource_name) + +def resource_listdir(package_or_requirement, resource_name): + return get_provider(package_or_requirement).resource_listdir( + resource_name) + +def extraction_error(): + pass + +def get_cache_path(archive_name, names=()): + extract_path = self.extraction_path or get_default_cache() + target_path = os.path.join(extract_path, archive_name+'-tmp', *names) + return target_path + +def postprocess(tempname, filename): + pass + +def set_extraction_path(path): + pass + +def cleanup_resources(force=False): + pass + +''') + + +def subprocess_transform(): + if PY3K: + communicate = (bytes('string', 'ascii'), bytes('string', 'ascii')) + communicate_signature = 'def communicate(self, input=None, timeout=None)' + init = """ + def __init__(self, args, bufsize=0, executable=None, + stdin=None, stdout=None, stderr=None, + preexec_fn=None, close_fds=False, shell=False, + cwd=None, env=None, universal_newlines=False, + startupinfo=None, creationflags=0, restore_signals=True, + start_new_session=False, pass_fds=()): + pass + """ + else: + communicate = ('string', 'string') + communicate_signature = 'def communicate(self, input=None)' + init = """ + def __init__(self, args, bufsize=0, executable=None, + stdin=None, stdout=None, stderr=None, + preexec_fn=None, close_fds=False, shell=False, + cwd=None, env=None, universal_newlines=False, + startupinfo=None, creationflags=0): + pass + """ + if PY33: + wait_signature = 'def wait(self, timeout=None)' + else: + wait_signature = 'def wait(self)' + if PY3K: + ctx_manager = ''' + def __enter__(self): return self + def __exit__(self, *args): pass + ''' + else: + ctx_manager = '' + code = dedent(''' + + class Popen(object): + returncode = pid = 0 + stdin = stdout = stderr = file() + + %(init)s + + %(communicate_signature)s: + return %(communicate)r + %(wait_signature)s: + return self.returncode + def poll(self): + return self.returncode + def send_signal(self, signal): + pass + def terminate(self): + pass + def kill(self): + pass + %(ctx_manager)s + ''' % {'init': init, + 'communicate': communicate, + 'communicate_signature': communicate_signature, + 'wait_signature': wait_signature, + 'ctx_manager': ctx_manager}) + return AstroidBuilder(MANAGER).string_build(code) + + +# namedtuple support ########################################################### + +def _looks_like(node, name): + func = node.func + if isinstance(func, nodes.Attribute): + return func.attrname == name + if isinstance(func, nodes.Name): + return func.name == name + return False + +_looks_like_namedtuple = functools.partial(_looks_like, name='namedtuple') +_looks_like_enum = functools.partial(_looks_like, name='Enum') + + +def infer_named_tuple(node, context=None): + """Specific inference function for namedtuple Call node""" + class_node, name, attributes = infer_func_form(node, nodes.Tuple._proxied, + context=context) + fake = AstroidBuilder(MANAGER).string_build(''' +class %(name)s(tuple): + _fields = %(fields)r + def _asdict(self): + return self.__dict__ + @classmethod + def _make(cls, iterable, new=tuple.__new__, len=len): + return new(cls, iterable) + def _replace(self, **kwds): + return self + ''' % {'name': name, 'fields': attributes}) + class_node._locals['_asdict'] = fake.body[0]._locals['_asdict'] + class_node._locals['_make'] = fake.body[0]._locals['_make'] + class_node._locals['_replace'] = fake.body[0]._locals['_replace'] + class_node._locals['_fields'] = fake.body[0]._locals['_fields'] + # we use UseInferenceDefault, we can't be a generator so return an iterator + return iter([class_node]) + + +def infer_enum(node, context=None): + """ Specific inference function for enum Call node. """ + enum_meta = nodes.ClassDef("EnumMeta", 'docstring') + class_node = infer_func_form(node, enum_meta, + context=context, enum=True)[0] + return iter([class_node]) + + +def infer_enum_class(node): + """ Specific inference for enums. """ + names = set(('Enum', 'IntEnum', 'enum.Enum', 'enum.IntEnum')) + for basename in node.basenames: + # TODO: doesn't handle subclasses yet. This implementation + # is a hack to support enums. + if basename not in names: + continue + if node.root().name == 'enum': + # Skip if the class is directly from enum module. + break + for local, values in node._locals.items(): + if any(not isinstance(value, nodes.AssignName) + for value in values): + continue + + stmt = values[0].statement() + if isinstance(stmt.targets[0], nodes.Tuple): + targets = stmt.targets[0].itered() + else: + targets = stmt.targets + + new_targets = [] + for target in targets: + # Replace all the assignments with our mocked class. + classdef = dedent(''' + class %(name)s(%(types)s): + @property + def value(self): + # Not the best return. + return None + @property + def name(self): + return %(name)r + ''' % {'name': target.name, 'types': ', '.join(node.basenames)}) + fake = AstroidBuilder(MANAGER).string_build(classdef)[target.name] + fake.parent = target.parent + for method in node.mymethods(): + fake._locals[method.name] = [method] + new_targets.append(fake.instantiate_class()) + node._locals[local] = new_targets + break + return node + +def multiprocessing_transform(): + module = AstroidBuilder(MANAGER).string_build(dedent(''' + from multiprocessing.managers import SyncManager + def Manager(): + return SyncManager() + ''')) + if not PY34: + return module + + # On Python 3.4, multiprocessing uses a getattr lookup inside contexts, + # in order to get the attributes they need. Since it's extremely + # dynamic, we use this approach to fake it. + node = AstroidBuilder(MANAGER).string_build(dedent(''' + from multiprocessing.context import DefaultContext, BaseContext + default = DefaultContext() + base = BaseContext() + ''')) + try: + context = next(node['default'].infer()) + base = next(node['base'].infer()) + except InferenceError: + return module + + for node in (context, base): + for key, value in node._locals.items(): + if key.startswith("_"): + continue + + value = value[0] + if isinstance(value, nodes.FunctionDef): + # We need to rebound this, since otherwise + # it will have an extra argument (self). + value = BoundMethod(value, node) + module[key] = value + return module + +def multiprocessing_managers_transform(): + return AstroidBuilder(MANAGER).string_build(dedent(''' + import array + import threading + import multiprocessing.pool as pool + + import six + + class Namespace(object): + pass + + class Value(object): + def __init__(self, typecode, value, lock=True): + self._typecode = typecode + self._value = value + def get(self): + return self._value + def set(self, value): + self._value = value + def __repr__(self): + return '%s(%r, %r)'%(type(self).__name__, self._typecode, self._value) + value = property(get, set) + + def Array(typecode, sequence, lock=True): + return array.array(typecode, sequence) + + class SyncManager(object): + Queue = JoinableQueue = six.moves.queue.Queue + Event = threading.Event + RLock = threading.RLock + BoundedSemaphore = threading.BoundedSemaphore + Condition = threading.Condition + Barrier = threading.Barrier + Pool = pool.Pool + list = list + dict = dict + Value = Value + Array = Array + Namespace = Namespace + __enter__ = lambda self: self + __exit__ = lambda *args: args + + def start(self, initializer=None, initargs=None): + pass + def shutdown(self): + pass + ''')) + + +MANAGER.register_transform(nodes.Call, inference_tip(infer_named_tuple), + _looks_like_namedtuple) +MANAGER.register_transform(nodes.Call, inference_tip(infer_enum), + _looks_like_enum) +MANAGER.register_transform(nodes.ClassDef, infer_enum_class) +register_module_extender(MANAGER, 'hashlib', hashlib_transform) +register_module_extender(MANAGER, 'collections', collections_transform) +register_module_extender(MANAGER, 'pkg_resources', pkg_resources_transform) +register_module_extender(MANAGER, 'subprocess', subprocess_transform) +register_module_extender(MANAGER, 'multiprocessing.managers', + multiprocessing_managers_transform) +register_module_extender(MANAGER, 'multiprocessing', multiprocessing_transform) diff --git a/pymode/libs/astroid/brain/py2qt4.py b/pymode/libs/astroid/brain/py2qt4.py deleted file mode 100644 index d5578097..00000000 --- a/pymode/libs/astroid/brain/py2qt4.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Astroid hooks for the Python 2 qt4 module. - -Currently help understanding of : - -* PyQT4.QtCore -""" - -from astroid import MANAGER, register_module_extender -from astroid.builder import AstroidBuilder - - -def pyqt4_qtcore_transform(): - return AstroidBuilder(MANAGER).string_build(''' - -def SIGNAL(signal_name): pass - -class QObject(object): - def emit(self, signal): pass -''') - - -register_module_extender(MANAGER, 'PyQt4.QtCore', pyqt4_qtcore_transform) diff --git a/pymode/libs/astroid/brain/py2stdlib.py b/pymode/libs/astroid/brain/py2stdlib.py deleted file mode 100644 index 2bfcbcd3..00000000 --- a/pymode/libs/astroid/brain/py2stdlib.py +++ /dev/null @@ -1,334 +0,0 @@ - -"""Astroid hooks for the Python 2 standard library. - -Currently help understanding of : - -* hashlib.md5 and hashlib.sha1 -""" - -import sys -from functools import partial -from textwrap import dedent - -from astroid import ( - MANAGER, AsStringRegexpPredicate, - UseInferenceDefault, inference_tip, - YES, InferenceError, register_module_extender) -from astroid import exceptions -from astroid import nodes -from astroid.builder import AstroidBuilder - -PY3K = sys.version_info > (3, 0) -PY33 = sys.version_info >= (3, 3) - -# general function - -def infer_func_form(node, base_type, context=None, enum=False): - """Specific inference function for namedtuple or Python 3 enum. """ - def infer_first(node): - try: - value = next(node.infer(context=context)) - if value is YES: - raise UseInferenceDefault() - else: - return value - except StopIteration: - raise InferenceError() - - # node is a CallFunc node, class name as first argument and generated class - # attributes as second argument - if len(node.args) != 2: - # something weird here, go back to class implementation - raise UseInferenceDefault() - # namedtuple or enums list of attributes can be a list of strings or a - # whitespace-separate string - try: - name = infer_first(node.args[0]).value - names = infer_first(node.args[1]) - try: - attributes = names.value.replace(',', ' ').split() - except AttributeError: - if not enum: - attributes = [infer_first(const).value for const in names.elts] - else: - # Enums supports either iterator of (name, value) pairs - # or mappings. - # TODO: support only list, tuples and mappings. - if hasattr(names, 'items') and isinstance(names.items, list): - attributes = [infer_first(const[0]).value - for const in names.items - if isinstance(const[0], nodes.Const)] - elif hasattr(names, 'elts'): - # Enums can support either ["a", "b", "c"] - # or [("a", 1), ("b", 2), ...], but they can't - # be mixed. - if all(isinstance(const, nodes.Tuple) - for const in names.elts): - attributes = [infer_first(const.elts[0]).value - for const in names.elts - if isinstance(const, nodes.Tuple)] - else: - attributes = [infer_first(const).value - for const in names.elts] - else: - raise AttributeError - if not attributes: - raise AttributeError - except (AttributeError, exceptions.InferenceError) as exc: - raise UseInferenceDefault() - # we want to return a Class node instance with proper attributes set - class_node = nodes.Class(name, 'docstring') - class_node.parent = node.parent - # set base class=tuple - class_node.bases.append(base_type) - # XXX add __init__(*attributes) method - for attr in attributes: - fake_node = nodes.EmptyNode() - fake_node.parent = class_node - class_node.instance_attrs[attr] = [fake_node] - return class_node, name, attributes - - -# module specific transformation functions ##################################### - -def hashlib_transform(): - template = ''' - -class %(name)s(object): - def __init__(self, value=''): pass - def digest(self): - return %(digest)s - def copy(self): - return self - def update(self, value): pass - def hexdigest(self): - return '' - @property - def name(self): - return %(name)r - @property - def block_size(self): - return 1 - @property - def digest_size(self): - return 1 -''' - algorithms = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512') - classes = "".join( - template % {'name': hashfunc, 'digest': 'b""' if PY3K else '""'} - for hashfunc in algorithms) - return AstroidBuilder(MANAGER).string_build(classes) - - -def collections_transform(): - return AstroidBuilder(MANAGER).string_build(''' - -class defaultdict(dict): - default_factory = None - def __missing__(self, key): pass - -class deque(object): - maxlen = 0 - def __init__(self, iterable=None, maxlen=None): pass - def append(self, x): pass - def appendleft(self, x): pass - def clear(self): pass - def count(self, x): return 0 - def extend(self, iterable): pass - def extendleft(self, iterable): pass - def pop(self): pass - def popleft(self): pass - def remove(self, value): pass - def reverse(self): pass - def rotate(self, n): pass - def __iter__(self): return self - -''') - - -def pkg_resources_transform(): - return AstroidBuilder(MANAGER).string_build(''' - -def resource_exists(package_or_requirement, resource_name): - pass - -def resource_isdir(package_or_requirement, resource_name): - pass - -def resource_filename(package_or_requirement, resource_name): - pass - -def resource_stream(package_or_requirement, resource_name): - pass - -def resource_string(package_or_requirement, resource_name): - pass - -def resource_listdir(package_or_requirement, resource_name): - pass - -def extraction_error(): - pass - -def get_cache_path(archive_name, names=()): - pass - -def postprocess(tempname, filename): - pass - -def set_extraction_path(path): - pass - -def cleanup_resources(force=False): - pass - -''') - - -def subprocess_transform(): - if PY3K: - communicate = (bytes('string', 'ascii'), bytes('string', 'ascii')) - init = """ - def __init__(self, args, bufsize=0, executable=None, - stdin=None, stdout=None, stderr=None, - preexec_fn=None, close_fds=False, shell=False, - cwd=None, env=None, universal_newlines=False, - startupinfo=None, creationflags=0, restore_signals=True, - start_new_session=False, pass_fds=()): - pass - """ - else: - communicate = ('string', 'string') - init = """ - def __init__(self, args, bufsize=0, executable=None, - stdin=None, stdout=None, stderr=None, - preexec_fn=None, close_fds=False, shell=False, - cwd=None, env=None, universal_newlines=False, - startupinfo=None, creationflags=0): - pass - """ - if PY33: - wait_signature = 'def wait(self, timeout=None)' - else: - wait_signature = 'def wait(self)' - return AstroidBuilder(MANAGER).string_build(''' - -class Popen(object): - returncode = pid = 0 - stdin = stdout = stderr = file() - - %(init)s - - def communicate(self, input=None): - return %(communicate)r - %(wait_signature)s: - return self.returncode - def poll(self): - return self.returncode - def send_signal(self, signal): - pass - def terminate(self): - pass - def kill(self): - pass - ''' % {'init': init, - 'communicate': communicate, - 'wait_signature': wait_signature}) - - -# namedtuple support ########################################################### - -def looks_like_namedtuple(node): - func = node.func - if type(func) is nodes.Getattr: - return func.attrname == 'namedtuple' - if type(func) is nodes.Name: - return func.name == 'namedtuple' - return False - -def infer_named_tuple(node, context=None): - """Specific inference function for namedtuple CallFunc node""" - class_node, name, attributes = infer_func_form(node, nodes.Tuple._proxied, - context=context) - fake = AstroidBuilder(MANAGER).string_build(''' -class %(name)s(tuple): - _fields = %(fields)r - def _asdict(self): - return self.__dict__ - @classmethod - def _make(cls, iterable, new=tuple.__new__, len=len): - return new(cls, iterable) - def _replace(_self, **kwds): - result = _self._make(map(kwds.pop, %(fields)r, _self)) - if kwds: - raise ValueError('Got unexpected field names: %%r' %% list(kwds)) - return result - ''' % {'name': name, 'fields': attributes}) - class_node.locals['_asdict'] = fake.body[0].locals['_asdict'] - class_node.locals['_make'] = fake.body[0].locals['_make'] - class_node.locals['_replace'] = fake.body[0].locals['_replace'] - class_node.locals['_fields'] = fake.body[0].locals['_fields'] - # we use UseInferenceDefault, we can't be a generator so return an iterator - return iter([class_node]) - -def infer_enum(node, context=None): - """ Specific inference function for enum CallFunc node. """ - enum_meta = nodes.Class("EnumMeta", 'docstring') - class_node = infer_func_form(node, enum_meta, - context=context, enum=True)[0] - return iter([class_node.instanciate_class()]) - -def infer_enum_class(node): - """ Specific inference for enums. """ - names = set(('Enum', 'IntEnum', 'enum.Enum', 'enum.IntEnum')) - for basename in node.basenames: - # TODO: doesn't handle subclasses yet. This implementation - # is a hack to support enums. - if basename not in names: - continue - if node.root().name == 'enum': - # Skip if the class is directly from enum module. - break - for local, values in node.locals.items(): - if any(not isinstance(value, nodes.AssName) - for value in values): - continue - - stmt = values[0].statement() - if isinstance(stmt.targets[0], nodes.Tuple): - targets = stmt.targets[0].itered() - else: - targets = stmt.targets - - new_targets = [] - for target in targets: - # Replace all the assignments with our mocked class. - classdef = dedent(''' - class %(name)s(object): - @property - def value(self): - # Not the best return. - return None - @property - def name(self): - return %(name)r - ''' % {'name': target.name}) - fake = AstroidBuilder(MANAGER).string_build(classdef)[target.name] - fake.parent = target.parent - for method in node.mymethods(): - fake.locals[method.name] = [method] - new_targets.append(fake.instanciate_class()) - node.locals[local] = new_targets - break - return node - - -MANAGER.register_transform(nodes.CallFunc, inference_tip(infer_named_tuple), - looks_like_namedtuple) -MANAGER.register_transform(nodes.CallFunc, inference_tip(infer_enum), - AsStringRegexpPredicate('Enum', 'func')) -MANAGER.register_transform(nodes.Class, infer_enum_class) -register_module_extender(MANAGER, 'hashlib', hashlib_transform) -register_module_extender(MANAGER, 'collections', collections_transform) -register_module_extender(MANAGER, 'pkg_resources', pkg_resources_transform) -register_module_extender(MANAGER, 'subprocess', subprocess_transform) diff --git a/pymode/libs/astroid/builder.py b/pymode/libs/astroid/builder.py index 1fe7a36d..63c156a1 100644 --- a/pymode/libs/astroid/builder.py +++ b/pymode/libs/astroid/builder.py @@ -22,23 +22,26 @@ """ from __future__ import with_statement -__docformat__ = "restructuredtext en" - +import _ast +import os import sys -from os.path import splitext, basename, exists, abspath +import textwrap + +from astroid import bases +from astroid import exceptions +from astroid import manager +from astroid import modutils +from astroid import raw_building +from astroid import rebuilder +from astroid import util -from astroid.exceptions import AstroidBuildingException, InferenceError -from astroid.raw_building import InspectBuilder -from astroid.rebuilder import TreeRebuilder -from astroid.manager import AstroidManager -from astroid.bases import YES, Instance -from astroid.modutils import modpath_from_file -from _ast import PyCF_ONLY_AST -def parse(string): - return compile(string, "", 'exec', PyCF_ONLY_AST) +def _parse(string): + return compile(string, "", 'exec', _ast.PyCF_ONLY_AST) + if sys.version_info >= (3, 0): + # pylint: disable=no-name-in-module; We don't understand flows yet. from tokenize import detect_encoding def open_source_file(filename): @@ -47,10 +50,10 @@ def open_source_file(filename): stream = open(filename, 'r', newline=None, encoding=encoding) try: data = stream.read() - except UnicodeError: # wrong encodingg + except UnicodeError: # wrong encoding # detect_encoding returns utf-8 if no encoding specified msg = 'Wrong (%s) or no encoding specified' % encoding - raise AstroidBuildingException(msg) + raise exceptions.AstroidBuildingException(msg) return stream, encoding, data else: @@ -59,8 +62,7 @@ def open_source_file(filename): _ENCODING_RGX = re.compile(r"\s*#+.*coding[:=]\s*([-\w.]+)") def _guess_encoding(string): - """get encoding from a python file as string or return None if not found - """ + """get encoding from a python file as string or return None if not found""" # check for UTF-8 byte-order mark if string.startswith('\xef\xbb\xbf'): return 'UTF-8' @@ -77,95 +79,101 @@ def open_source_file(filename): encoding = _guess_encoding(data) return stream, encoding, data -# ast NG builder ############################################################## -MANAGER = AstroidManager() +MANAGER = manager.AstroidManager() -class AstroidBuilder(InspectBuilder): - """provide astroid building methods""" - def __init__(self, manager=None): - InspectBuilder.__init__(self) +class AstroidBuilder(raw_building.InspectBuilder): + """Class for building an astroid tree from source code or from a live module. + + The param *manager* specifies the manager class which should be used. + If no manager is given, then the default one will be used. The + param *apply_transforms* determines if the transforms should be + applied after the tree was built from source or from a live object, + by default being True. + """ + + def __init__(self, manager=None, apply_transforms=True): + super(AstroidBuilder, self).__init__() self._manager = manager or MANAGER + self._apply_transforms = apply_transforms def module_build(self, module, modname=None): - """build an astroid from a living module instance - """ + """Build an astroid from a living module instance.""" node = None path = getattr(module, '__file__', None) if path is not None: - path_, ext = splitext(module.__file__) - if ext in ('.py', '.pyc', '.pyo') and exists(path_ + '.py'): + path_, ext = os.path.splitext(modutils._path_from_filename(path)) + if ext in ('.py', '.pyc', '.pyo') and os.path.exists(path_ + '.py'): node = self.file_build(path_ + '.py', modname) if node is None: # this is a built-in module # get a partial representation by introspection node = self.inspect_build(module, modname=modname, path=path) - # we have to handle transformation by ourselves since the rebuilder - # isn't called for builtin nodes - # - # XXX it's then only called for Module nodes, not for underlying - # nodes - node = self._manager.transform(node) + if self._apply_transforms: + # We have to handle transformation by ourselves since the + # rebuilder isn't called for builtin nodes + node = self._manager.visit_transforms(node) return node def file_build(self, path, modname=None): - """build astroid from a source code file (i.e. from an ast) + """Build astroid from a source code file (i.e. from an ast) - path is expected to be a python source file + *path* is expected to be a python source file """ try: stream, encoding, data = open_source_file(path) except IOError as exc: msg = 'Unable to load file %r (%s)' % (path, exc) - raise AstroidBuildingException(msg) - except SyntaxError as exc: # py3k encoding specification error - raise AstroidBuildingException(exc) - except LookupError as exc: # unknown encoding - raise AstroidBuildingException(exc) + raise exceptions.AstroidBuildingException(msg) + except SyntaxError as exc: # py3k encoding specification error + raise exceptions.AstroidBuildingException(exc) + except LookupError as exc: # unknown encoding + raise exceptions.AstroidBuildingException(exc) with stream: # get module name if necessary if modname is None: try: - modname = '.'.join(modpath_from_file(path)) + modname = '.'.join(modutils.modpath_from_file(path)) except ImportError: - modname = splitext(basename(path))[0] + modname = os.path.splitext(os.path.basename(path))[0] # build astroid representation module = self._data_build(data, modname, path) return self._post_build(module, encoding) def string_build(self, data, modname='', path=None): - """build astroid from source code string and return rebuilded astroid""" + """Build astroid from source code string.""" module = self._data_build(data, modname, path) - module.file_bytes = data.encode('utf-8') + module.source_code = data.encode('utf-8') return self._post_build(module, 'utf-8') def _post_build(self, module, encoding): - """handles encoding and delayed nodes - after a module has been built - """ + """Handles encoding and delayed nodes after a module has been built""" module.file_encoding = encoding self._manager.cache_module(module) # post tree building steps after we stored the module in the cache: - for from_node in module._from_nodes: + for from_node in module._import_from_nodes: if from_node.modname == '__future__': for symbol, _ in from_node.names: - module.future_imports.add(symbol) + module._future_imports.add(symbol) self.add_from_names_to_locals(from_node) # handle delayed assattr nodes for delayed in module._delayed_assattr: self.delayed_assattr(delayed) + + # Visit the transforms + if self._apply_transforms: + module = self._manager.visit_transforms(module) return module def _data_build(self, data, modname, path): - """build tree node from data and add some informations""" - # this method could be wrapped with a pickle/cache function + """Build tree node from data and add some informations""" try: - node = parse(data + '\n') - except TypeError as exc: - raise AstroidBuildingException(exc) + node = _parse(data + '\n') + except (TypeError, ValueError, SyntaxError) as exc: + raise exceptions.AstroidBuildingException(exc) if path is not None: - node_file = abspath(path) + node_file = os.path.abspath(path) else: node_file = '' if modname.endswith('.__init__'): @@ -173,68 +181,83 @@ def _data_build(self, data, modname, path): package = True else: package = path and path.find('__init__.py') > -1 or False - rebuilder = TreeRebuilder(self._manager) - module = rebuilder.visit_module(node, modname, node_file, package) - module._from_nodes = rebuilder._from_nodes - module._delayed_assattr = rebuilder._delayed_assattr + builder = rebuilder.TreeRebuilder(self._manager) + module = builder.visit_module(node, modname, node_file, package) + module._import_from_nodes = builder._import_from_nodes + module._delayed_assattr = builder._delayed_assattr return module def add_from_names_to_locals(self, node): - """store imported names to the locals; - resort the locals if coming from a delayed node - """ + """Store imported names to the locals + Resort the locals if coming from a delayed node + """ _key_func = lambda node: node.fromlineno def sort_locals(my_list): my_list.sort(key=_key_func) + for (name, asname) in node.names: if name == '*': try: imported = node.do_import_module() - except InferenceError: + except exceptions.InferenceError: continue - for name in imported.wildcard_import_names(): + for name in imported._public_names(): node.parent.set_local(name, node) - sort_locals(node.parent.scope().locals[name]) + sort_locals(node.parent.scope()._locals[name]) else: node.parent.set_local(asname or name, node) - sort_locals(node.parent.scope().locals[asname or name]) + sort_locals(node.parent.scope()._locals[asname or name]) def delayed_assattr(self, node): - """visit a AssAttr node -> add name to locals, handle members - definition + """Visit a AssAttr node + + This adds name to locals and handle members definition. """ try: frame = node.frame() - for infered in node.expr.infer(): - if infered is YES: + for inferred in node.expr.infer(): + if inferred is util.YES: continue try: - if infered.__class__ is Instance: - infered = infered._proxied - iattrs = infered.instance_attrs - elif isinstance(infered, Instance): + if inferred.__class__ is bases.Instance: + inferred = inferred._proxied + iattrs = inferred._instance_attrs + elif isinstance(inferred, bases.Instance): # Const, Tuple, ... we may be wrong, may be not, but # anyway we don't want to pollute builtin's namespace continue - elif infered.is_function: - iattrs = infered.instance_attrs + elif inferred.is_function: + iattrs = inferred._instance_attrs else: - iattrs = infered.locals + iattrs = inferred._locals except AttributeError: # XXX log error - #import traceback - #traceback.print_exc() continue values = iattrs.setdefault(node.attrname, []) if node in values: continue # get assign in __init__ first XXX useful ? - if frame.name == '__init__' and values and not \ - values[0].frame().name == '__init__': + if (frame.name == '__init__' and values and + not values[0].frame().name == '__init__'): values.insert(0, node) else: values.append(node) - except InferenceError: + except exceptions.InferenceError: pass + +def parse(code, module_name='', path=None, apply_transforms=True): + """Parses a source string in order to obtain an astroid AST from it + + :param str code: The code for the module. + :param str module_name: The name for the module, if any + :param str path: The path for the module + :param bool apply_transforms: + Apply the transforms for the give code. Use it if you + don't want the default transforms to be applied. + """ + code = textwrap.dedent(code) + builder = AstroidBuilder(manager=MANAGER, + apply_transforms=apply_transforms) + return builder.string_build(code, modname=module_name, path=path) diff --git a/pymode/libs/astroid/context.py b/pymode/libs/astroid/context.py new file mode 100644 index 00000000..284dfa18 --- /dev/null +++ b/pymode/libs/astroid/context.py @@ -0,0 +1,81 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . + +"""Various context related utilities, including inference and call contexts.""" + +import contextlib + + +class InferenceContext(object): + __slots__ = ('path', 'lookupname', 'callcontext', 'boundnode', 'inferred') + + def __init__(self, path=None, inferred=None): + self.path = path or set() + self.lookupname = None + self.callcontext = None + self.boundnode = None + self.inferred = inferred or {} + + def push(self, node): + name = self.lookupname + if (node, name) in self.path: + raise StopIteration() + self.path.add((node, name)) + + def clone(self): + # XXX copy lookupname/callcontext ? + clone = InferenceContext(self.path, inferred=self.inferred) + clone.callcontext = self.callcontext + clone.boundnode = self.boundnode + return clone + + def cache_generator(self, key, generator): + results = [] + for result in generator: + results.append(result) + yield result + + self.inferred[key] = tuple(results) + return + + @contextlib.contextmanager + def restore_path(self): + path = set(self.path) + yield + self.path = path + + +class CallContext(object): + """Holds information for a call site.""" + + __slots__ = ('args', 'keywords') + + def __init__(self, args, keywords=None): + self.args = args + if keywords: + keywords = [(arg.arg, arg.value) for arg in keywords] + else: + keywords = [] + self.keywords = keywords + + +def copy_context(context): + if context is not None: + return context.clone() + else: + return InferenceContext() diff --git a/pymode/libs/astroid/decorators.py b/pymode/libs/astroid/decorators.py new file mode 100644 index 00000000..a446536c --- /dev/null +++ b/pymode/libs/astroid/decorators.py @@ -0,0 +1,75 @@ +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +# +# The code in this file was originally part of logilab-common, licensed under +# the same license. + +""" A few useful function/method decorators.""" + +import wrapt + + +@wrapt.decorator +def cached(func, instance, args, kwargs): + """Simple decorator to cache result of method calls without args.""" + cache = getattr(instance, '__cache', None) + if cache is None: + instance.__cache = cache = {} + try: + return cache[func] + except KeyError: + cache[func] = result = func(*args, **kwargs) + return result + + +class cachedproperty(object): + """ Provides a cached property equivalent to the stacking of + @cached and @property, but more efficient. + + After first usage, the becomes part of the object's + __dict__. Doing: + + del obj. empties the cache. + + Idea taken from the pyramid_ framework and the mercurial_ project. + + .. _pyramid: http://pypi.python.org/pypi/pyramid + .. _mercurial: http://pypi.python.org/pypi/Mercurial + """ + __slots__ = ('wrapped',) + + def __init__(self, wrapped): + try: + wrapped.__name__ + except AttributeError: + raise TypeError('%s must have a __name__ attribute' % + wrapped) + self.wrapped = wrapped + + @property + def __doc__(self): + doc = getattr(self.wrapped, '__doc__', None) + return ('%s' + % ('\n%s' % doc if doc else '')) + + def __get__(self, inst, objtype=None): + if inst is None: + return self + val = self.wrapped(inst) + setattr(inst, self.wrapped.__name__, val) + return val diff --git a/pymode/libs/astroid/exceptions.py b/pymode/libs/astroid/exceptions.py index 3889e2e7..47f2fe50 100644 --- a/pymode/libs/astroid/exceptions.py +++ b/pymode/libs/astroid/exceptions.py @@ -30,6 +30,26 @@ class AstroidBuildingException(AstroidError): class ResolveError(AstroidError): """base class of astroid resolution/inference error""" +class MroError(ResolveError): + """Error raised when there is a problem with method resolution of a class.""" + + +class DuplicateBasesError(MroError): + """Error raised when there are duplicate bases in the same class bases.""" + + +class InconsistentMroError(MroError): + """Error raised when a class's MRO is inconsistent.""" + + +class SuperError(ResolveError): + """Error raised when there is a problem with a super call.""" + + +class SuperArgumentTypeError(SuperError): + """Error raised when the super arguments are invalid.""" + + class NotFoundError(ResolveError): """raised when we are unable to resolve a name""" diff --git a/pymode/libs/astroid/inference.py b/pymode/libs/astroid/inference.py index 22807049..ddd43561 100644 --- a/pymode/libs/astroid/inference.py +++ b/pymode/libs/astroid/inference.py @@ -18,125 +18,32 @@ """this module contains a set of functions to handle inference on astroid trees """ -__doctype__ = "restructuredtext en" - -from itertools import chain +from __future__ import print_function +from astroid import bases +from astroid import context as contextmod +from astroid import exceptions +from astroid import manager from astroid import nodes +from astroid import protocols +from astroid import util -from astroid.manager import AstroidManager -from astroid.exceptions import (AstroidError, InferenceError, NoDefault, - NotFoundError, UnresolvableName) -from astroid.bases import (YES, Instance, InferenceContext, - _infer_stmts, copy_context, path_wrapper, - raise_if_nothing_infered) -from astroid.protocols import ( - _arguments_infer_argname, - BIN_OP_METHOD, UNARY_OP_METHOD) - -MANAGER = AstroidManager() - - -class CallContext(object): - """when inferring a function call, this class is used to remember values - given as argument - """ - def __init__(self, args, starargs, dstarargs): - self.args = [] - self.nargs = {} - for arg in args: - if isinstance(arg, nodes.Keyword): - self.nargs[arg.arg] = arg.value - else: - self.args.append(arg) - self.starargs = starargs - self.dstarargs = dstarargs - def infer_argument(self, funcnode, name, context): - """infer a function argument value according to the call context""" - # 1. search in named keywords - try: - return self.nargs[name].infer(context) - except KeyError: - # Function.args.args can be None in astroid (means that we don't have - # information on argnames) - argindex = funcnode.args.find_argname(name)[0] - if argindex is not None: - # 2. first argument of instance/class method - if argindex == 0 and funcnode.type in ('method', 'classmethod'): - if context.boundnode is not None: - boundnode = context.boundnode - else: - # XXX can do better ? - boundnode = funcnode.parent.frame() - if funcnode.type == 'method': - if not isinstance(boundnode, Instance): - boundnode = Instance(boundnode) - return iter((boundnode,)) - if funcnode.type == 'classmethod': - return iter((boundnode,)) - # if we have a method, extract one position - # from the index, so we'll take in account - # the extra parameter represented by `self` or `cls` - if funcnode.type in ('method', 'classmethod'): - argindex -= 1 - # 2. search arg index - try: - return self.args[argindex].infer(context) - except IndexError: - pass - # 3. search in *args (.starargs) - if self.starargs is not None: - its = [] - for infered in self.starargs.infer(context): - if infered is YES: - its.append((YES,)) - continue - try: - its.append(infered.getitem(argindex, context).infer(context)) - except (InferenceError, AttributeError): - its.append((YES,)) - except (IndexError, TypeError): - continue - if its: - return chain(*its) - # 4. XXX search in **kwargs (.dstarargs) - if self.dstarargs is not None: - its = [] - for infered in self.dstarargs.infer(context): - if infered is YES: - its.append((YES,)) - continue - try: - its.append(infered.getitem(name, context).infer(context)) - except (InferenceError, AttributeError): - its.append((YES,)) - except (IndexError, TypeError): - continue - if its: - return chain(*its) - # 5. */** argument, (Tuple or Dict) - if name == funcnode.args.vararg: - return iter((nodes.const_factory(()))) - if name == funcnode.args.kwarg: - return iter((nodes.const_factory({}))) - # 6. return default value if any - try: - return funcnode.args.default_value(name).infer(context) - except NoDefault: - raise InferenceError(name) +MANAGER = manager.AstroidManager() # .infer method ############################################################### def infer_end(self, context=None): - """inference's end for node such as Module, Class, Function, Const... + """inference's end for node such as Module, ClassDef, FunctionDef, + Const... + """ yield self nodes.Module._infer = infer_end -nodes.Class._infer = infer_end -nodes.Function._infer = infer_end +nodes.ClassDef._infer = infer_end +nodes.FunctionDef._infer = infer_end nodes.Lambda._infer = infer_end nodes.Const._infer = infer_end nodes.List._infer = infer_end @@ -157,7 +64,7 @@ def _higher_function_scope(node): which encloses the given node. """ current = node - while current.parent and not isinstance(current.parent, nodes.Function): + while current.parent and not isinstance(current.parent, nodes.FunctionDef): current = current.parent if current and current.parent: return current.parent @@ -174,72 +81,80 @@ def infer_name(self, context=None): _, stmts = parent_function.lookup(self.name) if not stmts: - raise UnresolvableName(self.name) + raise exceptions.UnresolvableName(self.name) context = context.clone() context.lookupname = self.name - return _infer_stmts(stmts, context, frame) -nodes.Name._infer = path_wrapper(infer_name) -nodes.AssName.infer_lhs = infer_name # won't work with a path wrapper + return bases._infer_stmts(stmts, context, frame) +nodes.Name._infer = bases.path_wrapper(infer_name) +nodes.AssignName.infer_lhs = infer_name # won't work with a path wrapper -def infer_callfunc(self, context=None): - """infer a CallFunc node by trying to guess what the function returns""" +@bases.path_wrapper +@bases.raise_if_nothing_inferred +def infer_call(self, context=None): + """infer a Call node by trying to guess what the function returns""" callcontext = context.clone() - callcontext.callcontext = CallContext(self.args, self.starargs, self.kwargs) + callcontext.callcontext = contextmod.CallContext(args=self.args, + keywords=self.keywords) callcontext.boundnode = None for callee in self.func.infer(context): - if callee is YES: + if callee is util.YES: yield callee continue try: if hasattr(callee, 'infer_call_result'): - for infered in callee.infer_call_result(self, callcontext): - yield infered - except InferenceError: + for inferred in callee.infer_call_result(self, callcontext): + yield inferred + except exceptions.InferenceError: ## XXX log error ? continue -nodes.CallFunc._infer = path_wrapper(raise_if_nothing_infered(infer_callfunc)) +nodes.Call._infer = infer_call +@bases.path_wrapper def infer_import(self, context=None, asname=True): """infer an Import node: return the imported module/object""" name = context.lookupname if name is None: - raise InferenceError() + raise exceptions.InferenceError() if asname: yield self.do_import_module(self.real_name(name)) else: yield self.do_import_module(name) -nodes.Import._infer = path_wrapper(infer_import) +nodes.Import._infer = infer_import + def infer_name_module(self, name): - context = InferenceContext() + context = contextmod.InferenceContext() context.lookupname = name return self.infer(context, asname=False) nodes.Import.infer_name_module = infer_name_module -def infer_from(self, context=None, asname=True): - """infer a From nodes: return the imported module/object""" +@bases.path_wrapper +def infer_import_from(self, context=None, asname=True): + """infer a ImportFrom node: return the imported module/object""" name = context.lookupname if name is None: - raise InferenceError() + raise exceptions.InferenceError() if asname: name = self.real_name(name) module = self.do_import_module() try: - context = copy_context(context) + context = contextmod.copy_context(context) context.lookupname = name - return _infer_stmts(module.getattr(name, ignore_locals=module is self.root()), context) - except NotFoundError: - raise InferenceError(name) -nodes.From._infer = path_wrapper(infer_from) + stmts = module.getattr(name, ignore_locals=module is self.root()) + return bases._infer_stmts(stmts, context) + except exceptions.NotFoundError: + raise exceptions.InferenceError(name) +nodes.ImportFrom._infer = infer_import_from -def infer_getattr(self, context=None): - """infer a Getattr node by using getattr on the associated object""" +@bases.raise_if_nothing_inferred +def infer_attribute(self, context=None): + """infer an Attribute node by using getattr on the associated object""" for owner in self.expr.infer(context): - if owner is YES: + if owner is util.YES: yield owner continue try: @@ -247,58 +162,69 @@ def infer_getattr(self, context=None): for obj in owner.igetattr(self.attrname, context): yield obj context.boundnode = None - except (NotFoundError, InferenceError): + except (exceptions.NotFoundError, exceptions.InferenceError): context.boundnode = None except AttributeError: # XXX method / function context.boundnode = None -nodes.Getattr._infer = path_wrapper(raise_if_nothing_infered(infer_getattr)) -nodes.AssAttr.infer_lhs = raise_if_nothing_infered(infer_getattr) # # won't work with a path wrapper +nodes.Attribute._infer = bases.path_wrapper(infer_attribute) +nodes.AssignAttr.infer_lhs = infer_attribute # # won't work with a path wrapper +@bases.path_wrapper def infer_global(self, context=None): if context.lookupname is None: - raise InferenceError() + raise exceptions.InferenceError() try: - return _infer_stmts(self.root().getattr(context.lookupname), context) - except NotFoundError: - raise InferenceError() -nodes.Global._infer = path_wrapper(infer_global) + return bases._infer_stmts(self.root().getattr(context.lookupname), + context) + except exceptions.NotFoundError: + raise exceptions.InferenceError() +nodes.Global._infer = infer_global +@bases.raise_if_nothing_inferred def infer_subscript(self, context=None): - """infer simple subscription such as [1,2,3][0] or (1,2,3)[-1]""" + """Inference for subscripts + + We're understanding if the index is a Const + or a slice, passing the result of inference + to the value's `getitem` method, which should + handle each supported index type accordingly. + """ + value = next(self.value.infer(context)) - if value is YES: - yield YES + if value is util.YES: + yield util.YES return index = next(self.slice.infer(context)) - if index is YES: - yield YES + if index is util.YES: + yield util.YES return if isinstance(index, nodes.Const): try: assigned = value.getitem(index.value, context) except AttributeError: - raise InferenceError() + raise exceptions.InferenceError() except (IndexError, TypeError): - yield YES + yield util.YES return # Prevent inferring if the infered subscript # is the same as the original subscripted object. - if self is assigned: - yield YES + if self is assigned or assigned is util.YES: + yield util.YES return for infered in assigned.infer(context): yield infered else: - raise InferenceError() -nodes.Subscript._infer = path_wrapper(infer_subscript) -nodes.Subscript.infer_lhs = raise_if_nothing_infered(infer_subscript) + raise exceptions.InferenceError() +nodes.Subscript._infer = bases.path_wrapper(infer_subscript) +nodes.Subscript.infer_lhs = infer_subscript +@bases.raise_if_nothing_inferred def infer_unaryop(self, context=None): for operand in self.operand.infer(context): try: @@ -306,9 +232,9 @@ def infer_unaryop(self, context=None): except TypeError: continue except AttributeError: - meth = UNARY_OP_METHOD[self.op] + meth = protocols.UNARY_OP_METHOD[self.op] if meth is None: - yield YES + yield util.YES else: try: # XXX just suppose if the type implement meth, returned type @@ -318,88 +244,116 @@ def infer_unaryop(self, context=None): except GeneratorExit: raise except: - yield YES -nodes.UnaryOp._infer = path_wrapper(infer_unaryop) + yield util.YES +nodes.UnaryOp._infer = bases.path_wrapper(infer_unaryop) -def _infer_binop(operator, operand1, operand2, context, failures=None): - if operand1 is YES: +def _infer_binop(binop, operand1, operand2, context, failures=None): + if operand1 is util.YES: yield operand1 return try: - for valnode in operand1.infer_binary_op(operator, operand2, context): + for valnode in operand1.infer_binary_op(binop, operand2, context): yield valnode except AttributeError: try: # XXX just suppose if the type implement meth, returned type # will be the same - operand1.getattr(BIN_OP_METHOD[operator]) + operand1.getattr(protocols.BIN_OP_METHOD[operator]) yield operand1 except: if failures is None: - yield YES + yield util.YES else: failures.append(operand1) +@bases.yes_if_nothing_inferred def infer_binop(self, context=None): failures = [] for lhs in self.left.infer(context): - for val in _infer_binop(self.op, lhs, self.right, context, failures): + for val in _infer_binop(self, lhs, self.right, context, failures): yield val for lhs in failures: for rhs in self.right.infer(context): - for val in _infer_binop(self.op, rhs, lhs, context): + for val in _infer_binop(self, rhs, lhs, context): yield val -nodes.BinOp._infer = path_wrapper(infer_binop) +nodes.BinOp._infer = bases.path_wrapper(infer_binop) def infer_arguments(self, context=None): name = context.lookupname if name is None: - raise InferenceError() - return _arguments_infer_argname(self, name, context) + raise exceptions.InferenceError() + return protocols._arguments_infer_argname(self, name, context) nodes.Arguments._infer = infer_arguments -def infer_ass(self, context=None): - """infer a AssName/AssAttr: need to inspect the RHS part of the +@bases.path_wrapper +def infer_assign(self, context=None): + """infer a AssignName/AssignAttr: need to inspect the RHS part of the assign node """ stmt = self.statement() if isinstance(stmt, nodes.AugAssign): return stmt.infer(context) + stmts = list(self.assigned_stmts(context=context)) - return _infer_stmts(stmts, context) -nodes.AssName._infer = path_wrapper(infer_ass) -nodes.AssAttr._infer = path_wrapper(infer_ass) + return bases._infer_stmts(stmts, context) +nodes.AssignName._infer = infer_assign +nodes.AssignAttr._infer = infer_assign def infer_augassign(self, context=None): failures = [] for lhs in self.target.infer_lhs(context): - for val in _infer_binop(self.op, lhs, self.value, context, failures): + for val in _infer_binop(self, lhs, self.value, context, failures): yield val for lhs in failures: for rhs in self.value.infer(context): - for val in _infer_binop(self.op, rhs, lhs, context): + for val in _infer_binop(self, rhs, lhs, context): yield val -nodes.AugAssign._infer = path_wrapper(infer_augassign) +nodes.AugAssign._infer = bases.path_wrapper(infer_augassign) # no infer method on DelName and DelAttr (expected InferenceError) - +@bases.path_wrapper def infer_empty_node(self, context=None): if not self.has_underlying_object(): - yield YES + yield util.YES else: try: - for infered in MANAGER.infer_ast_from_something(self.object, - context=context): - yield infered - except AstroidError: - yield YES -nodes.EmptyNode._infer = path_wrapper(infer_empty_node) + for inferred in MANAGER.infer_ast_from_something(self.object, + context=context): + yield inferred + except exceptions.AstroidError: + yield util.YES +nodes.EmptyNode._infer = infer_empty_node def infer_index(self, context=None): return self.value.infer(context) nodes.Index._infer = infer_index + +# TODO: move directly into bases.Instance when the dependency hell +# will be solved. +def instance_getitem(self, index, context=None): + # Rewrap index to Const for this case + index = nodes.Const(index) + if context: + new_context = context.clone() + else: + context = new_context = contextmod.InferenceContext() + + # Create a new callcontext for providing index as an argument. + new_context.callcontext = contextmod.CallContext(args=[index]) + new_context.boundnode = self + + method = next(self.igetattr('__getitem__', context=context)) + if not isinstance(method, bases.BoundMethod): + raise exceptions.InferenceError + + try: + return next(method.infer_call_result(self, new_context)) + except StopIteration: + raise exceptions.InferenceError + +bases.Instance.getitem = instance_getitem diff --git a/pymode/libs/astroid/inspector.py b/pymode/libs/astroid/inspector.py deleted file mode 100644 index 1fc31926..00000000 --- a/pymode/libs/astroid/inspector.py +++ /dev/null @@ -1,273 +0,0 @@ -# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of astroid. -# -# astroid is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by the -# Free Software Foundation, either version 2.1 of the License, or (at your -# option) any later version. -# -# astroid 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 Lesser General Public License -# for more details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with astroid. If not, see . -"""visitor doing some postprocessing on the astroid tree. -Try to resolve definitions (namespace) dictionary, relationship... - -This module has been imported from pyreverse -""" - -__docformat__ = "restructuredtext en" - -from os.path import dirname - -import astroid -from astroid.exceptions import InferenceError -from astroid.utils import LocalsVisitor -from astroid.modutils import get_module_part, is_relative, is_standard_module - -class IdGeneratorMixIn(object): - """ - Mixin adding the ability to generate integer uid - """ - def __init__(self, start_value=0): - self.id_count = start_value - - def init_counter(self, start_value=0): - """init the id counter - """ - self.id_count = start_value - - def generate_id(self): - """generate a new identifier - """ - self.id_count += 1 - return self.id_count - - -class Linker(IdGeneratorMixIn, LocalsVisitor): - """ - walk on the project tree and resolve relationships. - - According to options the following attributes may be added to visited nodes: - - * uid, - a unique identifier for the node (on astroid.Project, astroid.Module, - astroid.Class and astroid.locals_type). Only if the linker has been instantiated - with tag=True parameter (False by default). - - * Function - a mapping from locals names to their bounded value, which may be a - constant like a string or an integer, or an astroid node (on astroid.Module, - astroid.Class and astroid.Function). - - * instance_attrs_type - as locals_type but for klass member attributes (only on astroid.Class) - - * implements, - list of implemented interface _objects_ (only on astroid.Class nodes) - """ - - def __init__(self, project, inherited_interfaces=0, tag=False): - IdGeneratorMixIn.__init__(self) - LocalsVisitor.__init__(self) - # take inherited interface in consideration or not - self.inherited_interfaces = inherited_interfaces - # tag nodes or not - self.tag = tag - # visited project - self.project = project - - - def visit_project(self, node): - """visit an astroid.Project node - - * optionally tag the node with a unique id - """ - if self.tag: - node.uid = self.generate_id() - for module in node.modules: - self.visit(module) - - def visit_package(self, node): - """visit an astroid.Package node - - * optionally tag the node with a unique id - """ - if self.tag: - node.uid = self.generate_id() - for subelmt in node.values(): - self.visit(subelmt) - - def visit_module(self, node): - """visit an astroid.Module node - - * set the locals_type mapping - * set the depends mapping - * optionally tag the node with a unique id - """ - if hasattr(node, 'locals_type'): - return - node.locals_type = {} - node.depends = [] - if self.tag: - node.uid = self.generate_id() - - def visit_class(self, node): - """visit an astroid.Class node - - * set the locals_type and instance_attrs_type mappings - * set the implements list and build it - * optionally tag the node with a unique id - """ - if hasattr(node, 'locals_type'): - return - node.locals_type = {} - if self.tag: - node.uid = self.generate_id() - # resolve ancestors - for baseobj in node.ancestors(recurs=False): - specializations = getattr(baseobj, 'specializations', []) - specializations.append(node) - baseobj.specializations = specializations - # resolve instance attributes - node.instance_attrs_type = {} - for assattrs in node.instance_attrs.values(): - for assattr in assattrs: - self.handle_assattr_type(assattr, node) - # resolve implemented interface - try: - node.implements = list(node.interfaces(self.inherited_interfaces)) - except InferenceError: - node.implements = () - - def visit_function(self, node): - """visit an astroid.Function node - - * set the locals_type mapping - * optionally tag the node with a unique id - """ - if hasattr(node, 'locals_type'): - return - node.locals_type = {} - if self.tag: - node.uid = self.generate_id() - - link_project = visit_project - link_module = visit_module - link_class = visit_class - link_function = visit_function - - def visit_assname(self, node): - """visit an astroid.AssName node - - handle locals_type - """ - # avoid double parsing done by different Linkers.visit - # running over the same project: - if hasattr(node, '_handled'): - return - node._handled = True - if node.name in node.frame(): - frame = node.frame() - else: - # the name has been defined as 'global' in the frame and belongs - # there. Btw the frame is not yet visited as the name is in the - # root locals; the frame hence has no locals_type attribute - frame = node.root() - try: - values = node.infered() - try: - already_infered = frame.locals_type[node.name] - for valnode in values: - if not valnode in already_infered: - already_infered.append(valnode) - except KeyError: - frame.locals_type[node.name] = values - except astroid.InferenceError: - pass - - def handle_assattr_type(self, node, parent): - """handle an astroid.AssAttr node - - handle instance_attrs_type - """ - try: - values = list(node.infer()) - try: - already_infered = parent.instance_attrs_type[node.attrname] - for valnode in values: - if not valnode in already_infered: - already_infered.append(valnode) - except KeyError: - parent.instance_attrs_type[node.attrname] = values - except astroid.InferenceError: - pass - - def visit_import(self, node): - """visit an astroid.Import node - - resolve module dependencies - """ - context_file = node.root().file - for name in node.names: - relative = is_relative(name[0], context_file) - self._imported_module(node, name[0], relative) - - - def visit_from(self, node): - """visit an astroid.From node - - resolve module dependencies - """ - basename = node.modname - context_file = node.root().file - if context_file is not None: - relative = is_relative(basename, context_file) - else: - relative = False - for name in node.names: - if name[0] == '*': - continue - # analyze dependencies - fullname = '%s.%s' % (basename, name[0]) - if fullname.find('.') > -1: - try: - # XXX: don't use get_module_part, missing package precedence - fullname = get_module_part(fullname, context_file) - except ImportError: - continue - if fullname != basename: - self._imported_module(node, fullname, relative) - - - def compute_module(self, context_name, mod_path): - """return true if the module should be added to dependencies""" - package_dir = dirname(self.project.path) - if context_name == mod_path: - return 0 - elif is_standard_module(mod_path, (package_dir,)): - return 1 - return 0 - - # protected methods ######################################################## - - def _imported_module(self, node, mod_path, relative): - """notify an imported module, used to analyze dependencies - """ - module = node.root() - context_name = module.name - if relative: - mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]), - mod_path) - if self.compute_module(context_name, mod_path): - # handle dependencies - if not hasattr(module, 'depends'): - module.depends = [] - mod_paths = module.depends - if not mod_path in mod_paths: - mod_paths.append(mod_path) diff --git a/pymode/libs/astroid/manager.py b/pymode/libs/astroid/manager.py index b1fb3058..d08adc29 100644 --- a/pymode/libs/astroid/manager.py +++ b/pymode/libs/astroid/manager.py @@ -21,45 +21,23 @@ """ from __future__ import print_function -__docformat__ = "restructuredtext en" - -import collections import imp import os -from os.path import dirname, join, isdir, exists -from warnings import warn import zipimport -from logilab.common.configuration import OptionsProviderMixIn - -from astroid.exceptions import AstroidBuildingException +from astroid import exceptions from astroid import modutils +from astroid import transforms -def astroid_wrapper(func, modname): - """wrapper to give to AstroidManager.project_from_files""" - print('parsing %s...' % modname) - try: - return func(modname) - except AstroidBuildingException as exc: - print(exc) - except Exception as exc: - import traceback - traceback.print_exc() - -def _silent_no_wrap(func, modname): - """silent wrapper that doesn't do anything; can be used for tests""" - return func(modname) - def safe_repr(obj): try: return repr(obj) - except: + except Exception: # pylint: disable=broad-except return '???' - -class AstroidManager(OptionsProviderMixIn): +class AstroidManager(object): """the astroid manager, responsible to build astroid from files or modules. @@ -67,31 +45,27 @@ class AstroidManager(OptionsProviderMixIn): """ name = 'astroid loader' - options = (("ignore", - {'type' : "csv", 'metavar' : "", - 'dest' : "black_list", "default" : ('CVS',), - 'help' : "add (may be a directory) to the black list\ -. It should be a base name, not a path. You may set this option multiple times\ -."}), - ("project", - {'default': "No Name", 'type' : 'string', 'short': 'p', - 'metavar' : '', - 'help' : 'set the project name.'}), - ) brain = {} + def __init__(self): self.__dict__ = AstroidManager.brain if not self.__dict__: - OptionsProviderMixIn.__init__(self) - self.load_defaults() # NOTE: cache entries are added by the [re]builder self.astroid_cache = {} self._mod_file_cache = {} - self.transforms = collections.defaultdict(list) self._failed_import_hooks = [] self.always_load_extensions = False self.optimize_ast = False self.extension_package_whitelist = set() + self._transform = transforms.TransformVisitor() + + # Export these APIs for convenience + self.register_transform = self._transform.register_transform + self.unregister_transform = self._transform.unregister_transform + + def visit_transforms(self, node): + """Visit the transforms and apply them to the given *node*.""" + return self._transform.visit(node) def ast_from_file(self, filepath, modname=None, fallback=True, source=False): """given a module name, return the astroid object""" @@ -105,15 +79,15 @@ def ast_from_file(self, filepath, modname=None, fallback=True, source=False): modname = '.'.join(modutils.modpath_from_file(filepath)) except ImportError: modname = filepath - if modname in self.astroid_cache and self.astroid_cache[modname].file == filepath: + if modname in self.astroid_cache and self.astroid_cache[modname].source_file == filepath: return self.astroid_cache[modname] if source: from astroid.builder import AstroidBuilder return AstroidBuilder(self).file_build(filepath, modname) elif fallback and modname: return self.ast_from_module_name(modname) - raise AstroidBuildingException('unable to get astroid for file %s' % - filepath) + raise exceptions.AstroidBuildingException( + 'unable to get astroid for file %s' % filepath) def _build_stub_module(self, modname): from astroid.builder import AstroidBuilder @@ -137,7 +111,7 @@ def ast_from_module_name(self, modname, context_file=None): return self._build_stub_module(modname) old_cwd = os.getcwd() if context_file: - os.chdir(dirname(context_file)) + os.chdir(os.path.dirname(context_file)) try: filepath, mp_type = self.file_from_module_name(modname, context_file) if mp_type == modutils.PY_ZIPMODULE: @@ -151,18 +125,20 @@ def ast_from_module_name(self, modname, context_file=None): module = modutils.load_module_from_name(modname) except Exception as ex: msg = 'Unable to load module %s (%s)' % (modname, ex) - raise AstroidBuildingException(msg) + raise exceptions.AstroidBuildingException(msg) return self.ast_from_module(module, modname) elif mp_type == imp.PY_COMPILED: - raise AstroidBuildingException("Unable to load compiled module %s" % (modname,)) + msg = "Unable to load compiled module %s" % (modname,) + raise exceptions.AstroidBuildingException(msg) if filepath is None: - raise AstroidBuildingException("Unable to load module %s" % (modname,)) + msg = "Unable to load module %s" % (modname,) + raise exceptions.AstroidBuildingException(msg) return self.ast_from_file(filepath, modname, fallback=False) - except AstroidBuildingException as e: + except exceptions.AstroidBuildingException as e: for hook in self._failed_import_hooks: try: return hook(modname) - except AstroidBuildingException: + except exceptions.AstroidBuildingException: pass raise e finally: @@ -186,11 +162,12 @@ def zip_import_data(self, filepath): module = builder.string_build(importer.get_source(resource), zmodname, filepath) return module - except: + except Exception: # pylint: disable=broad-except continue return None def file_from_module_name(self, modname, contextfile): + # pylint: disable=redefined-variable-type try: value = self._mod_file_cache[(modname, contextfile)] except KeyError: @@ -199,9 +176,9 @@ def file_from_module_name(self, modname, contextfile): modname.split('.'), context_file=contextfile) except ImportError as ex: msg = 'Unable to load module %s (%s)' % (modname, ex) - value = AstroidBuildingException(msg) + value = exceptions.AstroidBuildingException(msg) self._mod_file_cache[(modname, contextfile)] = value - if isinstance(value, AstroidBuildingException): + if isinstance(value, exceptions.AstroidBuildingException): raise value return value @@ -226,12 +203,11 @@ def ast_from_class(self, klass, modname=None): try: modname = klass.__module__ except AttributeError: - raise AstroidBuildingException( - 'Unable to get module for class %s' % safe_repr(klass)) + msg = 'Unable to get module for class %s' % safe_repr(klass) + raise exceptions.AstroidBuildingException(msg) modastroid = self.ast_from_module_name(modname) return modastroid.getattr(klass.__name__)[0] # XXX - def infer_ast_from_something(self, obj, context=None): """infer astroid for the given class""" if hasattr(obj, '__class__') and not isinstance(obj, type): @@ -241,75 +217,29 @@ def infer_ast_from_something(self, obj, context=None): try: modname = klass.__module__ except AttributeError: - raise AstroidBuildingException( - 'Unable to get module for %s' % safe_repr(klass)) + msg = 'Unable to get module for %s' % safe_repr(klass) + raise exceptions.AstroidBuildingException(msg) except Exception as ex: - raise AstroidBuildingException( - 'Unexpected error while retrieving module for %s: %s' - % (safe_repr(klass), ex)) + msg = ('Unexpected error while retrieving module for %s: %s' + % (safe_repr(klass), ex)) + raise exceptions.AstroidBuildingException(msg) try: name = klass.__name__ except AttributeError: - raise AstroidBuildingException( - 'Unable to get name for %s' % safe_repr(klass)) + msg = 'Unable to get name for %s' % safe_repr(klass) + raise exceptions.AstroidBuildingException(msg) except Exception as ex: - raise AstroidBuildingException( - 'Unexpected error while retrieving name for %s: %s' - % (safe_repr(klass), ex)) + exc = ('Unexpected error while retrieving name for %s: %s' + % (safe_repr(klass), ex)) + raise exceptions.AstroidBuildingException(exc) # take care, on living object __module__ is regularly wrong :( modastroid = self.ast_from_module_name(modname) if klass is obj: - for infered in modastroid.igetattr(name, context): - yield infered + for inferred in modastroid.igetattr(name, context): + yield inferred else: - for infered in modastroid.igetattr(name, context): - yield infered.instanciate_class() - - def project_from_files(self, files, func_wrapper=astroid_wrapper, - project_name=None, black_list=None): - """return a Project from a list of files or modules""" - # build the project representation - project_name = project_name or self.config.project - black_list = black_list or self.config.black_list - project = Project(project_name) - for something in files: - if not exists(something): - fpath = modutils.file_from_modpath(something.split('.')) - elif isdir(something): - fpath = join(something, '__init__.py') - else: - fpath = something - astroid = func_wrapper(self.ast_from_file, fpath) - if astroid is None: - continue - # XXX why is first file defining the project.path ? - project.path = project.path or astroid.file - project.add_module(astroid) - base_name = astroid.name - # recurse in package except if __init__ was explicitly given - if astroid.package and something.find('__init__') == -1: - # recurse on others packages / modules if this is a package - for fpath in modutils.get_module_files(dirname(astroid.file), - black_list): - astroid = func_wrapper(self.ast_from_file, fpath) - if astroid is None or astroid.name == base_name: - continue - project.add_module(astroid) - return project - - def register_transform(self, node_class, transform, predicate=None): - """Register `transform(node)` function to be applied on the given - Astroid's `node_class` if `predicate` is None or returns true - when called with the node as argument. - - The transform function may return a value which is then used to - substitute the original node in the tree. - """ - self.transforms[node_class].append((transform, predicate)) - - def unregister_transform(self, node_class, transform, predicate=None): - """Unregister the given transform.""" - self.transforms[node_class].remove((transform, predicate)) + for inferred in modastroid.igetattr(name, context): + yield inferred.instantiate_class() def register_failed_import_hook(self, hook): """Registers a hook to resolve imports that cannot be found otherwise. @@ -321,30 +251,6 @@ def register_failed_import_hook(self, hook): """ self._failed_import_hooks.append(hook) - def transform(self, node): - """Call matching transforms for the given node if any and return the - transformed node. - """ - cls = node.__class__ - if cls not in self.transforms: - # no transform registered for this class of node - return node - - transforms = self.transforms[cls] - orig_node = node # copy the reference - for transform_func, predicate in transforms: - if predicate is None or predicate(node): - ret = transform_func(node) - # if the transformation function returns something, it's - # expected to be a replacement for the node - if ret is not None: - if node is not orig_node: - # node has already be modified by some previous - # transformation, warn about it - warn('node %s substituted multiple times' % node) - node = ret - return node - def cache_module(self, module): """Cache a module if no module with the same name is known yet.""" self.astroid_cache.setdefault(module.name, module) @@ -359,33 +265,3 @@ def clear_cache(self, astroid_builtin=None): import astroid.raw_building astroid.raw_building._astroid_bootstrapping( astroid_builtin=astroid_builtin) - - -class Project(object): - """a project handle a set of modules / packages""" - def __init__(self, name=''): - self.name = name - self.path = None - self.modules = [] - self.locals = {} - self.__getitem__ = self.locals.__getitem__ - self.__iter__ = self.locals.__iter__ - self.values = self.locals.values - self.keys = self.locals.keys - self.items = self.locals.items - - def add_module(self, node): - self.locals[node.name] = node - self.modules.append(node) - - def get_module(self, name): - return self.locals[name] - - def get_children(self): - return self.modules - - def __repr__(self): - return '' % (self.name, id(self), - len(self.modules)) - - diff --git a/pymode/libs/astroid/mixins.py b/pymode/libs/astroid/mixins.py index dbf1673a..57082f0f 100644 --- a/pymode/libs/astroid/mixins.py +++ b/pymode/libs/astroid/mixins.py @@ -18,16 +18,16 @@ """This module contains some mixins for the different nodes. """ -from logilab.common.decorators import cachedproperty +import warnings -from astroid.exceptions import (AstroidBuildingException, InferenceError, - NotFoundError) +from astroid import decorators +from astroid import exceptions class BlockRangeMixIn(object): """override block range """ - @cachedproperty + @decorators.cachedproperty def blockstart_tolineno(self): return self.lineno @@ -55,15 +55,29 @@ def _get_filtered_stmts(self, _, node, _stmts, mystmt): return [node], True return _stmts, False - def ass_type(self): + def assign_type(self): return self + def ass_type(self): + warnings.warn('%s.ass_type() is deprecated and slated for removal ' + 'in astroid 2.0, use %s.assign_type() instead.' + % (type(self).__name__, type(self).__name__), + PendingDeprecationWarning, stacklevel=2) + return self.assign_type() + class AssignTypeMixin(object): - def ass_type(self): + def assign_type(self): return self + def ass_type(self): + warnings.warn('%s.ass_type() is deprecated and slated for removal ' + 'in astroid 2.0, use %s.assign_type() instead.' + % (type(self).__name__, type(self).__name__), + PendingDeprecationWarning, stacklevel=2) + return self.assign_type() + def _get_filtered_stmts(self, lookup_node, node, _stmts, mystmt): """method used in filter_stmts""" if self is mystmt: @@ -77,11 +91,18 @@ def _get_filtered_stmts(self, lookup_node, node, _stmts, mystmt): class ParentAssignTypeMixin(AssignTypeMixin): + def assign_type(self): + return self.parent.assign_type() + def ass_type(self): - return self.parent.ass_type() + warnings.warn('%s.ass_type() is deprecated and slated for removal ' + 'in astroid 2.0, use %s.assign_type() instead.' + % (type(self).__name__, type(self).__name__), + PendingDeprecationWarning, stacklevel=2) + return self.assign_type() -class FromImportMixIn(FilterStmtsMixin): +class ImportFromMixin(FilterStmtsMixin): """MixIn for From and Import Nodes""" def _infer_name(self, frame, name): @@ -104,11 +125,14 @@ def do_import_module(self, modname=None): # FIXME: we used to raise InferenceError here, but why ? return mymodule try: - return mymodule.import_module(modname, level=level) - except AstroidBuildingException: - raise InferenceError(modname) + return mymodule.import_module(modname, level=level, + relative_only=level and level >= 1) + except exceptions.AstroidBuildingException as ex: + if isinstance(ex.args[0], SyntaxError): + raise exceptions.InferenceError(str(ex)) + raise exceptions.InferenceError(modname) except SyntaxError as ex: - raise InferenceError(str(ex)) + raise exceptions.InferenceError(str(ex)) def real_name(self, asname): """get name from 'as' name""" @@ -120,5 +144,4 @@ def real_name(self, asname): _asname = name if asname == _asname: return name - raise NotFoundError(asname) - + raise exceptions.NotFoundError(asname) diff --git a/pymode/libs/astroid/modutils.py b/pymode/libs/astroid/modutils.py index c547f3e6..45c96f7e 100644 --- a/pymode/libs/astroid/modutils.py +++ b/pymode/libs/astroid/modutils.py @@ -28,10 +28,9 @@ """ from __future__ import with_statement -__docformat__ = "restructuredtext en" - import imp import os +import platform import sys from distutils.sysconfig import get_python_lib from distutils.errors import DistutilsPlatformError @@ -42,8 +41,6 @@ except ImportError: pkg_resources = None -from logilab.common import _handle_blacklist - PY_ZIPMODULE = object() if sys.platform.startswith('win'): @@ -53,12 +50,7 @@ PY_SOURCE_EXTS = ('py',) PY_COMPILED_EXTS = ('so',) -# Notes about STD_LIB_DIRS -# Consider arch-specific installation for STD_LIB_DIRS definition -# :mod:`distutils.sysconfig` contains to much hardcoded values to rely on -# -# :see: `Problems with /usr/lib64 builds `_ -# :see: `FHS `_ + try: # The explicit sys.prefix is to work around a patch in virtualenv that # replaces the 'real' sys.prefix (i.e. the location of the binary) @@ -70,22 +62,53 @@ # Take care of installations where exec_prefix != prefix. get_python_lib(standard_lib=True, prefix=sys.exec_prefix), get_python_lib(standard_lib=True)]) - if os.name == 'nt': - STD_LIB_DIRS.add(os.path.join(sys.prefix, 'dlls')) - try: - # real_prefix is defined when running inside virtualenv. - STD_LIB_DIRS.add(os.path.join(sys.real_prefix, 'dlls')) - except AttributeError: - pass # get_python_lib(standard_lib=1) is not available on pypy, set STD_LIB_DIR to # non-valid path, see https://bugs.pypy.org/issue1164 except DistutilsPlatformError: STD_LIB_DIRS = set() -EXT_LIB_DIR = get_python_lib() +if os.name == 'nt': + STD_LIB_DIRS.add(os.path.join(sys.prefix, 'dlls')) + try: + # real_prefix is defined when running inside virtualenv. + STD_LIB_DIRS.add(os.path.join(sys.real_prefix, 'dlls')) + except AttributeError: + pass +if platform.python_implementation() == 'PyPy': + _root = os.path.join(sys.prefix, 'lib_pypy') + STD_LIB_DIRS.add(_root) + try: + # real_prefix is defined when running inside virtualenv. + STD_LIB_DIRS.add(os.path.join(sys.real_prefix, 'lib_pypy')) + except AttributeError: + pass + del _root +if os.name == 'posix': + # Need the real prefix is we're under a virtualenv, otherwise + # the usual one will do. + try: + prefix = sys.real_prefix + except AttributeError: + prefix = sys.prefix + + def _posix_path(path): + base_python = 'python%d.%d' % sys.version_info[:2] + return os.path.join(prefix, path, base_python) + + STD_LIB_DIRS.add(_posix_path('lib')) + if sys.maxsize > 2**32: + # This tries to fix a problem with /usr/lib64 builds, + # where systems are running both 32-bit and 64-bit code + # on the same machine, which reflects into the places where + # standard library could be found. More details can be found + # here http://bugs.python.org/issue1294959. + # An easy reproducing case would be + # https://github.com/PyCQA/pylint/issues/712#issuecomment-163178753 + STD_LIB_DIRS.add(_posix_path('lib64')) -BUILTIN_MODULES = dict(zip(sys.builtin_module_names, - [1]*len(sys.builtin_module_names))) +EXT_LIB_DIR = get_python_lib() +IS_JYTHON = platform.python_implementation() == 'Jython' +BUILTIN_MODULES = dict.fromkeys(sys.builtin_module_names, True) class NoSourceFile(Exception): @@ -97,6 +120,32 @@ def _normalize_path(path): return os.path.normcase(os.path.abspath(path)) +def _path_from_filename(filename, is_jython=IS_JYTHON): + if not is_jython: + if sys.version_info > (3, 0): + return filename + else: + if filename.endswith(".pyc"): + return filename[:-1] + return filename + head, has_pyclass, _ = filename.partition("$py.class") + if has_pyclass: + return head + ".py" + return filename + + +def _handle_blacklist(blacklist, dirnames, filenames): + """remove files/directories in the black list + + dirnames/filenames are usually from os.walk + """ + for norecurs in blacklist: + if norecurs in dirnames: + dirnames.remove(norecurs) + elif norecurs in filenames: + filenames.remove(norecurs) + + _NORM_PATH_CACHE = {} def _cache_normalize_path(path): @@ -112,7 +161,7 @@ def _cache_normalize_path(path): result = _NORM_PATH_CACHE[path] = _normalize_path(path) return result -def load_module_from_name(dotted_name, path=None, use_sys=1): +def load_module_from_name(dotted_name, path=None, use_sys=True): """Load a Python module from its name. :type dotted_name: str @@ -184,14 +233,16 @@ def load_module_from_modpath(parts, path=None, use_sys=1): if prevmodule: setattr(prevmodule, part, module) _file = getattr(module, '__file__', '') + prevmodule = module + if not _file and _is_namespace(curname): + continue if not _file and len(modpath) != len(parts): raise ImportError('no module in %s' % '.'.join(parts[len(modpath):])) path = [os.path.dirname(_file)] - prevmodule = module return module -def load_module_from_file(filepath, path=None, use_sys=1, extrapath=None): +def load_module_from_file(filepath, path=None, use_sys=True, extrapath=None): """Load a Python module from it's path. :type filepath: str @@ -219,9 +270,11 @@ def load_module_from_file(filepath, path=None, use_sys=1, extrapath=None): def _check_init(path, mod_path): """check there are some __init__.py all along the way""" + modpath = [] for part in mod_path: + modpath.append(part) path = os.path.join(path, part) - if not _has_init(path): + if not _is_namespace('.'.join(modpath)) and not _has_init(path): return False return True @@ -246,7 +299,9 @@ def modpath_from_file(filename, extrapath=None): :rtype: list(str) :return: the corresponding splitted module's name """ - base = os.path.splitext(os.path.abspath(filename))[0] + filename = _path_from_filename(filename) + filename = os.path.abspath(filename) + base = os.path.splitext(filename)[0] if extrapath is not None: for path_ in extrapath: path = os.path.abspath(path_) @@ -317,8 +372,8 @@ def file_info_from_modpath(modpath, path=None, context_file=None): def get_module_part(dotted_name, context_file=None): """given a dotted name return the module part of the name : - >>> get_module_part('logilab.common.modutils.get_module_part') - 'logilab.common.modutils' + >>> get_module_part('astroid.as_string.dump') + 'astroid.as_string' :type dotted_name: str :param dotted_name: full name of the identifier we are interested in @@ -382,9 +437,8 @@ def get_module_files(src_directory, blacklist): path of the directory corresponding to the package :type blacklist: list or tuple - :param blacklist: - optional list of files or directory to ignore, default to the value of - `logilab.common.STD_BLACKLIST` + :param blacklist: iterable + list of files or directories to ignore. :rtype: list :return: @@ -419,7 +473,8 @@ def get_source_file(filename, include_no_ext=False): :rtype: str :return: the absolute path of the source file if it exists """ - base, orig_ext = os.path.splitext(os.path.abspath(filename)) + filename = os.path.abspath(_path_from_filename(filename)) + base, orig_ext = os.path.splitext(filename) for ext in PY_SOURCE_EXTS: source_path = '%s.%s' % (base, ext) if os.path.exists(source_path): @@ -464,7 +519,8 @@ def is_standard_module(modname, std_path=None): # modules which are not living in a file are considered standard # (sys and __builtin__ for instance) if filename is None: - return True + # we assume there are no namespaces in stdlib + return not _is_namespace(modname) filename = _normalize_path(filename) if filename.startswith(_cache_normalize_path(EXT_LIB_DIR)): return False @@ -538,15 +594,27 @@ def _file_from_modpath(modpath, path=None, context=None): return mp_filename, mtype def _search_zip(modpath, pic): - for filepath, importer in pic.items(): + for filepath, importer in list(pic.items()): if importer is not None: if importer.find_module(modpath[0]): if not importer.find_module(os.path.sep.join(modpath)): raise ImportError('No module named %s in %s/%s' % ( '.'.join(modpath[1:]), filepath, modpath)) - return PY_ZIPMODULE, os.path.abspath(filepath) + os.path.sep + os.path.sep.join(modpath), filepath + return (PY_ZIPMODULE, + os.path.abspath(filepath) + os.path.sep + os.path.sep.join(modpath), + filepath) raise ImportError('No module named %s' % '.'.join(modpath)) +try: + import pkg_resources +except ImportError: + pkg_resources = None + + +def _is_namespace(modname): + return (pkg_resources is not None + and modname in pkg_resources._namespace_packages) + def _module_file(modpath, path=None): """get a module type / file path @@ -579,14 +647,13 @@ def _module_file(modpath, path=None): except AttributeError: checkeggs = False # pkg_resources support (aka setuptools namespace packages) - if (pkg_resources is not None - and modpath[0] in pkg_resources._namespace_packages - and modpath[0] in sys.modules - and len(modpath) > 1): + if _is_namespace(modpath[0]) and modpath[0] in sys.modules: # setuptools has added into sys.modules a module object with proper # __path__, get back information from there module = sys.modules[modpath.pop(0)] path = module.__path__ + if not modpath: + return imp.C_BUILTIN, None imported = [] while modpath: modname = modpath[0] @@ -609,7 +676,7 @@ def _module_file(modpath, path=None): # Don't forget to close the stream to avoid # spurious ResourceWarnings. if stream: - stream.close() + stream.close() if checkeggs and mp_filename: fullabspath = [_cache_normalize_path(x) for x in _path] @@ -639,7 +706,11 @@ def _module_file(modpath, path=None): except IOError: path = [mp_filename] else: - if b'pkgutil' in data and b'extend_path' in data: + extend_path = b'pkgutil' in data and b'extend_path' in data + declare_namespace = ( + b"pkg_resources" in data + and b"declare_namespace(__name__)" in data) + if extend_path or declare_namespace: # extend_path is called, search sys.path for module/packages # of this name see pkgutil.extend_path documentation path = [os.path.join(p, *imported) for p in sys.path diff --git a/pymode/libs/astroid/node_classes.py b/pymode/libs/astroid/node_classes.py index 4b413ef8..ca773c3a 100644 --- a/pymode/libs/astroid/node_classes.py +++ b/pymode/libs/astroid/node_classes.py @@ -18,40 +18,47 @@ """Module for some node classes. More nodes in scoped_nodes.py """ -import sys +import abc +import warnings +import lazy_object_proxy import six -from logilab.common.decorators import cachedproperty -from astroid.exceptions import NoDefault -from astroid.bases import (NodeNG, Statement, Instance, InferenceContext, - _infer_stmts, YES, BUILTINS) -from astroid.mixins import (BlockRangeMixIn, AssignTypeMixin, - ParentAssignTypeMixin, FromImportMixIn) +from astroid import bases +from astroid import context as contextmod +from astroid import decorators +from astroid import exceptions +from astroid import mixins +from astroid import util -PY3K = sys.version_info >= (3, 0) +BUILTINS = six.moves.builtins.__name__ + +@bases.raise_if_nothing_inferred def unpack_infer(stmt, context=None): """recursively generate nodes inferred by the given statement. If the inferred value is a list or a tuple, recurse on the elements """ if isinstance(stmt, (List, Tuple)): for elt in stmt.elts: - for infered_elt in unpack_infer(elt, context): - yield infered_elt + if elt is util.YES: + yield elt + continue + for inferred_elt in unpack_infer(elt, context): + yield inferred_elt return - # if infered is a final node, return it and stop - infered = next(stmt.infer(context)) - if infered is stmt: - yield infered + # if inferred is a final node, return it and stop + inferred = next(stmt.infer(context)) + if inferred is stmt: + yield inferred return # else, infer recursivly, except YES object that should be returned as is - for infered in stmt.infer(context): - if infered is YES: - yield infered + for inferred in stmt.infer(context): + if inferred is util.YES: + yield inferred else: - for inf_inf in unpack_infer(infered, context): + for inf_inf in unpack_infer(inferred, context): yield inf_inf @@ -93,7 +100,9 @@ def are_exclusive(stmt1, stmt2, exceptions=None): c2attr, c2node = node.locate_child(previous) c1attr, c1node = node.locate_child(children[node]) if c1node is not c2node: - if ((c2attr == 'body' and c1attr == 'handlers' and children[node].catch(exceptions)) or + if ((c2attr == 'body' + and c1attr == 'handlers' + and children[node].catch(exceptions)) or (c2attr == 'handlers' and c1attr == 'body' and previous.catch(exceptions)) or (c2attr == 'handlers' and c1attr == 'orelse') or (c2attr == 'orelse' and c1attr == 'handlers')): @@ -106,6 +115,31 @@ def are_exclusive(stmt1, stmt2, exceptions=None): return False +@six.add_metaclass(abc.ABCMeta) +class _BaseContainer(mixins.ParentAssignTypeMixin, + bases.NodeNG, + bases.Instance): + """Base class for Set, FrozenSet, Tuple and List.""" + + _astroid_fields = ('elts',) + + def __init__(self, elts=None): + if elts is None: + self.elts = [] + else: + self.elts = [const_factory(e) for e in elts] + + def itered(self): + return self.elts + + def bool_value(self): + return bool(self.elts) + + @abc.abstractmethod + def pytype(self): + pass + + class LookupMixIn(object): """Mixin looking up a name in the right scope """ @@ -124,14 +158,14 @@ def lookup(self, name): return self.scope().scope_lookup(self, name) def ilookup(self, name): - """infered lookup + """inferred lookup - return an iterator on infered values of the statements returned by + return an iterator on inferred values of the statements returned by the lookup method """ frame, stmts = self.lookup(name) - context = InferenceContext() - return _infer_stmts(stmts, context, frame) + context = contextmod.InferenceContext() + return bases._infer_stmts(stmts, context, frame) def _filter_stmts(self, stmts, frame, offset): """filter statements to remove ignorable statements. @@ -163,8 +197,7 @@ def _filter_stmts(self, stmts, frame, offset): if self.statement() is myframe and myframe.parent: myframe = myframe.parent.frame() - if not myframe is frame or self is frame: - return stmts + mystmt = self.statement() # line filtering if we are in the same frame # @@ -183,19 +216,18 @@ def _filter_stmts(self, stmts, frame, offset): # line filtering is on and we have reached our location, break if mylineno > 0 and stmt.fromlineno > mylineno: break - assert hasattr(node, 'ass_type'), (node, node.scope(), - node.scope().locals) - ass_type = node.ass_type() - + assert hasattr(node, 'assign_type'), (node, node.scope(), + node.scope().locals) + assign_type = node.assign_type() if node.has_base(self): break - _stmts, done = ass_type._get_filtered_stmts(self, node, _stmts, mystmt) + _stmts, done = assign_type._get_filtered_stmts(self, node, _stmts, mystmt) if done: break - optional_assign = ass_type.optional_assign - if optional_assign and ass_type.parent_of(self): + optional_assign = assign_type.optional_assign + if optional_assign and assign_type.parent_of(self): # we are inside a loop, loop var assigment is hidding previous # assigment _stmts = [node] @@ -210,7 +242,7 @@ def _filter_stmts(self, stmts, frame, offset): else: # we got a parent index, this means the currently visited node # is at the same block level as a previously visited node - if _stmts[pindex].ass_type().parent_of(ass_type): + if _stmts[pindex].assign_type().parent_of(assign_type): # both statements are not at the same block level continue # if currently visited node is following previously considered @@ -239,7 +271,7 @@ def _filter_stmts(self, stmts, frame, offset): if not (optional_assign or are_exclusive(_stmts[pindex], node)): del _stmt_parents[pindex] del _stmts[pindex] - if isinstance(node, AssName): + if isinstance(node, AssignName): if not optional_assign and stmt.parent is mystmt.parent: _stmts = [] _stmt_parents = [] @@ -252,27 +284,24 @@ def _filter_stmts(self, stmts, frame, offset): _stmt_parents.append(stmt.parent) return _stmts + # Name classes -class AssName(LookupMixIn, ParentAssignTypeMixin, NodeNG): +class AssignName(LookupMixIn, mixins.ParentAssignTypeMixin, bases.NodeNG): """class representing an AssName node""" -class DelName(LookupMixIn, ParentAssignTypeMixin, NodeNG): +class DelName(LookupMixIn, mixins.ParentAssignTypeMixin, bases.NodeNG): """class representing a DelName node""" -class Name(LookupMixIn, NodeNG): +class Name(LookupMixIn, bases.NodeNG): """class representing a Name node""" - - -##################### node classes ######################################## - -class Arguments(NodeNG, AssignTypeMixin): +class Arguments(mixins.AssignTypeMixin, bases.NodeNG): """class representing an Arguments node""" - if PY3K: + if six.PY3: # Python 3.4+ uses a different approach regarding annotations, # each argument is a new class, _ast.arg, which exposes an # 'annotation' attribute. In astroid though, arguments are exposed @@ -306,7 +335,7 @@ def _infer_name(self, frame, name): return name return None - @cachedproperty + @decorators.cachedproperty def fromlineno(self): lineno = super(Arguments, self).fromlineno return max(lineno, self.parent.fromlineno or 0) @@ -315,15 +344,18 @@ def format_args(self): """return arguments formatted as string""" result = [] if self.args: - result.append(_format_args(self.args, self.defaults)) + result.append( + _format_args(self.args, self.defaults, + getattr(self, 'annotations', None)) + ) if self.vararg: result.append('*%s' % self.vararg) - if self.kwarg: - result.append('**%s' % self.kwarg) if self.kwonlyargs: if not self.vararg: result.append('*') result.append(_format_args(self.kwonlyargs, self.kw_defaults)) + if self.kwarg: + result.append('**%s' % self.kwarg) return ', '.join(result) def default_value(self, argname): @@ -339,7 +371,7 @@ def default_value(self, argname): i = _find_arg(argname, self.kwonlyargs)[0] if i is not None and self.kw_defaults[i] is not None: return self.kw_defaults[i] - raise NoDefault() + raise exceptions.NoDefault() def is_argument(self, name): """return True if the name is defined in arguments""" @@ -374,79 +406,91 @@ def _find_arg(argname, args, rec=False): return None, None -def _format_args(args, defaults=None): +def _format_args(args, defaults=None, annotations=None): values = [] if args is None: return '' + if annotations is None: + annotations = [] if defaults is not None: default_offset = len(args) - len(defaults) - for i, arg in enumerate(args): + packed = six.moves.zip_longest(args, annotations) + for i, (arg, annotation) in enumerate(packed): if isinstance(arg, Tuple): values.append('(%s)' % _format_args(arg.elts)) else: - values.append(arg.name) + argname = arg.name + if annotation is not None: + argname += ':' + annotation.as_string() + values.append(argname) + if defaults is not None and i >= default_offset: if defaults[i-default_offset] is not None: values[-1] += '=' + defaults[i-default_offset].as_string() return ', '.join(values) -class AssAttr(NodeNG, ParentAssignTypeMixin): - """class representing an AssAttr node""" +class AssignAttr(mixins.ParentAssignTypeMixin, bases.NodeNG): + """class representing an AssignAttr node""" _astroid_fields = ('expr',) expr = None -class Assert(Statement): +class Assert(bases.Statement): """class representing an Assert node""" _astroid_fields = ('test', 'fail',) test = None fail = None -class Assign(Statement, AssignTypeMixin): +class Assign(bases.Statement, mixins.AssignTypeMixin): """class representing an Assign node""" _astroid_fields = ('targets', 'value',) targets = None value = None -class AugAssign(Statement, AssignTypeMixin): +class AugAssign(bases.Statement, mixins.AssignTypeMixin): """class representing an AugAssign node""" _astroid_fields = ('target', 'value',) target = None value = None -class Backquote(NodeNG): +class Repr(bases.NodeNG): """class representing a Backquote node""" _astroid_fields = ('value',) value = None -class BinOp(NodeNG): +class BinOp(bases.NodeNG): """class representing a BinOp node""" _astroid_fields = ('left', 'right',) left = None right = None -class BoolOp(NodeNG): +class BoolOp(bases.NodeNG): """class representing a BoolOp node""" _astroid_fields = ('values',) values = None -class Break(Statement): +class Break(bases.Statement): """class representing a Break node""" -class CallFunc(NodeNG): - """class representing a CallFunc node""" - _astroid_fields = ('func', 'args', 'starargs', 'kwargs') +class Call(bases.NodeNG): + """class representing a Call node""" + _astroid_fields = ('func', 'args', 'keywords') func = None args = None - starargs = None - kwargs = None + keywords = None + + @property + def starargs(self): + args = self.args or [] + return [arg for arg in args if isinstance(arg, Starred)] - def __init__(self): - self.starargs = None - self.kwargs = None + @property + def kwargs(self): + keywords = self.keywords or [] + return [keyword for keyword in keywords if keyword.arg is None] -class Compare(NodeNG): +class Compare(bases.NodeNG): """class representing a Compare node""" _astroid_fields = ('left', 'ops',) left = None @@ -464,7 +508,8 @@ def last_child(self): return self.ops[-1][1] #return self.left -class Comprehension(NodeNG): + +class Comprehension(bases.NodeNG): """class representing a Comprehension node""" _astroid_fields = ('target', 'iter', 'ifs') target = None @@ -472,9 +517,16 @@ class Comprehension(NodeNG): ifs = None optional_assign = True - def ass_type(self): + def assign_type(self): return self + def ass_type(self): + warnings.warn('%s.ass_type() is deprecated and slated for removal' + 'in astroid 2.0, use %s.assign_type() instead.' + % (type(self).__name__, type(self).__name__), + PendingDeprecationWarning, stacklevel=2) + return self.assign_type() + def _get_filtered_stmts(self, lookup_node, node, stmts, mystmt): """method used in filter_stmts""" if self is mystmt: @@ -490,7 +542,7 @@ def _get_filtered_stmts(self, lookup_node, node, stmts, mystmt): return stmts, False -class Const(NodeNG, Instance): +class Const(bases.NodeNG, bases.Instance): """represent a constant node like num, str, bool, None, bytes""" def __init__(self, value=None): @@ -499,6 +551,11 @@ def __init__(self, value=None): def getitem(self, index, context=None): if isinstance(self.value, six.string_types): return Const(self.value[index]) + if isinstance(self.value, bytes) and six.PY3: + # Bytes aren't instances of six.string_types + # on Python 3. Also, indexing them should return + # integers. + return Const(self.value[index]) raise TypeError('%r (value=%s)' % (self, self.value)) def has_dynamic_getattr(self): @@ -513,11 +570,11 @@ def pytype(self): return self._proxied.qname() -class Continue(Statement): +class Continue(bases.Statement): """class representing a Continue node""" -class Decorators(NodeNG): +class Decorators(bases.NodeNG): """class representing a Decorators node""" _astroid_fields = ('nodes',) nodes = None @@ -529,19 +586,21 @@ def scope(self): # skip the function node to go directly to the upper level scope return self.parent.parent.scope() -class DelAttr(NodeNG, ParentAssignTypeMixin): + +class DelAttr(mixins.ParentAssignTypeMixin, bases.NodeNG): """class representing a DelAttr node""" _astroid_fields = ('expr',) expr = None -class Delete(Statement, AssignTypeMixin): + +class Delete(mixins.AssignTypeMixin, bases.Statement): """class representing a Delete node""" _astroid_fields = ('targets',) targets = None -class Dict(NodeNG, Instance): +class Dict(bases.NodeNG, bases.Instance): """class representing a Dict node""" _astroid_fields = ('items',) @@ -550,7 +609,7 @@ def __init__(self, items=None): self.items = [] else: self.items = [(const_factory(k), const_factory(v)) - for k, v in items.items()] + for k, v in list(items.items())] def pytype(self): return '%s.dict' % BUILTINS @@ -573,39 +632,45 @@ def itered(self): def getitem(self, lookup_key, context=None): for key, value in self.items: - for inferedkey in key.infer(context): - if inferedkey is YES: + # TODO(cpopa): no support for overriding yet, {1:2, **{1: 3}}. + if isinstance(key, DictUnpack): + try: + return value.getitem(lookup_key, context) + except IndexError: continue - if isinstance(inferedkey, Const) \ - and inferedkey.value == lookup_key: + for inferredkey in key.infer(context): + if inferredkey is util.YES: + continue + if isinstance(inferredkey, Const) \ + and inferredkey.value == lookup_key: return value # This should raise KeyError, but all call sites only catch # IndexError. Let's leave it like that for now. raise IndexError(lookup_key) -class Discard(Statement): - """class representing a Discard node""" +class Expr(bases.Statement): + """class representing a Expr node""" _astroid_fields = ('value',) value = None -class Ellipsis(NodeNG): +class Ellipsis(bases.NodeNG): # pylint: disable=redefined-builtin """class representing an Ellipsis node""" -class EmptyNode(NodeNG): +class EmptyNode(bases.NodeNG): """class representing an EmptyNode node""" -class ExceptHandler(Statement, AssignTypeMixin): +class ExceptHandler(mixins.AssignTypeMixin, bases.Statement): """class representing an ExceptHandler node""" _astroid_fields = ('type', 'name', 'body',) type = None name = None body = None - @cachedproperty + @decorators.cachedproperty def blockstart_tolineno(self): if self.name: return self.name.tolineno @@ -622,7 +687,7 @@ def catch(self, exceptions): return True -class Exec(Statement): +class Exec(bases.Statement): """class representing an Exec node""" _astroid_fields = ('expr', 'globals', 'locals',) expr = None @@ -630,12 +695,12 @@ class Exec(Statement): locals = None -class ExtSlice(NodeNG): +class ExtSlice(bases.NodeNG): """class representing an ExtSlice node""" _astroid_fields = ('dims',) dims = None -class For(BlockRangeMixIn, AssignTypeMixin, Statement): +class For(mixins.BlockRangeMixIn, mixins.AssignTypeMixin, bases.Statement): """class representing a For node""" _astroid_fields = ('target', 'iter', 'body', 'orelse',) target = None @@ -644,12 +709,26 @@ class For(BlockRangeMixIn, AssignTypeMixin, Statement): orelse = None optional_assign = True - @cachedproperty + @decorators.cachedproperty def blockstart_tolineno(self): return self.iter.tolineno -class From(FromImportMixIn, Statement): +class AsyncFor(For): + """Asynchronous For built with `async` keyword.""" + + +class Await(bases.NodeNG): + """Await node for the `await` keyword.""" + + _astroid_fields = ('value', ) + value = None + + def postinit(self, value=None): + self.value = value + + +class ImportFrom(mixins.ImportFromMixin, bases.Statement): """class representing a From node""" def __init__(self, fromname, names, level=0): @@ -657,13 +736,13 @@ def __init__(self, fromname, names, level=0): self.names = names self.level = level -class Getattr(NodeNG): - """class representing a Getattr node""" +class Attribute(bases.NodeNG): + """class representing a Attribute node""" _astroid_fields = ('expr',) expr = None -class Global(Statement): +class Global(bases.Statement): """class representing a Global node""" def __init__(self, names): @@ -673,14 +752,14 @@ def _infer_name(self, frame, name): return name -class If(BlockRangeMixIn, Statement): +class If(mixins.BlockRangeMixIn, bases.Statement): """class representing an If node""" _astroid_fields = ('test', 'body', 'orelse') test = None body = None orelse = None - @cachedproperty + @decorators.cachedproperty def blockstart_tolineno(self): return self.test.tolineno @@ -694,7 +773,7 @@ def block_range(self, lineno): self.body[0].fromlineno - 1) -class IfExp(NodeNG): +class IfExp(bases.NodeNG): """class representing an IfExp node""" _astroid_fields = ('test', 'body', 'orelse') test = None @@ -702,31 +781,24 @@ class IfExp(NodeNG): orelse = None -class Import(FromImportMixIn, Statement): +class Import(mixins.ImportFromMixin, bases.Statement): """class representing an Import node""" -class Index(NodeNG): +class Index(bases.NodeNG): """class representing an Index node""" _astroid_fields = ('value',) value = None -class Keyword(NodeNG): +class Keyword(bases.NodeNG): """class representing a Keyword node""" _astroid_fields = ('value',) value = None -class List(NodeNG, Instance, ParentAssignTypeMixin): +class List(_BaseContainer): """class representing a List node""" - _astroid_fields = ('elts',) - - def __init__(self, elts=None): - if elts is None: - self.elts = [] - else: - self.elts = [const_factory(e) for e in elts] def pytype(self): return '%s.list' % BUILTINS @@ -734,11 +806,8 @@ def pytype(self): def getitem(self, index, context=None): return self.elts[index] - def itered(self): - return self.elts - -class Nonlocal(Statement): +class Nonlocal(bases.Statement): """class representing a Nonlocal node""" def __init__(self, names): @@ -748,21 +817,21 @@ def _infer_name(self, frame, name): return name -class Pass(Statement): +class Pass(bases.Statement): """class representing a Pass node""" -class Print(Statement): +class Print(bases.Statement): """class representing a Print node""" _astroid_fields = ('dest', 'values',) dest = None values = None -class Raise(Statement): +class Raise(bases.Statement): """class representing a Raise node""" exc = None - if sys.version_info < (3, 0): + if six.PY2: _astroid_fields = ('exc', 'inst', 'tback') inst = None tback = None @@ -779,50 +848,40 @@ def raises_not_implemented(self): return True -class Return(Statement): +class Return(bases.Statement): """class representing a Return node""" _astroid_fields = ('value',) value = None -class Set(NodeNG, Instance, ParentAssignTypeMixin): +class Set(_BaseContainer): """class representing a Set node""" - _astroid_fields = ('elts',) - - def __init__(self, elts=None): - if elts is None: - self.elts = [] - else: - self.elts = [const_factory(e) for e in elts] def pytype(self): return '%s.set' % BUILTINS - def itered(self): - return self.elts - -class Slice(NodeNG): +class Slice(bases.NodeNG): """class representing a Slice node""" _astroid_fields = ('lower', 'upper', 'step') lower = None upper = None step = None -class Starred(NodeNG, ParentAssignTypeMixin): +class Starred(mixins.ParentAssignTypeMixin, bases.NodeNG): """class representing a Starred node""" _astroid_fields = ('value',) value = None -class Subscript(NodeNG): +class Subscript(bases.NodeNG): """class representing a Subscript node""" _astroid_fields = ('value', 'slice') value = None slice = None -class TryExcept(BlockRangeMixIn, Statement): +class TryExcept(mixins.BlockRangeMixIn, bases.Statement): """class representing a TryExcept node""" _astroid_fields = ('body', 'handlers', 'orelse',) body = None @@ -845,7 +904,7 @@ def block_range(self, lineno): return self._elsed_block_range(lineno, self.orelse, last) -class TryFinally(BlockRangeMixIn, Statement): +class TryFinally(mixins.BlockRangeMixIn, bases.Statement): """class representing a TryFinally node""" _astroid_fields = ('body', 'finalbody',) body = None @@ -861,15 +920,8 @@ def block_range(self, lineno): return self._elsed_block_range(lineno, self.finalbody) -class Tuple(NodeNG, Instance, ParentAssignTypeMixin): +class Tuple(_BaseContainer): """class representing a Tuple node""" - _astroid_fields = ('elts',) - - def __init__(self, elts=None): - if elts is None: - self.elts = [] - else: - self.elts = [const_factory(e) for e in elts] def pytype(self): return '%s.tuple' % BUILTINS @@ -877,24 +929,21 @@ def pytype(self): def getitem(self, index, context=None): return self.elts[index] - def itered(self): - return self.elts - -class UnaryOp(NodeNG): +class UnaryOp(bases.NodeNG): """class representing an UnaryOp node""" _astroid_fields = ('operand',) operand = None -class While(BlockRangeMixIn, Statement): +class While(mixins.BlockRangeMixIn, bases.Statement): """class representing a While node""" _astroid_fields = ('test', 'body', 'orelse',) test = None body = None orelse = None - @cachedproperty + @decorators.cachedproperty def blockstart_tolineno(self): return self.test.tolineno @@ -903,13 +952,13 @@ def block_range(self, lineno): return self. _elsed_block_range(lineno, self.orelse) -class With(BlockRangeMixIn, AssignTypeMixin, Statement): +class With(mixins.BlockRangeMixIn, mixins.AssignTypeMixin, bases.Statement): """class representing a With node""" _astroid_fields = ('items', 'body') items = None body = None - @cachedproperty + @decorators.cachedproperty def blockstart_tolineno(self): return self.items[-1][0].tolineno @@ -921,7 +970,12 @@ def get_children(self): for elt in self.body: yield elt -class Yield(NodeNG): + +class AsyncWith(With): + """Asynchronous `with` built with the `async` keyword.""" + + +class Yield(bases.NodeNG): """class representing a Yield node""" _astroid_fields = ('value',) value = None @@ -929,6 +983,11 @@ class Yield(NodeNG): class YieldFrom(Yield): """ Class representing a YieldFrom node. """ + +class DictUnpack(bases.NodeNG): + """Represents the unpacking of dicts into dicts using PEP 448.""" + + # constants ############################################################## CONST_CLS = { @@ -937,19 +996,20 @@ class YieldFrom(Yield): dict: Dict, set: Set, type(None): Const, + type(NotImplemented): Const, } def _update_const_classes(): """update constant classes, so the keys of CONST_CLS can be reused""" klasses = (bool, int, float, complex, str) - if sys.version_info < (3, 0): + if six.PY2: klasses += (unicode, long) - if sys.version_info >= (2, 6): - klasses += (bytes,) + klasses += (bytes,) for kls in klasses: CONST_CLS[kls] = Const _update_const_classes() + def const_factory(value): """return an astroid node for a python value""" # XXX we should probably be stricter here and only consider stuff in @@ -957,10 +1017,37 @@ def const_factory(value): # we should rather recall the builder on this value than returning an empty # node (another option being that const_factory shouldn't be called with something # not in CONST_CLS) - assert not isinstance(value, NodeNG) + assert not isinstance(value, bases.NodeNG) try: return CONST_CLS[value.__class__](value) except (KeyError, AttributeError): node = EmptyNode() node.object = value return node + + +# Backward-compatibility aliases +def instancecheck(cls, other): + wrapped = cls.__wrapped__ + other_cls = other.__class__ + is_instance_of = wrapped is other_cls or issubclass(other_cls, wrapped) + warnings.warn("%r is deprecated and slated for removal in astroid " + "2.0, use %r instead" % (cls.__class__.__name__, + wrapped.__name__), + PendingDeprecationWarning, stacklevel=2) + return is_instance_of + + +def proxy_alias(alias_name, node_type): + proxy = type(alias_name, (lazy_object_proxy.Proxy,), + {'__class__': object.__dict__['__class__'], + '__instancecheck__': instancecheck}) + return proxy(lambda: node_type) + +Backquote = proxy_alias('Backquote', Repr) +Discard = proxy_alias('Discard', Expr) +AssName = proxy_alias('AssName', AssignName) +AssAttr = proxy_alias('AssAttr', AssignAttr) +Getattr = proxy_alias('Getattr', Attribute) +CallFunc = proxy_alias('CallFunc', Call) +From = proxy_alias('From', ImportFrom) diff --git a/pymode/libs/astroid/nodes.py b/pymode/libs/astroid/nodes.py index 67c2f8e8..2fd6cb65 100644 --- a/pymode/libs/astroid/nodes.py +++ b/pymode/libs/astroid/nodes.py @@ -24,40 +24,54 @@ .next_sibling(), returning next sibling statement node .statement(), returning the first parent node marked as statement node .frame(), returning the first node defining a new local scope (i.e. - Module, Function or Class) + Module, FunctionDef or ClassDef) .set_local(name, node), define an identifier on the first parent frame, with the node defining it. This is used by the astroid builder and should not be used from out there. -on From and Import : +on ImportFrom and Import : .real_name(name), """ -# pylint: disable=unused-import +# pylint: disable=unused-import,redefined-builtin + +from astroid.node_classes import ( + Arguments, AssignAttr, Assert, Assign, + AssignName, AugAssign, Repr, BinOp, BoolOp, Break, Call, Compare, + Comprehension, Const, Continue, Decorators, DelAttr, DelName, Delete, + Dict, Expr, Ellipsis, EmptyNode, ExceptHandler, Exec, ExtSlice, For, + ImportFrom, Attribute, Global, If, IfExp, Import, Index, Keyword, + List, Name, Nonlocal, Pass, Print, Raise, Return, Set, Slice, Starred, Subscript, + TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, YieldFrom, + const_factory, + AsyncFor, Await, AsyncWith, + # Backwards-compatibility aliases + Backquote, Discard, AssName, AssAttr, Getattr, CallFunc, From, + # Node not present in the builtin ast module. + DictUnpack, +) +from astroid.scoped_nodes import ( + Module, GeneratorExp, Lambda, DictComp, + ListComp, SetComp, FunctionDef, ClassDef, + AsyncFunctionDef, + # Backwards-compatibility aliases + Class, Function, GenExpr, +) -__docformat__ = "restructuredtext en" -from astroid.node_classes import Arguments, AssAttr, Assert, Assign, \ - AssName, AugAssign, Backquote, BinOp, BoolOp, Break, CallFunc, Compare, \ - Comprehension, Const, Continue, Decorators, DelAttr, DelName, Delete, \ - Dict, Discard, Ellipsis, EmptyNode, ExceptHandler, Exec, ExtSlice, For, \ - From, Getattr, Global, If, IfExp, Import, Index, Keyword, \ - List, Name, Nonlocal, Pass, Print, Raise, Return, Set, Slice, Starred, Subscript, \ - TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, YieldFrom, \ - const_factory -from astroid.scoped_nodes import Module, GenExpr, Lambda, DictComp, \ - ListComp, SetComp, Function, Class ALL_NODE_CLASSES = ( - Arguments, AssAttr, Assert, Assign, AssName, AugAssign, - Backquote, BinOp, BoolOp, Break, - CallFunc, Class, Compare, Comprehension, Const, Continue, + AsyncFunctionDef, AsyncFor, AsyncWith, Await, + + Arguments, AssignAttr, Assert, Assign, AssignName, AugAssign, + Repr, BinOp, BoolOp, Break, + Call, ClassDef, Compare, Comprehension, Const, Continue, Decorators, DelAttr, DelName, Delete, - Dict, DictComp, Discard, + Dict, DictComp, DictUnpack, Expr, Ellipsis, EmptyNode, ExceptHandler, Exec, ExtSlice, - For, From, Function, - Getattr, GenExpr, Global, + For, ImportFrom, FunctionDef, + Attribute, GeneratorExp, Global, If, IfExp, Import, Index, Keyword, Lambda, List, ListComp, @@ -69,6 +83,5 @@ TryExcept, TryFinally, Tuple, UnaryOp, While, With, - Yield, YieldFrom + Yield, YieldFrom, ) - diff --git a/pymode/libs/astroid/objects.py b/pymode/libs/astroid/objects.py new file mode 100644 index 00000000..d2f4270b --- /dev/null +++ b/pymode/libs/astroid/objects.py @@ -0,0 +1,186 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . + +""" +Inference objects are a way to represent composite AST nodes, +which are used only as inference results, so they can't be found in the +code tree. For instance, inferring the following frozenset use, leads to an +inferred FrozenSet: + + CallFunc(func=Name('frozenset'), args=Tuple(...)) + +""" + +import six + +from astroid import MANAGER +from astroid.bases import ( + BUILTINS, NodeNG, Instance, _infer_stmts, + BoundMethod, _is_property +) +from astroid.decorators import cachedproperty +from astroid.exceptions import ( + SuperError, SuperArgumentTypeError, + NotFoundError, MroError +) +from astroid.node_classes import const_factory +from astroid.scoped_nodes import ClassDef, FunctionDef +from astroid.mixins import ParentAssignTypeMixin + + +class FrozenSet(NodeNG, Instance, ParentAssignTypeMixin): + """class representing a FrozenSet composite node""" + + def __init__(self, elts=None): + if elts is None: + self.elts = [] + else: + self.elts = [const_factory(e) for e in elts] + + def pytype(self): + return '%s.frozenset' % BUILTINS + + def itered(self): + return self.elts + + def _infer(self, context=None): + yield self + + @cachedproperty + def _proxied(self): + builtins = MANAGER.astroid_cache[BUILTINS] + return builtins.getattr('frozenset')[0] + + +class Super(NodeNG): + """Proxy class over a super call. + + This class offers almost the same behaviour as Python's super, + which is MRO lookups for retrieving attributes from the parents. + + The *mro_pointer* is the place in the MRO from where we should + start looking, not counting it. *mro_type* is the object which + provides the MRO, it can be both a type or an instance. + *self_class* is the class where the super call is, while + *scope* is the function where the super call is. + """ + + def __init__(self, mro_pointer, mro_type, self_class, scope): + self.type = mro_type + self.mro_pointer = mro_pointer + self._class_based = False + self._self_class = self_class + self._scope = scope + self._model = { + '__thisclass__': self.mro_pointer, + '__self_class__': self._self_class, + '__self__': self.type, + '__class__': self._proxied, + } + + def _infer(self, context=None): + yield self + + def super_mro(self): + """Get the MRO which will be used to lookup attributes in this super.""" + if not isinstance(self.mro_pointer, ClassDef): + raise SuperArgumentTypeError("The first super argument must be type.") + + if isinstance(self.type, ClassDef): + # `super(type, type)`, most likely in a class method. + self._class_based = True + mro_type = self.type + else: + mro_type = getattr(self.type, '_proxied', None) + if not isinstance(mro_type, (Instance, ClassDef)): + raise SuperArgumentTypeError("super(type, obj): obj must be an " + "instance or subtype of type") + + if not mro_type.newstyle: + raise SuperError("Unable to call super on old-style classes.") + + mro = mro_type.mro() + if self.mro_pointer not in mro: + raise SuperArgumentTypeError("super(type, obj): obj must be an " + "instance or subtype of type") + + index = mro.index(self.mro_pointer) + return mro[index + 1:] + + @cachedproperty + def _proxied(self): + builtins = MANAGER.astroid_cache[BUILTINS] + return builtins.getattr('super')[0] + + def pytype(self): + return '%s.super' % BUILTINS + + def display_type(self): + return 'Super of' + + @property + def name(self): + """Get the name of the MRO pointer.""" + return self.mro_pointer.name + + def igetattr(self, name, context=None): + """Retrieve the inferred values of the given attribute name.""" + + local_name = self._model.get(name) + if local_name: + yield local_name + return + + try: + mro = self.super_mro() + except (MroError, SuperError) as exc: + # Don't let invalid MROs or invalid super calls + # to leak out as is from this function. + six.raise_from(NotFoundError, exc) + + found = False + for cls in mro: + if name not in cls._locals: + continue + + found = True + for infered in _infer_stmts([cls[name]], context, frame=self): + if not isinstance(infered, FunctionDef): + yield infered + continue + + # We can obtain different descriptors from a super depending + # on what we are accessing and where the super call is. + if infered.type == 'classmethod': + yield BoundMethod(infered, cls) + elif self._scope.type == 'classmethod' and infered.type == 'method': + yield infered + elif self._class_based or infered.type == 'staticmethod': + yield infered + elif _is_property(infered): + # TODO: support other descriptors as well. + for value in infered.infer_call_result(self, context): + yield value + else: + yield BoundMethod(infered, cls) + + if not found: + raise NotFoundError(name) + + def getattr(self, name, context=None): + return list(self.igetattr(name, context=context)) diff --git a/pymode/libs/astroid/protocols.py b/pymode/libs/astroid/protocols.py index 4c11f9cf..87a6d4d2 100644 --- a/pymode/libs/astroid/protocols.py +++ b/pymode/libs/astroid/protocols.py @@ -19,28 +19,31 @@ where it makes sense. """ -__doctype__ = "restructuredtext en" import collections - -from astroid.exceptions import InferenceError, NoDefault, NotFoundError -from astroid.node_classes import unpack_infer -from astroid.bases import InferenceContext, copy_context, \ - raise_if_nothing_infered, yes_if_nothing_infered, Instance, YES -from astroid.nodes import const_factory +import operator +import sys + +from astroid import arguments +from astroid import bases +from astroid import context as contextmod +from astroid import exceptions +from astroid import node_classes from astroid import nodes +from astroid import util BIN_OP_METHOD = {'+': '__add__', '-': '__sub__', '/': '__div__', '//': '__floordiv__', '*': '__mul__', - '**': '__power__', + '**': '__pow__', '%': '__mod__', '&': '__and__', '|': '__or__', '^': '__xor__', '<<': '__lshift__', '>>': '__rshift__', + '@': '__matmul__' } UNARY_OP_METHOD = {'+': '__pos__', @@ -53,7 +56,7 @@ def tl_infer_unary_op(self, operator): if operator == 'not': - return const_factory(not bool(self.elts)) + return node_classes.const_factory(not bool(self.elts)) raise TypeError() # XXX log unsupported operation nodes.Tuple.infer_unary_op = tl_infer_unary_op nodes.List.infer_unary_op = tl_infer_unary_op @@ -61,19 +64,19 @@ def tl_infer_unary_op(self, operator): def dict_infer_unary_op(self, operator): if operator == 'not': - return const_factory(not bool(self.items)) + return node_classes.const_factory(not bool(self.items)) raise TypeError() # XXX log unsupported operation nodes.Dict.infer_unary_op = dict_infer_unary_op def const_infer_unary_op(self, operator): if operator == 'not': - return const_factory(not self.value) + return node_classes.const_factory(not self.value) # XXX log potentially raised TypeError elif operator == '+': - return const_factory(+self.value) + return node_classes.const_factory(+self.value) else: # operator == '-': - return const_factory(-self.value) + return node_classes.const_factory(-self.value) nodes.Const.infer_unary_op = const_infer_unary_op @@ -92,17 +95,23 @@ def const_infer_unary_op(self, operator): '<<': lambda a, b: a << b, '>>': lambda a, b: a >> b, } + +if sys.version_info >= (3, 5): + # MatMult is available since Python 3.5+. + BIN_OP_IMPL['@'] = operator.matmul + for key, impl in list(BIN_OP_IMPL.items()): BIN_OP_IMPL[key+'='] = impl -def const_infer_binary_op(self, operator, other, context): +def const_infer_binary_op(self, binop, other, context): + operator = binop.op for other in other.infer(context): if isinstance(other, nodes.Const): try: impl = BIN_OP_IMPL[operator] try: - yield const_factory(impl(self.value, other.value)) + yield node_classes.const_factory(impl(self.value, other.value)) except Exception: # ArithmeticError is not enough: float >> float is a TypeError # TODO : let pylint know about the problem @@ -110,68 +119,88 @@ def const_infer_binary_op(self, operator, other, context): except TypeError: # XXX log TypeError continue - elif other is YES: + elif other is util.YES: yield other else: try: - for val in other.infer_binary_op(operator, self, context): + for val in other.infer_binary_op(binop, self, context): yield val except AttributeError: - yield YES -nodes.Const.infer_binary_op = yes_if_nothing_infered(const_infer_binary_op) + yield util.YES +nodes.Const.infer_binary_op = bases.yes_if_nothing_inferred(const_infer_binary_op) + + +def _multiply_seq_by_int(self, binop, other, context): + node = self.__class__() + node.parent = binop + elts = [] + for elt in self.elts: + infered = util.safe_infer(elt, context) + if infered is None: + infered = util.YES + elts.append(infered) + node.elts = elts * other.value + return node -def tl_infer_binary_op(self, operator, other, context): + +def _filter_uninferable_nodes(elts, context): + for elt in elts: + if elt is util.YES: + yield elt + else: + for inferred in elt.infer(context): + yield inferred + + +def tl_infer_binary_op(self, binop, other, context): + operator = binop.op for other in other.infer(context): if isinstance(other, self.__class__) and operator == '+': node = self.__class__() - elts = [n for elt in self.elts for n in elt.infer(context) - if not n is YES] - elts += [n for elt in other.elts for n in elt.infer(context) - if not n is YES] + node.parent = binop + elts = list(_filter_uninferable_nodes(self.elts, context)) + elts += list(_filter_uninferable_nodes(other.elts, context)) node.elts = elts yield node elif isinstance(other, nodes.Const) and operator == '*': if not isinstance(other.value, int): - yield YES + yield util.YES continue - node = self.__class__() - elts = [n for elt in self.elts for n in elt.infer(context) - if not n is YES] * other.value - node.elts = elts - yield node - elif isinstance(other, Instance) and not isinstance(other, nodes.Const): - yield YES + yield _multiply_seq_by_int(self, binop, other, context) + elif isinstance(other, bases.Instance) and not isinstance(other, nodes.Const): + yield util.YES # XXX else log TypeError -nodes.Tuple.infer_binary_op = yes_if_nothing_infered(tl_infer_binary_op) -nodes.List.infer_binary_op = yes_if_nothing_infered(tl_infer_binary_op) +nodes.Tuple.infer_binary_op = bases.yes_if_nothing_inferred(tl_infer_binary_op) +nodes.List.infer_binary_op = bases.yes_if_nothing_inferred(tl_infer_binary_op) -def dict_infer_binary_op(self, operator, other, context): +def dict_infer_binary_op(self, binop, other, context): for other in other.infer(context): - if isinstance(other, Instance) and isinstance(other._proxied, nodes.Class): - yield YES + if isinstance(other, bases.Instance) and isinstance(other._proxied, nodes.ClassDef): + yield util.YES # XXX else log TypeError -nodes.Dict.infer_binary_op = yes_if_nothing_infered(dict_infer_binary_op) +nodes.Dict.infer_binary_op = bases.yes_if_nothing_inferred(dict_infer_binary_op) -def instance_infer_binary_op(self, operator, other, context): +def instance_infer_binary_op(self, binop, other, context): + operator = binop.op try: methods = self.getattr(BIN_OP_METHOD[operator]) - except (NotFoundError, KeyError): + except (exceptions.NotFoundError, KeyError): # Unknown operator - yield YES + yield util.YES else: for method in methods: - if not isinstance(method, nodes.Function): + if not isinstance(method, nodes.FunctionDef): continue for result in method.infer_call_result(self, context): - if result is not YES: + if result is not util.YES: yield result # We are interested only in the first infered method, # don't go looking in the rest of the methods of the ancestors. break -Instance.infer_binary_op = yes_if_nothing_infered(instance_infer_binary_op) +bases.Instance.infer_binary_op = bases.yes_if_nothing_inferred(instance_infer_binary_op) # assignment ################################################################## @@ -192,7 +221,7 @@ def _resolve_looppart(parts, asspath, context): asspath = asspath[:] index = asspath.pop(0) for part in parts: - if part is YES: + if part is util.YES: continue # XXX handle __iter__ and log potentially detected errors if not hasattr(part, 'itered'): @@ -212,104 +241,125 @@ def _resolve_looppart(parts, asspath, context): # we achieved to resolved the assignment path, # don't infer the last part yield assigned - elif assigned is YES: + elif assigned is util.YES: break else: # we are not yet on the last part of the path # search on each possibly inferred value try: - for infered in _resolve_looppart(assigned.infer(context), + for inferred in _resolve_looppart(assigned.infer(context), asspath, context): - yield infered - except InferenceError: + yield inferred + except exceptions.InferenceError: break -def for_assigned_stmts(self, node, context=None, asspath=None): +@bases.raise_if_nothing_inferred +def for_assigned_stmts(self, node=None, context=None, asspath=None): if asspath is None: for lst in self.iter.infer(context): if isinstance(lst, (nodes.Tuple, nodes.List)): for item in lst.elts: yield item else: - for infered in _resolve_looppart(self.iter.infer(context), + for inferred in _resolve_looppart(self.iter.infer(context), asspath, context): - yield infered + yield inferred -nodes.For.assigned_stmts = raise_if_nothing_infered(for_assigned_stmts) -nodes.Comprehension.assigned_stmts = raise_if_nothing_infered(for_assigned_stmts) +nodes.For.assigned_stmts = for_assigned_stmts +nodes.Comprehension.assigned_stmts = for_assigned_stmts -def mulass_assigned_stmts(self, node, context=None, asspath=None): +def sequence_assigned_stmts(self, node=None, context=None, asspath=None): if asspath is None: asspath = [] - asspath.insert(0, self.elts.index(node)) - return self.parent.assigned_stmts(self, context, asspath) -nodes.Tuple.assigned_stmts = mulass_assigned_stmts -nodes.List.assigned_stmts = mulass_assigned_stmts + try: + index = self.elts.index(node) + except ValueError: + util.reraise(exceptions.InferenceError( + 'Tried to retrieve a node {node!r} which does not exist', + node=self, assign_path=asspath, context=context)) + + asspath.insert(0, index) + return self.parent.assigned_stmts(node=self, context=context, asspath=asspath) + +nodes.Tuple.assigned_stmts = sequence_assigned_stmts +nodes.List.assigned_stmts = sequence_assigned_stmts -def assend_assigned_stmts(self, context=None): - return self.parent.assigned_stmts(self, context=context) -nodes.AssName.assigned_stmts = assend_assigned_stmts -nodes.AssAttr.assigned_stmts = assend_assigned_stmts +def assend_assigned_stmts(self, node=None, context=None, asspath=None): + return self.parent.assigned_stmts(node=self, context=context) +nodes.AssignName.assigned_stmts = assend_assigned_stmts +nodes.AssignAttr.assigned_stmts = assend_assigned_stmts def _arguments_infer_argname(self, name, context): # arguments information may be missing, in which case we can't do anything # more if not (self.args or self.vararg or self.kwarg): - yield YES + yield util.YES return # first argument of instance/class method if self.args and getattr(self.args[0], 'name', None) == name: functype = self.parent.type if functype == 'method': - yield Instance(self.parent.parent.frame()) + yield bases.Instance(self.parent.parent.frame()) return if functype == 'classmethod': yield self.parent.parent.frame() return + + if context and context.callcontext: + call_site = arguments.CallSite(context.callcontext) + for value in call_site.infer_argument(self.parent, name, context): + yield value + return + + # TODO: just provide the type here, no need to have an empty Dict. if name == self.vararg: - vararg = const_factory(()) + vararg = node_classes.const_factory(()) vararg.parent = self yield vararg return if name == self.kwarg: - kwarg = const_factory({}) + kwarg = node_classes.const_factory({}) kwarg.parent = self yield kwarg return # if there is a default value, yield it. And then yield YES to reflect # we can't guess given argument value try: - context = copy_context(context) - for infered in self.default_value(name).infer(context): - yield infered - yield YES - except NoDefault: - yield YES + context = contextmod.copy_context(context) + for inferred in self.default_value(name).infer(context): + yield inferred + yield util.YES + except exceptions.NoDefault: + yield util.YES -def arguments_assigned_stmts(self, node, context, asspath=None): +def arguments_assigned_stmts(self, node=None, context=None, asspath=None): if context.callcontext: # reset call context/name callcontext = context.callcontext - context = copy_context(context) + context = contextmod.copy_context(context) context.callcontext = None - return callcontext.infer_argument(self.parent, node.name, context) + args = arguments.CallSite(callcontext) + return args.infer_argument(self.parent, node.name, context) return _arguments_infer_argname(self, node.name, context) + nodes.Arguments.assigned_stmts = arguments_assigned_stmts -def assign_assigned_stmts(self, node, context=None, asspath=None): +@bases.raise_if_nothing_inferred +def assign_assigned_stmts(self, node=None, context=None, asspath=None): if not asspath: yield self.value return - for infered in _resolve_asspart(self.value.infer(context), asspath, context): - yield infered -nodes.Assign.assigned_stmts = raise_if_nothing_infered(assign_assigned_stmts) -nodes.AugAssign.assigned_stmts = raise_if_nothing_infered(assign_assigned_stmts) + for inferred in _resolve_asspart(self.value.infer(context), asspath, context): + yield inferred + +nodes.Assign.assigned_stmts = assign_assigned_stmts +nodes.AugAssign.assigned_stmts = assign_assigned_stmts def _resolve_asspart(parts, asspath, context): @@ -328,28 +378,30 @@ def _resolve_asspart(parts, asspath, context): # we achieved to resolved the assignment path, don't infer the # last part yield assigned - elif assigned is YES: + elif assigned is util.YES: return else: # we are not yet on the last part of the path search on each # possibly inferred value try: - for infered in _resolve_asspart(assigned.infer(context), + for inferred in _resolve_asspart(assigned.infer(context), asspath, context): - yield infered - except InferenceError: + yield inferred + except exceptions.InferenceError: return -def excepthandler_assigned_stmts(self, node, context=None, asspath=None): - for assigned in unpack_infer(self.type): - if isinstance(assigned, nodes.Class): - assigned = Instance(assigned) +@bases.raise_if_nothing_inferred +def excepthandler_assigned_stmts(self, node=None, context=None, asspath=None): + for assigned in node_classes.unpack_infer(self.type): + if isinstance(assigned, nodes.ClassDef): + assigned = bases.Instance(assigned) yield assigned -nodes.ExceptHandler.assigned_stmts = raise_if_nothing_infered(excepthandler_assigned_stmts) +nodes.ExceptHandler.assigned_stmts = bases.raise_if_nothing_inferred(excepthandler_assigned_stmts) -def with_assigned_stmts(self, node, context=None, asspath=None): +@bases.raise_if_nothing_inferred +def with_assigned_stmts(self, node=None, context=None, asspath=None): if asspath is None: for _, vars in self.items: if vars is None: @@ -358,13 +410,14 @@ def with_assigned_stmts(self, node, context=None, asspath=None): if isinstance(lst, (nodes.Tuple, nodes.List)): for item in lst.nodes: yield item -nodes.With.assigned_stmts = raise_if_nothing_infered(with_assigned_stmts) +nodes.With.assigned_stmts = with_assigned_stmts +@bases.yes_if_nothing_inferred def starred_assigned_stmts(self, node=None, context=None, asspath=None): stmt = self.statement() if not isinstance(stmt, (nodes.Assign, nodes.For)): - raise InferenceError() + raise exceptions.InferenceError() if isinstance(stmt, nodes.Assign): value = stmt.value @@ -372,24 +425,24 @@ def starred_assigned_stmts(self, node=None, context=None, asspath=None): if sum(1 for node in lhs.nodes_of_class(nodes.Starred)) > 1: # Too many starred arguments in the expression. - raise InferenceError() + raise exceptions.InferenceError() if context is None: - context = InferenceContext() + context = contextmod.InferenceContext() try: rhs = next(value.infer(context)) - except InferenceError: - yield YES + except exceptions.InferenceError: + yield util.YES return - if rhs is YES or not hasattr(rhs, 'elts'): + if rhs is util.YES or not hasattr(rhs, 'elts'): # Not interested in inferred values without elts. - yield YES + yield util.YES return elts = collections.deque(rhs.elts[:]) if len(lhs.elts) > len(rhs.elts): # a, *b, c = (1, 2) - raise InferenceError() + raise exceptions.InferenceError() # Unpack iteratively the values from the rhs of the assignment, # until the find the starred node. What will remain will @@ -408,8 +461,10 @@ def starred_assigned_stmts(self, node=None, context=None, asspath=None): elts.pop() continue # We're done - for elt in elts: - yield elt + packed = nodes.List() + packed.elts = elts + packed.parent = self + yield packed break nodes.Starred.assigned_stmts = starred_assigned_stmts diff --git a/pymode/libs/astroid/raw_building.py b/pymode/libs/astroid/raw_building.py index 99a026a7..aaaf52f2 100644 --- a/pymode/libs/astroid/raw_building.py +++ b/pymode/libs/astroid/raw_building.py @@ -19,9 +19,8 @@ (build_* functions) or from living object (object_build_* functions) """ -__docformat__ = "restructuredtext en" - import sys +import os from os.path import abspath from inspect import (getargspec, isdatadescriptor, isfunction, ismethod, ismethoddescriptor, isclass, isbuiltin, ismodule) @@ -35,6 +34,8 @@ MANAGER = AstroidManager() _CONSTANTS = tuple(CONST_CLS) # the keys of CONST_CLS eg python builtin types +_JYTHON = os.name == 'java' +_BUILTINS = vars(six.moves.builtins) def _io_discrepancy(member): # _io module names itself `io`: http://bugs.python.org/issue18602 @@ -48,6 +49,18 @@ def _attach_local_node(parent, node, name): node.name = name # needed by add_local_node parent.add_local_node(node) + +def _add_dunder_class(func, member): + """Add a __class__ member to the given func node, if we can determine it.""" + python_cls = member.__class__ + cls_name = getattr(python_cls, '__name__', None) + if not cls_name: + return + bases = [ancestor.__name__ for ancestor in python_cls.__bases__] + ast_klass = build_class(cls_name, bases, python_cls.__doc__) + func._instance_attrs['__class__'] = [ast_klass] + + _marker = object() def attach_dummy_node(node, name, object=_marker): @@ -170,6 +183,7 @@ def object_build_methoddescriptor(node, member, localname): # and empty argument list func.args.args = None node.add_local_node(func, localname) + _add_dunder_class(func, member) def _base_class_object_build(node, member, basenames, name=None, localname=None): """create astroid for a living class object, with a given set of base names @@ -196,7 +210,7 @@ def _base_class_object_build(node, member, basenames, name=None, localname=None) valnode.object = obj valnode.parent = klass valnode.lineno = 1 - klass.instance_attrs[name] = [valnode] + klass._instance_attrs[name] = [valnode] return klass @@ -228,7 +242,7 @@ def inspect_build(self, module, modname=None, path=None): except AttributeError: # in jython, java modules have no __doc__ (see #109562) node = build_module(modname) - node.file = node.path = path and abspath(path) or path + node.source_file = path and abspath(path) or path node.name = modname MANAGER.cache_module(node) node.package = hasattr(module, '__path__') @@ -273,7 +287,7 @@ def object_build(self, node, obj): continue if member in self._done: class_node = self._done[member] - if not class_node in node.locals.get(name, ()): + if not class_node in node._locals.get(name, ()): node.add_local_node(class_node, name) else: class_node = object_build_class(node, member, name) @@ -307,7 +321,8 @@ def imported_member(self, node, member, name): traceback.print_exc() modname = None if modname is None: - if name in ('__new__', '__subclasshook__'): + if (name in ('__new__', '__subclasshook__') + or (name in _BUILTINS and _JYTHON)): # Python 2.5.1 (r251:54863, Sep 1 2010, 22:03:14) # >>> print object.__new__.__module__ # None @@ -315,7 +330,13 @@ def imported_member(self, node, member, name): else: attach_dummy_node(node, name, member) return True - if {'gtk': 'gtk._gtk'}.get(modname, modname) != self._module.__name__: + + real_name = { + 'gtk': 'gtk_gtk', + '_io': 'io', + }.get(modname, modname) + + if real_name != self._module.__name__: # check if it sounds valid and then add an import node, else use a # dummy node try: @@ -337,13 +358,16 @@ def _astroid_bootstrapping(astroid_builtin=None): # this boot strapping is necessary since we need the Const nodes to # inspect_build builtins, and then we can proxy Const if astroid_builtin is None: - from logilab.common.compat import builtins + from six.moves import builtins astroid_builtin = Astroid_BUILDER.inspect_build(builtins) for cls, node_cls in CONST_CLS.items(): if cls is type(None): proxy = build_class('NoneType') proxy.parent = astroid_builtin + elif cls is type(NotImplemented): + proxy = build_class('NotImplementedType') + proxy.parent = astroid_builtin else: proxy = astroid_builtin.getattr(cls.__name__)[0] if cls in (dict, list, set, tuple): diff --git a/pymode/libs/astroid/rebuilder.py b/pymode/libs/astroid/rebuilder.py index 013479a8..eee401ca 100644 --- a/pymode/libs/astroid/rebuilder.py +++ b/pymode/libs/astroid/rebuilder.py @@ -20,10 +20,10 @@ """ import sys +import _ast from _ast import ( - Expr as Discard, Str, # binary operators - Add, BinOp, Div, FloorDiv, Mod, Mult, Pow, Sub, BitAnd, BitOr, BitXor, + Add, Div, FloorDiv, Mod, Mult, Pow, Sub, BitAnd, BitOr, BitXor, LShift, RShift, # logical operators And, Or, @@ -50,6 +50,9 @@ LShift: '<<', RShift: '>>', } +if sys.version_info >= (3, 5): + from _ast import MatMult + _BIN_OP_CLASSES[MatMult] = '@' _BOOL_OP_CLASSES = {And: 'and', Or: 'or', @@ -79,19 +82,11 @@ } REDIRECT = {'arguments': 'Arguments', - 'Attribute': 'Getattr', 'comprehension': 'Comprehension', - 'Call': 'CallFunc', - 'ClassDef': 'Class', "ListCompFor": 'Comprehension', "GenExprFor": 'Comprehension', 'excepthandler': 'ExceptHandler', - 'Expr': 'Discard', - 'FunctionDef': 'Function', - 'GeneratorExp': 'GenExpr', - 'ImportFrom': 'From', 'keyword': 'Keyword', - 'Repr': 'Backquote', } PY3K = sys.version_info >= (3, 0) PY34 = sys.version_info >= (3, 4) @@ -99,7 +94,7 @@ def _init_set_doc(node, newnode): newnode.doc = None try: - if isinstance(node.body[0], Discard) and isinstance(node.body[0].value, Str): + if isinstance(node.body[0], _ast.Expr) and isinstance(node.body[0].value, _ast.Str): newnode.doc = node.body[0].value.s node.body = node.body[1:] @@ -122,9 +117,21 @@ def _create_yield_node(node, parent, rebuilder, factory): newnode = factory() _lineno_parent(node, newnode, parent) if node.value is not None: - newnode.value = rebuilder.visit(node.value, newnode) + newnode.value = rebuilder.visit(node.value, newnode, None) return newnode +def _visit_or_none(node, attr, visitor, parent, assign_ctx, visit='visit', + **kws): + """If the given node has an attribute, visits the attribute, and + otherwise returns None. + + """ + value = getattr(node, attr, None) + if value: + return getattr(visitor, visit)(value, parent, assign_ctx, **kws) + else: + return None + class TreeRebuilder(object): """Rebuilds the _ast tree to become an Astroid tree""" @@ -133,10 +140,9 @@ def __init__(self, manager): self._manager = manager self.asscontext = None self._global_names = [] - self._from_nodes = [] + self._import_from_nodes = [] self._delayed_assattr = [] self._visit_meths = {} - self._transform = manager.transform self._peepholer = astpeephole.ASTPeepholeOptimizer() def visit_module(self, node, modname, modpath, package): @@ -146,10 +152,10 @@ def visit_module(self, node, modname, modpath, package): newnode.parent = None _init_set_doc(node, newnode) newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.file = newnode.path = modpath - return self._transform(newnode) + newnode.source_file = modpath + return newnode - def visit(self, node, parent): + def visit(self, node, parent, assign_ctx=None): cls = node.__class__ if cls in self._visit_meths: visit_method = self._visit_meths[cls] @@ -158,7 +164,7 @@ def visit(self, node, parent): visit_name = 'visit_' + REDIRECT.get(cls_name, cls_name).lower() visit_method = getattr(self, visit_name) self._visit_meths[cls] = visit_method - return self._transform(visit_method(node, parent)) + return visit_method(node, parent, assign_ctx) def _save_assignment(self, node, name=None): """save assignement situation since node.parent is not available yet""" @@ -167,15 +173,14 @@ def _save_assignment(self, node, name=None): else: node.parent.set_local(node.name, node) - - def visit_arguments(self, node, parent): + def visit_arguments(self, node, parent, assign_ctx=None): """visit a Arguments node by returning a fresh instance of it""" newnode = new.Arguments() newnode.parent = parent - self.asscontext = "Ass" - newnode.args = [self.visit(child, newnode) for child in node.args] - self.asscontext = None - newnode.defaults = [self.visit(child, newnode) for child in node.defaults] + newnode.args = [self.visit(child, newnode, "Assign") + for child in node.args] + newnode.defaults = [self.visit(child, newnode, assign_ctx) + for child in node.defaults] newnode.kwonlyargs = [] newnode.kw_defaults = [] vararg, kwarg = node.vararg, node.kwarg @@ -185,21 +190,21 @@ def visit_arguments(self, node, parent): if PY34: if vararg.annotation: newnode.varargannotation = self.visit(vararg.annotation, - newnode) + newnode, assign_ctx) vararg = vararg.arg elif PY3K and node.varargannotation: newnode.varargannotation = self.visit(node.varargannotation, - newnode) + newnode, assign_ctx) if kwarg: if PY34: if kwarg.annotation: newnode.kwargannotation = self.visit(kwarg.annotation, - newnode) + newnode, assign_ctx) kwarg = kwarg.arg elif PY3K: if node.kwargannotation: newnode.kwargannotation = self.visit(node.kwargannotation, - newnode) + newnode, assign_ctx) newnode.vararg = vararg newnode.kwarg = kwarg # save argument names in locals: @@ -209,81 +214,59 @@ def visit_arguments(self, node, parent): newnode.parent.set_local(kwarg, newnode) return newnode - def visit_assattr(self, node, parent): + def visit_assignattr(self, node, parent, assign_ctx=None): """visit a AssAttr node by returning a fresh instance of it""" - assc, self.asscontext = self.asscontext, None - newnode = new.AssAttr() + newnode = new.AssignAttr() _lineno_parent(node, newnode, parent) - newnode.expr = self.visit(node.expr, newnode) - self.asscontext = assc + newnode.expr = self.visit(node.expr, newnode, assign_ctx) self._delayed_assattr.append(newnode) return newnode - def visit_assert(self, node, parent): + def visit_assert(self, node, parent, assign_ctx=None): """visit a Assert node by returning a fresh instance of it""" newnode = new.Assert() _lineno_parent(node, newnode, parent) - newnode.test = self.visit(node.test, newnode) + newnode.test = self.visit(node.test, newnode, assign_ctx) if node.msg is not None: - newnode.fail = self.visit(node.msg, newnode) + newnode.fail = self.visit(node.msg, newnode, assign_ctx) return newnode - def visit_assign(self, node, parent): + def visit_assign(self, node, parent, assign_ctx=None): """visit a Assign node by returning a fresh instance of it""" newnode = new.Assign() _lineno_parent(node, newnode, parent) - self.asscontext = "Ass" - newnode.targets = [self.visit(child, newnode) for child in node.targets] - self.asscontext = None - newnode.value = self.visit(node.value, newnode) - # set some function or metaclass infos XXX explain ? - klass = newnode.parent.frame() - if (isinstance(klass, new.Class) - and isinstance(newnode.value, new.CallFunc) - and isinstance(newnode.value.func, new.Name)): - func_name = newnode.value.func.name - for ass_node in newnode.targets: - try: - meth = klass[ass_node.name] - if isinstance(meth, new.Function): - if func_name in ('classmethod', 'staticmethod'): - meth.type = func_name - elif func_name == 'classproperty': # see lgc.decorators - meth.type = 'classmethod' - meth.extra_decorators.append(newnode.value) - except (AttributeError, KeyError): - continue - return newnode - - def visit_assname(self, node, parent, node_name=None): + newnode.targets = [self.visit(child, newnode, "Assign") + for child in node.targets] + newnode.value = self.visit(node.value, newnode, None) + return newnode + + def visit_assignname(self, node, parent, assign_ctx=None, node_name=None): '''visit a node and return a AssName node''' - newnode = new.AssName() + newnode = new.AssignName() _set_infos(node, newnode, parent) newnode.name = node_name self._save_assignment(newnode) return newnode - def visit_augassign(self, node, parent): + def visit_augassign(self, node, parent, assign_ctx=None): """visit a AugAssign node by returning a fresh instance of it""" newnode = new.AugAssign() _lineno_parent(node, newnode, parent) newnode.op = _BIN_OP_CLASSES[node.op.__class__] + "=" - self.asscontext = "Ass" - newnode.target = self.visit(node.target, newnode) - self.asscontext = None - newnode.value = self.visit(node.value, newnode) + newnode.target = self.visit(node.target, newnode, "Assign") + newnode.value = self.visit(node.value, newnode, None) return newnode - def visit_backquote(self, node, parent): + def visit_repr(self, node, parent, assign_ctx=None): """visit a Backquote node by returning a fresh instance of it""" - newnode = new.Backquote() + newnode = new.Repr() _lineno_parent(node, newnode, parent) - newnode.value = self.visit(node.value, newnode) + newnode.value = self.visit(node.value, newnode, assign_ctx) return newnode - def visit_binop(self, node, parent): + def visit_binop(self, node, parent, assign_ctx=None): """visit a BinOp node by returning a fresh instance of it""" - if isinstance(node.left, BinOp) and self._manager.optimize_ast: + if isinstance(node.left, _ast.BinOp) and self._manager.optimize_ast: # Optimize BinOp operations in order to remove # redundant recursion. For instance, if the # following code is parsed in order to obtain @@ -296,264 +279,297 @@ def visit_binop(self, node, parent): # problem for the correctness of the program). # # ("a" + "b" + # one thousand more + "c") - newnode = self._peepholer.optimize_binop(node) - if newnode: - _lineno_parent(node, newnode, parent) - return newnode + optimized = self._peepholer.optimize_binop(node) + if optimized: + _lineno_parent(node, optimized, parent) + return optimized newnode = new.BinOp() _lineno_parent(node, newnode, parent) - newnode.left = self.visit(node.left, newnode) - newnode.right = self.visit(node.right, newnode) + newnode.left = self.visit(node.left, newnode, assign_ctx) + newnode.right = self.visit(node.right, newnode, assign_ctx) newnode.op = _BIN_OP_CLASSES[node.op.__class__] return newnode - def visit_boolop(self, node, parent): + def visit_boolop(self, node, parent, assign_ctx=None): """visit a BoolOp node by returning a fresh instance of it""" newnode = new.BoolOp() _lineno_parent(node, newnode, parent) - newnode.values = [self.visit(child, newnode) for child in node.values] + newnode.values = [self.visit(child, newnode, assign_ctx) + for child in node.values] newnode.op = _BOOL_OP_CLASSES[node.op.__class__] return newnode - def visit_break(self, node, parent): + def visit_break(self, node, parent, assign_ctx=None): """visit a Break node by returning a fresh instance of it""" newnode = new.Break() _set_infos(node, newnode, parent) return newnode - def visit_callfunc(self, node, parent): + def visit_call(self, node, parent, assign_ctx=None): """visit a CallFunc node by returning a fresh instance of it""" - newnode = new.CallFunc() - _lineno_parent(node, newnode, parent) - newnode.func = self.visit(node.func, newnode) - newnode.args = [self.visit(child, newnode) for child in node.args] - if node.starargs is not None: - newnode.starargs = self.visit(node.starargs, newnode) - if node.kwargs is not None: - newnode.kwargs = self.visit(node.kwargs, newnode) - for child in node.keywords: - newnode.args.append(self.visit(child, newnode)) + newnode = new.Call() + _lineno_parent(node, newnode, parent) + newnode.func = self.visit(node.func, newnode, assign_ctx) + args = [self.visit(child, newnode, assign_ctx) + for child in node.args] + + starargs = _visit_or_none(node, 'starargs', self, newnode, + assign_ctx) + kwargs = _visit_or_none(node, 'kwargs', self, newnode, + assign_ctx) + keywords = None + if node.keywords: + keywords = [self.visit(child, newnode, assign_ctx) + for child in node.keywords] + + if starargs: + new_starargs = new.Starred() + new_starargs.col_offset = starargs.col_offset + new_starargs.lineno = starargs.lineno + new_starargs.parent = starargs.parent + new_starargs.value = starargs + args.append(new_starargs) + if kwargs: + new_kwargs = new.Keyword() + new_kwargs.arg = None + new_kwargs.col_offset = kwargs.col_offset + new_kwargs.lineno = kwargs.lineno + new_kwargs.parent = kwargs.parent + new_kwargs.value = kwargs + if keywords: + keywords.append(new_kwargs) + else: + keywords = [new_kwargs] + + newnode.args = args + newnode.keywords = keywords return newnode - def visit_class(self, node, parent): + def visit_classdef(self, node, parent, assign_ctx=None): """visit a Class node to become astroid""" - newnode = new.Class(node.name, None) + newnode = new.ClassDef(node.name, None) _lineno_parent(node, newnode, parent) _init_set_doc(node, newnode) - newnode.bases = [self.visit(child, newnode) for child in node.bases] - newnode.body = [self.visit(child, newnode) for child in node.body] - if 'decorator_list' in node._fields and node.decorator_list:# py >= 2.6 - newnode.decorators = self.visit_decorators(node, newnode) + newnode.bases = [self.visit(child, newnode, assign_ctx) + for child in node.bases] + newnode.body = [self.visit(child, newnode, assign_ctx) + for child in node.body] + if node.decorator_list: + newnode.decorators = self.visit_decorators(node, newnode, assign_ctx) newnode.parent.frame().set_local(newnode.name, newnode) return newnode - def visit_const(self, node, parent): + def visit_const(self, node, parent, assign_ctx=None): """visit a Const node by returning a fresh instance of it""" newnode = new.Const(node.value) _set_infos(node, newnode, parent) return newnode - def visit_continue(self, node, parent): + def visit_continue(self, node, parent, assign_ctx=None): """visit a Continue node by returning a fresh instance of it""" newnode = new.Continue() _set_infos(node, newnode, parent) return newnode - def visit_compare(self, node, parent): + def visit_compare(self, node, parent, assign_ctx=None): """visit a Compare node by returning a fresh instance of it""" newnode = new.Compare() _lineno_parent(node, newnode, parent) - newnode.left = self.visit(node.left, newnode) - newnode.ops = [(_CMP_OP_CLASSES[op.__class__], self.visit(expr, newnode)) + newnode.left = self.visit(node.left, newnode, assign_ctx) + newnode.ops = [(_CMP_OP_CLASSES[op.__class__], self.visit(expr, newnode, assign_ctx)) for (op, expr) in zip(node.ops, node.comparators)] return newnode - def visit_comprehension(self, node, parent): + def visit_comprehension(self, node, parent, assign_ctx=None): """visit a Comprehension node by returning a fresh instance of it""" newnode = new.Comprehension() newnode.parent = parent - self.asscontext = "Ass" - newnode.target = self.visit(node.target, newnode) - self.asscontext = None - newnode.iter = self.visit(node.iter, newnode) - newnode.ifs = [self.visit(child, newnode) for child in node.ifs] + newnode.target = self.visit(node.target, newnode, 'Assign') + newnode.iter = self.visit(node.iter, newnode, None) + newnode.ifs = [self.visit(child, newnode, None) + for child in node.ifs] return newnode - def visit_decorators(self, node, parent): + def visit_decorators(self, node, parent, assign_ctx=None): """visit a Decorators node by returning a fresh instance of it""" # /!\ node is actually a _ast.Function node while # parent is a astroid.nodes.Function node newnode = new.Decorators() _lineno_parent(node, newnode, parent) - if 'decorators' in node._fields: # py < 2.6, i.e. 2.5 - decorators = node.decorators - else: - decorators = node.decorator_list - newnode.nodes = [self.visit(child, newnode) for child in decorators] + decorators = node.decorator_list + newnode.nodes = [self.visit(child, newnode, assign_ctx) + for child in decorators] return newnode - def visit_delete(self, node, parent): + def visit_delete(self, node, parent, assign_ctx=None): """visit a Delete node by returning a fresh instance of it""" newnode = new.Delete() _lineno_parent(node, newnode, parent) - self.asscontext = "Del" - newnode.targets = [self.visit(child, newnode) for child in node.targets] - self.asscontext = None + newnode.targets = [self.visit(child, newnode, 'Del') + for child in node.targets] return newnode - def visit_dict(self, node, parent): + def _visit_dict_items(self, node, parent, newnode, assign_ctx): + for key, value in zip(node.keys, node.values): + rebuilt_value = self.visit(value, newnode, assign_ctx) + if not key: + # Python 3.5 and extended unpacking + rebuilt_key = new.DictUnpack() + rebuilt_key.lineno = rebuilt_value.lineno + rebuilt_key.col_offset = rebuilt_value.col_offset + rebuilt_key.parent = rebuilt_value.parent + else: + rebuilt_key = self.visit(key, newnode, assign_ctx) + yield rebuilt_key, rebuilt_value + + def visit_dict(self, node, parent, assign_ctx=None): """visit a Dict node by returning a fresh instance of it""" newnode = new.Dict() _lineno_parent(node, newnode, parent) - newnode.items = [(self.visit(key, newnode), self.visit(value, newnode)) - for key, value in zip(node.keys, node.values)] + newnode.items = list(self._visit_dict_items(node, parent, newnode, assign_ctx)) return newnode - def visit_dictcomp(self, node, parent): + def visit_dictcomp(self, node, parent, assign_ctx=None): """visit a DictComp node by returning a fresh instance of it""" newnode = new.DictComp() _lineno_parent(node, newnode, parent) - newnode.key = self.visit(node.key, newnode) - newnode.value = self.visit(node.value, newnode) - newnode.generators = [self.visit(child, newnode) + newnode.key = self.visit(node.key, newnode, assign_ctx) + newnode.value = self.visit(node.value, newnode, assign_ctx) + newnode.generators = [self.visit(child, newnode, assign_ctx) for child in node.generators] return newnode - def visit_discard(self, node, parent): + def visit_expr(self, node, parent, assign_ctx=None): """visit a Discard node by returning a fresh instance of it""" - newnode = new.Discard() + newnode = new.Expr() _lineno_parent(node, newnode, parent) - newnode.value = self.visit(node.value, newnode) + newnode.value = self.visit(node.value, newnode, assign_ctx) return newnode - def visit_ellipsis(self, node, parent): + def visit_ellipsis(self, node, parent, assign_ctx=None): """visit an Ellipsis node by returning a fresh instance of it""" newnode = new.Ellipsis() _set_infos(node, newnode, parent) return newnode - def visit_emptynode(self, node, parent): + def visit_emptynode(self, node, parent, assign_ctx=None): """visit an EmptyNode node by returning a fresh instance of it""" newnode = new.EmptyNode() _set_infos(node, newnode, parent) return newnode - def visit_excepthandler(self, node, parent): + def visit_excepthandler(self, node, parent, assign_ctx=None): """visit an ExceptHandler node by returning a fresh instance of it""" newnode = new.ExceptHandler() _lineno_parent(node, newnode, parent) if node.type is not None: - newnode.type = self.visit(node.type, newnode) + newnode.type = self.visit(node.type, newnode, assign_ctx) if node.name is not None: # /!\ node.name can be a tuple - self.asscontext = "Ass" - newnode.name = self.visit(node.name, newnode) - self.asscontext = None - newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.name = self.visit(node.name, newnode, 'Assign') + newnode.body = [self.visit(child, newnode, None) + for child in node.body] return newnode - def visit_exec(self, node, parent): + def visit_exec(self, node, parent, assign_ctx=None): """visit an Exec node by returning a fresh instance of it""" newnode = new.Exec() _lineno_parent(node, newnode, parent) newnode.expr = self.visit(node.body, newnode) if node.globals is not None: - newnode.globals = self.visit(node.globals, newnode) + newnode.globals = self.visit(node.globals, newnode, + assign_ctx) if node.locals is not None: - newnode.locals = self.visit(node.locals, newnode) + newnode.locals = self.visit(node.locals, newnode, + assign_ctx) return newnode - def visit_extslice(self, node, parent): + def visit_extslice(self, node, parent, assign_ctx=None): """visit an ExtSlice node by returning a fresh instance of it""" newnode = new.ExtSlice() newnode.parent = parent - newnode.dims = [self.visit(dim, newnode) for dim in node.dims] + newnode.dims = [self.visit(dim, newnode, assign_ctx) + for dim in node.dims] return newnode - def visit_for(self, node, parent): + def _visit_for(self, cls, node, parent, assign_ctx=None): """visit a For node by returning a fresh instance of it""" - newnode = new.For() + newnode = cls() _lineno_parent(node, newnode, parent) - self.asscontext = "Ass" - newnode.target = self.visit(node.target, newnode) - self.asscontext = None - newnode.iter = self.visit(node.iter, newnode) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.orelse = [self.visit(child, newnode) for child in node.orelse] + newnode.target = self.visit(node.target, newnode, "Assign") + newnode.iter = self.visit(node.iter, newnode, None) + newnode.body = [self.visit(child, newnode, None) + for child in node.body] + newnode.orelse = [self.visit(child, newnode, None) + for child in node.orelse] return newnode - def visit_from(self, node, parent): + def visit_for(self, node, parent, assign_ctx=None): + return self._visit_for(new.For, node, parent, + assign_ctx=assign_ctx) + def visit_importfrom(self, node, parent, assign_ctx=None): """visit a From node by returning a fresh instance of it""" names = [(alias.name, alias.asname) for alias in node.names] - newnode = new.From(node.module or '', names, node.level or None) + newnode = new.ImportFrom(node.module or '', names, node.level or None) _set_infos(node, newnode, parent) # store From names to add them to locals after building - self._from_nodes.append(newnode) + self._import_from_nodes.append(newnode) return newnode - def visit_function(self, node, parent): - """visit an Function node to become astroid""" + def _visit_functiondef(self, cls, node, parent, assign_ctx=None): + """visit an FunctionDef node to become astroid""" self._global_names.append({}) - newnode = new.Function(node.name, None) + newnode = cls(node.name, None) _lineno_parent(node, newnode, parent) _init_set_doc(node, newnode) - newnode.args = self.visit(node.args, newnode) - newnode.body = [self.visit(child, newnode) for child in node.body] - if 'decorators' in node._fields: # py < 2.6 - attr = 'decorators' - else: - attr = 'decorator_list' - decorators = getattr(node, attr) + newnode.args = self.visit(node.args, newnode, assign_ctx) + newnode.body = [self.visit(child, newnode, assign_ctx) + for child in node.body] + decorators = node.decorator_list if decorators: - newnode.decorators = self.visit_decorators(node, newnode) + newnode.decorators = self.visit_decorators( + node, newnode, assign_ctx) if PY3K and node.returns: - newnode.returns = self.visit(node.returns, newnode) + newnode.returns = self.visit(node.returns, newnode, + assign_ctx) self._global_names.pop() frame = newnode.parent.frame() - if isinstance(frame, new.Class): - if newnode.name == '__new__': - newnode._type = 'classmethod' - else: - newnode._type = 'method' - if newnode.decorators is not None: - for decorator_expr in newnode.decorators.nodes: - if isinstance(decorator_expr, new.Name): - if decorator_expr.name in ('classmethod', 'staticmethod'): - newnode._type = decorator_expr.name - elif decorator_expr.name == 'classproperty': - newnode._type = 'classmethod' frame.set_local(newnode.name, newnode) return newnode - def visit_genexpr(self, node, parent): + def visit_functiondef(self, node, parent, assign_ctx=None): + return self._visit_functiondef(new.FunctionDef, node, parent, + assign_ctx=assign_ctx) + + def visit_generatorexp(self, node, parent, assign_ctx=None): """visit a GenExpr node by returning a fresh instance of it""" - newnode = new.GenExpr() + newnode = new.GeneratorExp() _lineno_parent(node, newnode, parent) - newnode.elt = self.visit(node.elt, newnode) - newnode.generators = [self.visit(child, newnode) for child in node.generators] + newnode.elt = self.visit(node.elt, newnode, assign_ctx) + newnode.generators = [self.visit(child, newnode, assign_ctx) + for child in node.generators] return newnode - def visit_getattr(self, node, parent): + def visit_attribute(self, node, parent, assign_ctx=None): """visit a Getattr node by returning a fresh instance of it""" - if self.asscontext == "Del": + # pylint: disable=redefined-variable-type + if assign_ctx == "Del": # FIXME : maybe we should reintroduce and visit_delattr ? # for instance, deactivating asscontext newnode = new.DelAttr() - elif self.asscontext == "Ass": + elif assign_ctx == "Assign": # FIXME : maybe we should call visit_assattr ? - newnode = new.AssAttr() + newnode = new.AssignAttr() self._delayed_assattr.append(newnode) else: - newnode = new.Getattr() + newnode = new.Attribute() _lineno_parent(node, newnode, parent) - asscontext, self.asscontext = self.asscontext, None - newnode.expr = self.visit(node.value, newnode) - self.asscontext = asscontext + newnode.expr = self.visit(node.value, newnode, None) newnode.attrname = node.attr return newnode - def visit_global(self, node, parent): + def visit_global(self, node, parent, assign_ctx=None): """visit an Global node to become astroid""" newnode = new.Global(node.names) _set_infos(node, newnode, parent) @@ -562,25 +578,27 @@ def visit_global(self, node, parent): self._global_names[-1].setdefault(name, []).append(newnode) return newnode - def visit_if(self, node, parent): + def visit_if(self, node, parent, assign_ctx=None): """visit a If node by returning a fresh instance of it""" newnode = new.If() _lineno_parent(node, newnode, parent) - newnode.test = self.visit(node.test, newnode) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.orelse = [self.visit(child, newnode) for child in node.orelse] + newnode.test = self.visit(node.test, newnode, assign_ctx) + newnode.body = [self.visit(child, newnode, assign_ctx) + for child in node.body] + newnode.orelse = [self.visit(child, newnode, assign_ctx) + for child in node.orelse] return newnode - def visit_ifexp(self, node, parent): + def visit_ifexp(self, node, parent, assign_ctx=None): """visit a IfExp node by returning a fresh instance of it""" newnode = new.IfExp() _lineno_parent(node, newnode, parent) - newnode.test = self.visit(node.test, newnode) - newnode.body = self.visit(node.body, newnode) - newnode.orelse = self.visit(node.orelse, newnode) + newnode.test = self.visit(node.test, newnode, assign_ctx) + newnode.body = self.visit(node.body, newnode, assign_ctx) + newnode.orelse = self.visit(node.orelse, newnode, assign_ctx) return newnode - def visit_import(self, node, parent): + def visit_import(self, node, parent, assign_ctx=None): """visit a Import node by returning a fresh instance of it""" newnode = new.Import() _set_infos(node, newnode, parent) @@ -591,53 +609,54 @@ def visit_import(self, node, parent): newnode.parent.set_local(name.split('.')[0], newnode) return newnode - def visit_index(self, node, parent): + def visit_index(self, node, parent, assign_ctx=None): """visit a Index node by returning a fresh instance of it""" newnode = new.Index() newnode.parent = parent - newnode.value = self.visit(node.value, newnode) + newnode.value = self.visit(node.value, newnode, assign_ctx) return newnode - def visit_keyword(self, node, parent): + def visit_keyword(self, node, parent, assign_ctx=None): """visit a Keyword node by returning a fresh instance of it""" newnode = new.Keyword() newnode.parent = parent newnode.arg = node.arg - newnode.value = self.visit(node.value, newnode) + newnode.value = self.visit(node.value, newnode, assign_ctx) return newnode - def visit_lambda(self, node, parent): + def visit_lambda(self, node, parent, assign_ctx=None): """visit a Lambda node by returning a fresh instance of it""" newnode = new.Lambda() _lineno_parent(node, newnode, parent) - newnode.args = self.visit(node.args, newnode) - newnode.body = self.visit(node.body, newnode) + newnode.args = self.visit(node.args, newnode, assign_ctx) + newnode.body = self.visit(node.body, newnode, assign_ctx) return newnode - def visit_list(self, node, parent): + def visit_list(self, node, parent, assign_ctx=None): """visit a List node by returning a fresh instance of it""" newnode = new.List() _lineno_parent(node, newnode, parent) - newnode.elts = [self.visit(child, newnode) for child in node.elts] + newnode.elts = [self.visit(child, newnode, assign_ctx) + for child in node.elts] return newnode - def visit_listcomp(self, node, parent): + def visit_listcomp(self, node, parent, assign_ctx=None): """visit a ListComp node by returning a fresh instance of it""" newnode = new.ListComp() _lineno_parent(node, newnode, parent) - newnode.elt = self.visit(node.elt, newnode) - newnode.generators = [self.visit(child, newnode) + newnode.elt = self.visit(node.elt, newnode, assign_ctx) + newnode.generators = [self.visit(child, newnode, assign_ctx) for child in node.generators] return newnode - def visit_name(self, node, parent): + def visit_name(self, node, parent, assign_ctx=None): """visit a Name node by returning a fresh instance of it""" # True and False can be assigned to something in py2x, so we have to # check first the asscontext - if self.asscontext == "Del": + # pylint: disable=redefined-variable-type + if assign_ctx == "Del": newnode = new.DelName() - elif self.asscontext is not None: # Ass - assert self.asscontext == "Ass" + elif assign_ctx is not None: # Ass newnode = new.AssName() elif node.id in CONST_NAME_TRANSFORMS: newnode = new.Const(CONST_NAME_TRANSFORMS[node.id]) @@ -648,279 +667,321 @@ def visit_name(self, node, parent): _lineno_parent(node, newnode, parent) newnode.name = node.id # XXX REMOVE me : - if self.asscontext in ('Del', 'Ass'): # 'Aug' ?? + if assign_ctx in ('Del', 'Assign'): # 'Aug' ?? self._save_assignment(newnode) return newnode - def visit_bytes(self, node, parent): + def visit_bytes(self, node, parent, assign_ctx=None): """visit a Bytes node by returning a fresh instance of Const""" newnode = new.Const(node.s) _set_infos(node, newnode, parent) return newnode - def visit_num(self, node, parent): + def visit_num(self, node, parent, assign_ctx=None): """visit a Num node by returning a fresh instance of Const""" newnode = new.Const(node.n) _set_infos(node, newnode, parent) return newnode - def visit_pass(self, node, parent): + def visit_pass(self, node, parent, assign_ctx=None): """visit a Pass node by returning a fresh instance of it""" newnode = new.Pass() _set_infos(node, newnode, parent) return newnode - def visit_str(self, node, parent): + def visit_str(self, node, parent, assign_ctx=None): """visit a Str node by returning a fresh instance of Const""" newnode = new.Const(node.s) _set_infos(node, newnode, parent) return newnode - def visit_print(self, node, parent): + def visit_print(self, node, parent, assign_ctx=None): """visit a Print node by returning a fresh instance of it""" newnode = new.Print() _lineno_parent(node, newnode, parent) newnode.nl = node.nl if node.dest is not None: - newnode.dest = self.visit(node.dest, newnode) - newnode.values = [self.visit(child, newnode) for child in node.values] + newnode.dest = self.visit(node.dest, newnode, assign_ctx) + newnode.values = [self.visit(child, newnode, assign_ctx) + for child in node.values] return newnode - def visit_raise(self, node, parent): + def visit_raise(self, node, parent, assign_ctx=None): """visit a Raise node by returning a fresh instance of it""" newnode = new.Raise() _lineno_parent(node, newnode, parent) if node.type is not None: - newnode.exc = self.visit(node.type, newnode) + newnode.exc = self.visit(node.type, newnode, assign_ctx) if node.inst is not None: - newnode.inst = self.visit(node.inst, newnode) + newnode.inst = self.visit(node.inst, newnode, assign_ctx) if node.tback is not None: - newnode.tback = self.visit(node.tback, newnode) + newnode.tback = self.visit(node.tback, newnode, assign_ctx) return newnode - def visit_return(self, node, parent): + def visit_return(self, node, parent, assign_ctx=None): """visit a Return node by returning a fresh instance of it""" newnode = new.Return() _lineno_parent(node, newnode, parent) if node.value is not None: - newnode.value = self.visit(node.value, newnode) + newnode.value = self.visit(node.value, newnode, assign_ctx) return newnode - def visit_set(self, node, parent): + def visit_set(self, node, parent, assign_ctx=None): """visit a Set node by returning a fresh instance of it""" newnode = new.Set() _lineno_parent(node, newnode, parent) - newnode.elts = [self.visit(child, newnode) for child in node.elts] + newnode.elts = [self.visit(child, newnode, assign_ctx) + for child in node.elts] return newnode - def visit_setcomp(self, node, parent): + def visit_setcomp(self, node, parent, assign_ctx=None): """visit a SetComp node by returning a fresh instance of it""" newnode = new.SetComp() _lineno_parent(node, newnode, parent) - newnode.elt = self.visit(node.elt, newnode) - newnode.generators = [self.visit(child, newnode) + newnode.elt = self.visit(node.elt, newnode, assign_ctx) + newnode.generators = [self.visit(child, newnode, assign_ctx) for child in node.generators] return newnode - def visit_slice(self, node, parent): + def visit_slice(self, node, parent, assign_ctx=None): """visit a Slice node by returning a fresh instance of it""" newnode = new.Slice() newnode.parent = parent if node.lower is not None: - newnode.lower = self.visit(node.lower, newnode) + newnode.lower = self.visit(node.lower, newnode, assign_ctx) if node.upper is not None: - newnode.upper = self.visit(node.upper, newnode) + newnode.upper = self.visit(node.upper, newnode, assign_ctx) if node.step is not None: - newnode.step = self.visit(node.step, newnode) + newnode.step = self.visit(node.step, newnode, assign_ctx) return newnode - def visit_subscript(self, node, parent): + def visit_subscript(self, node, parent, assign_ctx=None): """visit a Subscript node by returning a fresh instance of it""" newnode = new.Subscript() _lineno_parent(node, newnode, parent) - subcontext, self.asscontext = self.asscontext, None - newnode.value = self.visit(node.value, newnode) - newnode.slice = self.visit(node.slice, newnode) - self.asscontext = subcontext + newnode.value = self.visit(node.value, newnode, None) + newnode.slice = self.visit(node.slice, newnode, None) return newnode - def visit_tryexcept(self, node, parent): + def visit_tryexcept(self, node, parent, assign_ctx=None): """visit a TryExcept node by returning a fresh instance of it""" newnode = new.TryExcept() _lineno_parent(node, newnode, parent) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.handlers = [self.visit(child, newnode) for child in node.handlers] - newnode.orelse = [self.visit(child, newnode) for child in node.orelse] + newnode.body = [self.visit(child, newnode, assign_ctx) + for child in node.body] + newnode.handlers = [self.visit(child, newnode, assign_ctx) + for child in node.handlers] + newnode.orelse = [self.visit(child, newnode, assign_ctx) + for child in node.orelse] return newnode - def visit_tryfinally(self, node, parent): + def visit_tryfinally(self, node, parent, assign_ctx=None): """visit a TryFinally node by returning a fresh instance of it""" newnode = new.TryFinally() _lineno_parent(node, newnode, parent) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.finalbody = [self.visit(n, newnode) for n in node.finalbody] + newnode.body = [self.visit(child, newnode, assign_ctx) + for child in node.body] + newnode.finalbody = [self.visit(n, newnode, assign_ctx) + for n in node.finalbody] return newnode - def visit_tuple(self, node, parent): + def visit_tuple(self, node, parent, assign_ctx=None): """visit a Tuple node by returning a fresh instance of it""" newnode = new.Tuple() _lineno_parent(node, newnode, parent) - newnode.elts = [self.visit(child, newnode) for child in node.elts] + newnode.elts = [self.visit(child, newnode, assign_ctx) + for child in node.elts] return newnode - def visit_unaryop(self, node, parent): + def visit_unaryop(self, node, parent, assign_ctx=None): """visit a UnaryOp node by returning a fresh instance of it""" newnode = new.UnaryOp() _lineno_parent(node, newnode, parent) - newnode.operand = self.visit(node.operand, newnode) + newnode.operand = self.visit(node.operand, newnode, assign_ctx) newnode.op = _UNARY_OP_CLASSES[node.op.__class__] return newnode - def visit_while(self, node, parent): + def visit_while(self, node, parent, assign_ctx=None): """visit a While node by returning a fresh instance of it""" newnode = new.While() _lineno_parent(node, newnode, parent) - newnode.test = self.visit(node.test, newnode) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.orelse = [self.visit(child, newnode) for child in node.orelse] + newnode.test = self.visit(node.test, newnode, assign_ctx) + newnode.body = [self.visit(child, newnode, assign_ctx) + for child in node.body] + newnode.orelse = [self.visit(child, newnode, assign_ctx) + for child in node.orelse] return newnode - def visit_with(self, node, parent): + def visit_with(self, node, parent, assign_ctx=None): newnode = new.With() _lineno_parent(node, newnode, parent) - expr = self.visit(node.context_expr, newnode) - self.asscontext = "Ass" + expr = self.visit(node.context_expr, newnode, assign_ctx) if node.optional_vars is not None: - vars = self.visit(node.optional_vars, newnode) + vars = self.visit(node.optional_vars, newnode, 'Assign') else: vars = None self.asscontext = None newnode.items = [(expr, vars)] - newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.body = [self.visit(child, newnode, assign_ctx) + for child in node.body] return newnode - def visit_yield(self, node, parent): + def visit_yield(self, node, parent, assign_ctx=None): """visit a Yield node by returning a fresh instance of it""" return _create_yield_node(node, parent, self, new.Yield) class TreeRebuilder3k(TreeRebuilder): """extend and overwrite TreeRebuilder for python3k""" - def visit_arg(self, node, parent): + def visit_arg(self, node, parent, assign_ctx=None): """visit a arg node by returning a fresh AssName instance""" - # the node is coming from py>=3.0, but we use AssName in py2.x - # XXX or we should instead introduce a Arg node in astroid ? - return self.visit_assname(node, parent, node.arg) + # TODO(cpopa): introduce an Arg node instead of using AssignName. + return self.visit_assignname(node, parent, assign_ctx, node.arg) - def visit_nameconstant(self, node, parent): + def visit_nameconstant(self, node, parent, assign_ctx=None): # in Python 3.4 we have NameConstant for True / False / None newnode = new.Const(node.value) _set_infos(node, newnode, parent) return newnode - def visit_arguments(self, node, parent): - newnode = super(TreeRebuilder3k, self).visit_arguments(node, parent) - self.asscontext = "Ass" - newnode.kwonlyargs = [self.visit(child, newnode) for child in node.kwonlyargs] - self.asscontext = None - newnode.kw_defaults = [self.visit(child, newnode) if child else None for child in node.kw_defaults] + def visit_arguments(self, node, parent, assign_ctx=None): + newnode = super(TreeRebuilder3k, self).visit_arguments(node, parent, assign_ctx) + newnode.kwonlyargs = [self.visit(child, newnode, 'Assign') + for child in node.kwonlyargs] + newnode.kw_defaults = [self.visit(child, newnode, None) + if child else None for child in node.kw_defaults] newnode.annotations = [ - self.visit(arg.annotation, newnode) if arg.annotation else None + self.visit(arg.annotation, newnode, None) if arg.annotation else None for arg in node.args] return newnode - def visit_excepthandler(self, node, parent): + def visit_excepthandler(self, node, parent, assign_ctx=None): """visit an ExceptHandler node by returning a fresh instance of it""" newnode = new.ExceptHandler() _lineno_parent(node, newnode, parent) if node.type is not None: - newnode.type = self.visit(node.type, newnode) + newnode.type = self.visit(node.type, newnode, assign_ctx) if node.name is not None: - newnode.name = self.visit_assname(node, newnode, node.name) - newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.name = self.visit_assignname(node, newnode, 'Assign', node.name) + newnode.body = [self.visit(child, newnode, None) + for child in node.body] return newnode - def visit_nonlocal(self, node, parent): + def visit_nonlocal(self, node, parent, assign_ctx=None): """visit a Nonlocal node and return a new instance of it""" newnode = new.Nonlocal(node.names) _set_infos(node, newnode, parent) return newnode - def visit_raise(self, node, parent): + def visit_raise(self, node, parent, assign_ctx=None): """visit a Raise node by returning a fresh instance of it""" newnode = new.Raise() _lineno_parent(node, newnode, parent) # no traceback; anyway it is not used in Pylint if node.exc is not None: - newnode.exc = self.visit(node.exc, newnode) + newnode.exc = self.visit(node.exc, newnode, assign_ctx) if node.cause is not None: - newnode.cause = self.visit(node.cause, newnode) + newnode.cause = self.visit(node.cause, newnode, assign_ctx) return newnode - def visit_starred(self, node, parent): + def visit_starred(self, node, parent, assign_ctx=None): """visit a Starred node and return a new instance of it""" newnode = new.Starred() _lineno_parent(node, newnode, parent) - newnode.value = self.visit(node.value, newnode) + newnode.value = self.visit(node.value, newnode, assign_ctx) return newnode - def visit_try(self, node, parent): + def visit_try(self, node, parent, assign_ctx=None): # python 3.3 introduce a new Try node replacing TryFinally/TryExcept nodes + # pylint: disable=redefined-variable-type if node.finalbody: newnode = new.TryFinally() _lineno_parent(node, newnode, parent) - newnode.finalbody = [self.visit(n, newnode) for n in node.finalbody] + newnode.finalbody = [self.visit(n, newnode, assign_ctx) + for n in node.finalbody] if node.handlers: excnode = new.TryExcept() _lineno_parent(node, excnode, newnode) - excnode.body = [self.visit(child, excnode) for child in node.body] - excnode.handlers = [self.visit(child, excnode) for child in node.handlers] - excnode.orelse = [self.visit(child, excnode) for child in node.orelse] + excnode.body = [self.visit(child, excnode, assign_ctx) + for child in node.body] + excnode.handlers = [self.visit(child, excnode, assign_ctx) + for child in node.handlers] + excnode.orelse = [self.visit(child, excnode, assign_ctx) + for child in node.orelse] newnode.body = [excnode] else: - newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.body = [self.visit(child, newnode, assign_ctx) + for child in node.body] elif node.handlers: newnode = new.TryExcept() _lineno_parent(node, newnode, parent) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.handlers = [self.visit(child, newnode) for child in node.handlers] - newnode.orelse = [self.visit(child, newnode) for child in node.orelse] + newnode.body = [self.visit(child, newnode, assign_ctx) + for child in node.body] + newnode.handlers = [self.visit(child, newnode, assign_ctx) + for child in node.handlers] + newnode.orelse = [self.visit(child, newnode, assign_ctx) + for child in node.orelse] return newnode - def visit_with(self, node, parent): + def _visit_with(self, cls, node, parent, assign_ctx=None): if 'items' not in node._fields: # python < 3.3 - return super(TreeRebuilder3k, self).visit_with(node, parent) + return super(TreeRebuilder3k, self).visit_with(node, parent, + assign_ctx) - newnode = new.With() + newnode = cls() _lineno_parent(node, newnode, parent) def visit_child(child): expr = self.visit(child.context_expr, newnode) - self.asscontext = 'Ass' if child.optional_vars: - var = self.visit(child.optional_vars, newnode) + var = self.visit(child.optional_vars, newnode, + 'Assign') else: var = None - self.asscontext = None return expr, var newnode.items = [visit_child(child) for child in node.items] - newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.body = [self.visit(child, newnode, None) + for child in node.body] return newnode - def visit_yieldfrom(self, node, parent): + def visit_with(self, node, parent, assign_ctx=None): + return self._visit_with(new.With, node, parent, assign_ctx=assign_ctx) + + def visit_yieldfrom(self, node, parent, assign_ctx=None): return _create_yield_node(node, parent, self, new.YieldFrom) - def visit_class(self, node, parent): - newnode = super(TreeRebuilder3k, self).visit_class(node, parent) + def visit_classdef(self, node, parent, assign_ctx=None): + newnode = super(TreeRebuilder3k, self).visit_classdef(node, parent, assign_ctx) newnode._newstyle = True for keyword in node.keywords: if keyword.arg == 'metaclass': - newnode._metaclass = self.visit(keyword, newnode).value + newnode._metaclass = self.visit(keyword, newnode, assign_ctx).value break return newnode -if sys.version_info >= (3, 0): - TreeRebuilder = TreeRebuilder3k + # Async structs added in Python 3.5 + def visit_asyncfunctiondef(self, node, parent, assign_ctx=None): + return self._visit_functiondef(new.AsyncFunctionDef, node, parent, + assign_ctx=assign_ctx) + def visit_asyncfor(self, node, parent, assign_ctx=None): + return self._visit_for(new.AsyncFor, node, parent, + assign_ctx=assign_ctx) + + def visit_await(self, node, parent, assign_ctx=None): + newnode = new.Await() + newnode.lineno = node.lineno + newnode.col_offset = node.col_offset + newnode.parent = parent + newnode.value = self.visit(node.value, newnode, None) + return newnode + + def visit_asyncwith(self, node, parent, assign_ctx=None): + return self._visit_with(new.AsyncWith, node, parent, + assign_ctx=assign_ctx) + + +if sys.version_info >= (3, 0): + TreeRebuilder = TreeRebuilder3k diff --git a/pymode/libs/astroid/scoped_nodes.py b/pymode/libs/astroid/scoped_nodes.py index ac90f878..2a23f0b6 100644 --- a/pymode/libs/astroid/scoped_nodes.py +++ b/pymode/libs/astroid/scoped_nodes.py @@ -15,40 +15,33 @@ # # You should have received a copy of the GNU Lesser General Public License along # with astroid. If not, see . -"""This module contains the classes for "scoped" node, i.e. which are opening a -new local scope in the language definition : Module, Class, Function (and -Lambda, GenExpr, DictComp and SetComp to some extent). -""" -from __future__ import with_statement -__doctype__ = "restructuredtext en" +""" +This module contains the classes for "scoped" node, i.e. which are opening a +new local scope in the language definition : Module, ClassDef, FunctionDef (and +Lambda, GeneratorExp, DictComp and SetComp to some extent). +""" -import sys +import io +import itertools import warnings -from itertools import chain -try: - from io import BytesIO -except ImportError: - from cStringIO import StringIO as BytesIO import six -from logilab.common.compat import builtins -from logilab.common.decorators import cached, cachedproperty - -from astroid.exceptions import NotFoundError, \ - AstroidBuildingException, InferenceError, ResolveError -from astroid.node_classes import Const, DelName, DelAttr, \ - Dict, From, List, Pass, Raise, Return, Tuple, Yield, YieldFrom, \ - LookupMixIn, const_factory as cf, unpack_infer, CallFunc -from astroid.bases import NodeNG, InferenceContext, Instance, copy_context, \ - YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, \ - BUILTINS -from astroid.mixins import FilterStmtsMixin -from astroid.bases import Statement -from astroid.manager import AstroidManager +import wrapt + +from astroid import bases +from astroid import context as contextmod +from astroid import exceptions +from astroid import manager +from astroid import mixins +from astroid import node_classes +from astroid import decorators as decorators_mod +from astroid import util + +BUILTINS = six.moves.builtins.__name__ ITER_METHODS = ('__iter__', '__getitem__') -PY3K = sys.version_info >= (3, 0) + def _c3_merge(sequences): """Merges MROs in *sequences* to a single MRO using the C3 algorithm. @@ -75,8 +68,10 @@ def _c3_merge(sequences): bases = ["({})".format(", ".join(base.name for base in subsequence)) for subsequence in sequences] - raise ResolveError("Cannot create a consistent method resolution " - "order for bases %s" % ", ".join(bases)) + raise exceptions.InconsistentMroError( + "Cannot create a consistent method resolution " + "order for bases %s" % ", ".join(bases)) + result.append(candidate) # remove the chosen candidate for seq in sequences: @@ -88,59 +83,62 @@ def _verify_duplicates_mro(sequences): for sequence in sequences: names = [node.qname() for node in sequence] if len(names) != len(set(names)): - raise ResolveError('Duplicates found in the mro.') + raise exceptions.DuplicateBasesError('Duplicates found in the mro.') -def remove_nodes(func, cls): - def wrapper(*args, **kwargs): +def remove_nodes(cls): + @wrapt.decorator + def decorator(func, instance, args, kwargs): nodes = [n for n in func(*args, **kwargs) if not isinstance(n, cls)] if not nodes: - raise NotFoundError() + raise exceptions.NotFoundError() return nodes - return wrapper + return decorator def function_to_method(n, klass): - if isinstance(n, Function): + if isinstance(n, FunctionDef): if n.type == 'classmethod': - return BoundMethod(n, klass) + return bases.BoundMethod(n, klass) if n.type != 'staticmethod': - return UnboundMethod(n) + return bases.UnboundMethod(n) return n + def std_special_attributes(self, name, add_locals=True): if add_locals: - locals = self.locals + locals = self._locals else: locals = {} if name == '__name__': - return [cf(self.name)] + locals.get(name, []) + return [node_classes.const_factory(self.name)] + locals.get(name, []) if name == '__doc__': - return [cf(self.doc)] + locals.get(name, []) + return [node_classes.const_factory(self.doc)] + locals.get(name, []) if name == '__dict__': - return [Dict()] + locals.get(name, []) - raise NotFoundError(name) + return [node_classes.Dict()] + locals.get(name, []) + raise exceptions.NotFoundError(name) -MANAGER = AstroidManager() + +MANAGER = manager.AstroidManager() def builtin_lookup(name): """lookup a name into the builtin module return the list of matching statements and the astroid for the builtin module """ - builtin_astroid = MANAGER.ast_from_module(builtins) + builtin_astroid = MANAGER.ast_from_module(six.moves.builtins) if name == '__dict__': return builtin_astroid, () try: - stmts = builtin_astroid.locals[name] + stmts = builtin_astroid._locals[name] except KeyError: stmts = () return builtin_astroid, stmts -# TODO move this Mixin to mixins.py; problem: 'Function' in _scope_lookup -class LocalsDictNodeNG(LookupMixIn, NodeNG): - """ this class provides locals handling common to Module, Function - and Class nodes, including a dict like interface for direct access +# TODO move this Mixin to mixins.py; problem: 'FunctionDef' in _scope_lookup +class LocalsDictNodeNG(node_classes.LookupMixIn, bases.NodeNG): + """ this class provides locals handling common to Module, FunctionDef + and ClassDef nodes, including a dict like interface for direct access to locals information """ @@ -148,6 +146,18 @@ class LocalsDictNodeNG(LookupMixIn, NodeNG): # dictionary of locals with name as key and node defining the local as # value + @property + def locals(self): + util.attribute_to_function_warning('locals', 2.0, 'get_locals') + return self._locals + @locals.setter + def locals(self, _locals): + util.attribute_to_function_warning('locals', 2.0, 'get_locals') + self._locals = _locals + @locals.deleter + def locals(self): + util.attribute_to_function_warning('locals', 2.0, 'get_locals') + del self._locals def qname(self): """return the 'qualified' name of the node, eg module.name, @@ -158,21 +168,20 @@ def qname(self): return '%s.%s' % (self.parent.frame().qname(), self.name) def frame(self): - """return the first parent frame node (i.e. Module, Function or Class) + """return the first parent frame node (i.e. Module, FunctionDef or ClassDef) """ return self def scope(self): """return the first node defining a new scope (i.e. Module, - Function, Class, Lambda but also GenExpr, DictComp and SetComp) + FunctionDef, ClassDef, Lambda but also GeneratorExp, DictComp and SetComp) """ return self - def _scope_lookup(self, node, name, offset=0): """XXX method for interfacing the scope lookup""" try: - stmts = node._filter_stmts(self.locals[name], self, offset) + stmts = node._filter_stmts(self._locals[name], self, offset) except KeyError: stmts = () if stmts: @@ -186,8 +195,6 @@ def _scope_lookup(self, node, name, offset=0): return pscope.scope_lookup(node, name) return builtin_lookup(name) # Module - - def set_local(self, name, stmt): """define in locals ( is the node defining the name) if the node is a Module node (i.e. has globals), add the name to @@ -195,8 +202,8 @@ def set_local(self, name, stmt): if the name is already defined, ignore it """ - #assert not stmt in self.locals.get(name, ()), (self, stmt) - self.locals.setdefault(name, []).append(stmt) + #assert not stmt in self._locals.get(name, ()), (self, stmt) + self._locals.setdefault(name, []).append(stmt) __setitem__ = set_local @@ -212,7 +219,6 @@ def add_local_node(self, child_node, name=None): self._append_node(child_node) self.set_local(name or child_node.name, child_node) - def __getitem__(self, item): """method from the `dict` interface returning the first node associated with the given name in the locals dictionary @@ -221,7 +227,7 @@ def __getitem__(self, item): :param item: the name of the locally defined object :raises KeyError: if the name is not defined """ - return self.locals[item][0] + return self._locals[item][0] def __iter__(self): """method from the `dict` interface returning an iterator on @@ -233,27 +239,24 @@ def keys(self): """method from the `dict` interface returning a tuple containing locally defined names """ - return list(self.locals.keys()) + return list(self._locals.keys()) def values(self): """method from the `dict` interface returning a tuple containing - locally defined nodes which are instance of `Function` or `Class` + locally defined nodes which are instance of `FunctionDef` or `ClassDef` """ return [self[key] for key in self.keys()] def items(self): """method from the `dict` interface returning a list of tuple containing each locally defined name with its associated node, - which is an instance of `Function` or `Class` + which is an instance of `FunctionDef` or `ClassDef` """ return list(zip(self.keys(), self.values())) - def __contains__(self, name): - return name in self.locals - has_key = __contains__ + return name in self._locals -# Module ##################################################################### class Module(LocalsDictNodeNG): _astroid_fields = ('body',) @@ -265,9 +268,9 @@ class Module(LocalsDictNodeNG): # the file from which as been extracted the astroid representation. It may # be None if the representation has been built from a built-in module - file = None + source_file = None # Alternatively, if built from a string/bytes, this can be set - file_bytes = None + source_code = None # encoding of python source file, so we can get unicode out of it (python2 # only) file_encoding = None @@ -279,10 +282,10 @@ class Module(LocalsDictNodeNG): package = None # dictionary of globals with name as key and node defining the global # as value - globals = None + _globals = None # Future imports - future_imports = None + _future_imports = None # names of python special attributes (handled by getattr impl.) special_attributes = set(('__name__', '__doc__', '__file__', '__path__', @@ -294,15 +297,81 @@ def __init__(self, name, doc, pure_python=True): self.name = name self.doc = doc self.pure_python = pure_python - self.locals = self.globals = {} + self._locals = self._globals = {} self.body = [] - self.future_imports = set() + self._future_imports = set() + + # Future deprecation warnings + @property + def file(self): + util.rename_warning('file', 2.0, 'source_file') + return self.source_file + @file.setter + def file(self, source_file): + util.rename_warning('file', 2.0, 'source_file') + self.source_file = source_file + @file.deleter + def file(self): + util.rename_warning('file', 2.0, 'source_file') + del self.source_file + + @property + def path(self): + util.rename_warning('path', 2.0, 'source_file') + return self.source_file + @path.setter + def path(self, source_file): + util.rename_warning('path', 2.0, 'source_file') + self.source_file = source_file + @path.deleter + def path(self): + util.rename_warning('path', 2.0, 'source_file') + del self.source_file + + @property + def file_bytes(self): + util.rename_warning('file_bytes', 2.0, 'source_code') + return self.source_code + @file_bytes.setter + def file_bytes(self, source_code): + util.rename_warning('file_bytes', 2.0, 'source_code') + self.source_code = source_code + @file_bytes.deleter + def file_bytes(self): + util.rename_warning('file_bytes', 2.0, 'source_code') + del self.source_code + + @property + def globals(self): + util.attribute_to_function_warning('globals', 2.0, 'get_locals') + return self._locals + @globals.setter + def globals(self, _globals): + util.attribute_to_function_warning('globals', 2.0, 'get_locals') + self._locals = _globals + @globals.deleter + def globals(self): + util.attribute_to_function_warning('globals', 2.0, 'get_locals') + del self._locals + + @property + def future_imports(self): + util.attribute_to_function_warning('future_imports', 2.0, 'future_imports') + return self._future_imports + @future_imports.setter + def future_imports(self, _future_imports): + util.attribute_to_function_warning('future_imports', 2.0, 'future_imports') + self._future_imports = _future_imports + @future_imports.deleter + def future_imports(self): + util.attribute_to_function_warning('future_imports', 2.0, 'future_imports') + del self._future_imports def _get_stream(self): - if self.file_bytes is not None: - return BytesIO(self.file_bytes) - if self.file is not None: - stream = open(self.file, 'rb') + if self.source_code is not None: + return io.BytesIO(self.source_code) + if self.source_file is not None: + stream = open(self.source_file, 'rb') return stream return None @@ -337,10 +406,10 @@ def block_range(self, lineno): return self.fromlineno, self.tolineno def scope_lookup(self, node, name, offset=0): - if name in self.scope_attrs and not name in self.locals: + if name in self.scope_attrs and name not in self._locals: try: return self, self.getattr(name) - except NotFoundError: + except exceptions.NotFoundError: return self, () return self._scope_lookup(node, name, offset) @@ -350,44 +419,42 @@ def pytype(self): def display_type(self): return 'Module' + @remove_nodes(node_classes.DelName) def getattr(self, name, context=None, ignore_locals=False): if name in self.special_attributes: if name == '__file__': - return [cf(self.file)] + self.locals.get(name, []) + return [node_classes.const_factory(self.source_file)] + self._locals.get(name, []) if name == '__path__' and self.package: - return [List()] + self.locals.get(name, []) + return [node_classes.List()] + self._locals.get(name, []) return std_special_attributes(self, name) - if not ignore_locals and name in self.locals: - return self.locals[name] + if not ignore_locals and name in self._locals: + return self._locals[name] if self.package: try: return [self.import_module(name, relative_only=True)] - except AstroidBuildingException: - raise NotFoundError(name) + except exceptions.AstroidBuildingException: + raise exceptions.NotFoundError(name) except SyntaxError: - raise NotFoundError(name) - except Exception:# XXX pylint tests never pass here; do we need it? - import traceback - traceback.print_exc() - raise NotFoundError(name) - getattr = remove_nodes(getattr, DelName) + raise exceptions.NotFoundError(name) + raise exceptions.NotFoundError(name) def igetattr(self, name, context=None): """inferred getattr""" # set lookup name since this is necessary to infer on import nodes for # instance - context = copy_context(context) + context = contextmod.copy_context(context) context.lookupname = name try: - return _infer_stmts(self.getattr(name, context), context, frame=self) - except NotFoundError: - raise InferenceError(name) + return bases._infer_stmts(self.getattr(name, context), + context, frame=self) + except exceptions.NotFoundError: + raise exceptions.InferenceError(name) def fully_defined(self): """return True if this module has been built from a .py file and so contains a complete representation including the code """ - return self.file is not None and self.file.endswith('.py') + return self.source_file is not None and self.source_file.endswith('.py') def statement(self): """return the first parent node marked as statement node @@ -403,11 +470,11 @@ def next_sibling(self): """module has no sibling""" return - if sys.version_info < (2, 8): - @cachedproperty + if six.PY2: + @decorators_mod.cachedproperty def _absolute_import_activated(self): - for stmt in self.locals.get('absolute_import', ()): - if isinstance(stmt, From) and stmt.modname == '__future__': + for stmt in self._locals.get('absolute_import', ()): + if isinstance(stmt, node_classes.ImportFrom) and stmt.modname == '__future__': return True return False else: @@ -423,7 +490,7 @@ def import_module(self, modname, relative_only=False, level=None): absmodname = self.relative_to_absolute_name(modname, level) try: return MANAGER.ast_from_module_name(absmodname) - except AstroidBuildingException: + except exceptions.AstroidBuildingException: # we only want to import a sub module or package of this module, # skip here if relative_only: @@ -454,7 +521,6 @@ def relative_to_absolute_name(self, modname, level): return '%s.%s' % (package_name, modname) return modname - def wildcard_import_names(self): """return the list of imported names when this module is 'wildcard imported' @@ -462,19 +528,6 @@ def wildcard_import_names(self): It doesn't include the '__builtins__' name which is added by the current CPython implementation of wildcard imports. """ - # take advantage of a living module if it exists - try: - living = sys.modules[self.name] - except KeyError: - pass - else: - try: - return living.__all__ - except AttributeError: - return [name for name in living.__dict__.keys() - if not name.startswith('_')] - # else lookup the astroid - # # We separate the different steps of lookup in try/excepts # to avoid catching too many Exceptions default = [name for name in self.keys() if not name.startswith('_')] @@ -482,9 +535,10 @@ def wildcard_import_names(self): all = self['__all__'] except KeyError: return default + try: explicit = next(all.assigned_stmts()) - except InferenceError: + except exceptions.InferenceError: return default except AttributeError: # not an assignment node @@ -492,28 +546,34 @@ def wildcard_import_names(self): return default # Try our best to detect the exported name. - infered = [] + inferred = [] try: explicit = next(explicit.infer()) - except InferenceError: + except exceptions.InferenceError: return default - if not isinstance(explicit, (Tuple, List)): + if not isinstance(explicit, (node_classes.Tuple, node_classes.List)): return default - str_const = lambda node: (isinstance(node, Const) and + str_const = lambda node: (isinstance(node, node_classes.Const) and isinstance(node.value, six.string_types)) for node in explicit.elts: if str_const(node): - infered.append(node.value) + inferred.append(node.value) else: try: - infered_node = next(node.infer()) - except InferenceError: + inferred_node = next(node.infer()) + except exceptions.InferenceError: continue - if str_const(infered_node): - infered.append(infered_node.value) - return infered + if str_const(inferred_node): + inferred.append(inferred_node.value) + return inferred + def _public_names(self): + """Get the list of the names which are publicly available in this module.""" + return [name for name in self.keys() if not name.startswith('_')] + + def bool_value(self): + return True class ComprehensionScope(LocalsDictNodeNG): @@ -523,11 +583,11 @@ def frame(self): scope_lookup = LocalsDictNodeNG._scope_lookup -class GenExpr(ComprehensionScope): +class GeneratorExp(ComprehensionScope): _astroid_fields = ('elt', 'generators') def __init__(self): - self.locals = {} + self._locals = {} self.elt = None self.generators = [] @@ -536,7 +596,7 @@ class DictComp(ComprehensionScope): _astroid_fields = ('key', 'value', 'generators') def __init__(self): - self.locals = {} + self._locals = {} self.key = None self.value = None self.generators = [] @@ -546,97 +606,53 @@ class SetComp(ComprehensionScope): _astroid_fields = ('elt', 'generators') def __init__(self): - self.locals = {} + self._locals = {} self.elt = None self.generators = [] -class _ListComp(NodeNG): +class _ListComp(bases.NodeNG): """class representing a ListComp node""" _astroid_fields = ('elt', 'generators') elt = None generators = None -if sys.version_info >= (3, 0): + +if six.PY3: class ListComp(_ListComp, ComprehensionScope): """class representing a ListComp node""" def __init__(self): - self.locals = {} + self._locals = {} else: class ListComp(_ListComp): """class representing a ListComp node""" -# Function ################################################################### def _infer_decorator_callchain(node): """Detect decorator call chaining and see if the end result is a static or a classmethod. """ - if not isinstance(node, Function): + if not isinstance(node, FunctionDef): return if not node.parent: return try: - # TODO: We don't handle multiple inference results right now, - # because there's no flow to reason when the return - # is what we are looking for, a static or a class method. - result = next(node.infer_call_result(node.parent)) - except (StopIteration, InferenceError): - return - if isinstance(result, Instance): - result = result._proxied - if isinstance(result, Class): - if result.is_subtype_of('%s.classmethod' % BUILTINS): - return 'classmethod' - if result.is_subtype_of('%s.staticmethod' % BUILTINS): - return 'staticmethod' - - -def _function_type(self): - """ - Function type, possible values are: - method, function, staticmethod, classmethod. - """ - # Can't infer that this node is decorated - # with a subclass of `classmethod` where `type` is first set, - # so do it here. - if self.decorators: - for node in self.decorators.nodes: - if isinstance(node, CallFunc): - # Handle the following case: - # @some_decorator(arg1, arg2) - # def func(...) - # - try: - current = next(node.func.infer()) - except InferenceError: - continue - _type = _infer_decorator_callchain(current) - if _type is not None: - return _type - - try: - for infered in node.infer(): - # Check to see if this returns a static or a class method. - _type = _infer_decorator_callchain(infered) - if _type is not None: - return _type - - if not isinstance(infered, Class): - continue - for ancestor in infered.ancestors(): - if not isinstance(ancestor, Class): - continue - if ancestor.is_subtype_of('%s.classmethod' % BUILTINS): - return 'classmethod' - elif ancestor.is_subtype_of('%s.staticmethod' % BUILTINS): - return 'staticmethod' - except InferenceError: - pass - return self._type + # TODO: We don't handle multiple inference results right now, + # because there's no flow to reason when the return + # is what we are looking for, a static or a class method. + result = next(node.infer_call_result(node.parent)) + except (StopIteration, exceptions.InferenceError): + return + if isinstance(result, bases.Instance): + result = result._proxied + if isinstance(result, ClassDef): + if result.is_subtype_of('%s.classmethod' % BUILTINS): + return 'classmethod' + if result.is_subtype_of('%s.staticmethod' % BUILTINS): + return 'staticmethod' -class Lambda(LocalsDictNodeNG, FilterStmtsMixin): +class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG): _astroid_fields = ('args', 'body',) name = '' @@ -644,7 +660,7 @@ class Lambda(LocalsDictNodeNG, FilterStmtsMixin): type = 'function' def __init__(self): - self.locals = {} + self._locals = {} self.args = [] self.body = [] @@ -689,8 +705,9 @@ def scope_lookup(self, node, name, offset=0): return frame._scope_lookup(node, name, offset) -class Function(Statement, Lambda): - if PY3K: + +class FunctionDef(bases.Statement, Lambda): + if six.PY3: _astroid_fields = ('decorators', 'args', 'body', 'returns') returns = None else: @@ -699,32 +716,137 @@ class Function(Statement, Lambda): special_attributes = set(('__name__', '__doc__', '__dict__')) is_function = True # attributes below are set by the builder module or by raw factories - blockstart_tolineno = None decorators = None - _type = "function" - type = cachedproperty(_function_type) def __init__(self, name, doc): - self.locals = {} + self._locals = {} self.args = [] self.body = [] self.name = name self.doc = doc - self.extra_decorators = [] - self.instance_attrs = {} + self._instance_attrs = {} + + @property + def instance_attrs(self): + util.attribute_to_function_warning('instance_attrs', 2.0, 'get_attributes') + return self._instance_attrs + @instance_attrs.setter + def instance_attrs(self, _instance_attrs): + util.attribute_to_function_warning('instance_attrs', 2.0, 'get_attributes') + self._instance_attrs = _instance_attrs + @instance_attrs.deleter + def instance_attrs(self): + util.attribute_to_function_warning('instance_attrs', 2.0, 'get_attributes') + del self._instance_attrs + + @decorators_mod.cachedproperty + def extra_decorators(self): + """Get the extra decorators that this function can haves + Additional decorators are considered when they are used as + assignments, as in `method = staticmethod(method)`. + The property will return all the callables that are used for + decoration. + """ + frame = self.parent.frame() + if not isinstance(frame, ClassDef): + return [] + + decorators = [] + for assign in frame.nodes_of_class(node_classes.Assign): + if (isinstance(assign.value, node_classes.Call) + and isinstance(assign.value.func, node_classes.Name)): + for assign_node in assign.targets: + if not isinstance(assign_node, node_classes.AssignName): + # Support only `name = callable(name)` + continue + + if assign_node.name != self.name: + # Interested only in the assignment nodes that + # decorates the current method. + continue + try: + meth = frame[self.name] + except KeyError: + continue + else: + # Must be a function and in the same frame as the + # original method. + if (isinstance(meth, FunctionDef) + and assign_node.frame() == frame): + decorators.append(assign.value) + return decorators + + @decorators_mod.cachedproperty + def type(self): + """Get the function type for this node. + + Possible values are: method, function, staticmethod, classmethod. + """ + builtin_descriptors = {'classmethod', 'staticmethod'} + + for decorator in self.extra_decorators: + if decorator.func.name in builtin_descriptors: + return decorator.func.name - @cachedproperty + frame = self.parent.frame() + type_name = 'function' + if isinstance(frame, ClassDef): + if self.name == '__new__': + return 'classmethod' + else: + type_name = 'method' + + if self.decorators: + for node in self.decorators.nodes: + if isinstance(node, node_classes.Name): + if node.name in builtin_descriptors: + return node.name + + if isinstance(node, node_classes.Call): + # Handle the following case: + # @some_decorator(arg1, arg2) + # def func(...) + # + try: + current = next(node.func.infer()) + except exceptions.InferenceError: + continue + _type = _infer_decorator_callchain(current) + if _type is not None: + return _type + + try: + for inferred in node.infer(): + # Check to see if this returns a static or a class method. + _type = _infer_decorator_callchain(inferred) + if _type is not None: + return _type + + if not isinstance(inferred, ClassDef): + continue + for ancestor in inferred.ancestors(): + if not isinstance(ancestor, ClassDef): + continue + if ancestor.is_subtype_of('%s.classmethod' % BUILTINS): + return 'classmethod' + elif ancestor.is_subtype_of('%s.staticmethod' % BUILTINS): + return 'staticmethod' + except exceptions.InferenceError: + pass + return type_name + + @decorators_mod.cachedproperty def fromlineno(self): # lineno is the line number of the first decorator, we want the def # statement lineno lineno = self.lineno if self.decorators is not None: lineno += sum(node.tolineno - node.lineno + 1 - for node in self.decorators.nodes) + for node in self.decorators.nodes) return lineno - @cachedproperty + @decorators_mod.cachedproperty def blockstart_tolineno(self): return self.args.tolineno @@ -740,29 +862,41 @@ def getattr(self, name, context=None): done by an Instance proxy at inference time. """ if name == '__module__': - return [cf(self.root().qname())] - if name in self.instance_attrs: - return self.instance_attrs[name] + return [node_classes.const_factory(self.root().qname())] + if name in self._instance_attrs: + return self._instance_attrs[name] return std_special_attributes(self, name, False) + def igetattr(self, name, context=None): + """Inferred getattr, which returns an iterator of inferred statements.""" + try: + return bases._infer_stmts(self.getattr(name, context), + context, frame=self) + except exceptions.NotFoundError: + raise exceptions.InferenceError(name) + def is_method(self): """return true if the function node should be considered as a method""" - # check we are defined in a Class, because this is usually expected + # check we are defined in a ClassDef, because this is usually expected # (e.g. pylint...) when is_method() return True - return self.type != 'function' and isinstance(self.parent.frame(), Class) + return self.type != 'function' and isinstance(self.parent.frame(), ClassDef) + @decorators_mod.cached def decoratornames(self): """return a list of decorator qualified names""" result = set() decoratornodes = [] if self.decorators is not None: + # pylint: disable=unsupported-binary-operation; damn flow control. decoratornodes += self.decorators.nodes decoratornodes += self.extra_decorators for decnode in decoratornodes: - for infnode in decnode.infer(): - result.add(infnode.qname()) + try: + for infnode in decnode.infer(): + result.add(infnode.qname()) + except exceptions.InferenceError: + continue return result - decoratornames = cached(decoratornames) def is_bound(self): """return true if the function is bound to an Instance or a class""" @@ -779,34 +913,34 @@ def is_abstract(self, pass_is_abstract=True): if self.decorators: for node in self.decorators.nodes: try: - infered = next(node.infer()) - except InferenceError: + inferred = next(node.infer()) + except exceptions.InferenceError: continue - if infered and infered.qname() in ('abc.abstractproperty', - 'abc.abstractmethod'): + if inferred and inferred.qname() in ('abc.abstractproperty', + 'abc.abstractmethod'): return True for child_node in self.body: - if isinstance(child_node, Raise): + if isinstance(child_node, node_classes.Raise): if child_node.raises_not_implemented(): return True - if pass_is_abstract and isinstance(child_node, Pass): - return True - return False + return pass_is_abstract and isinstance(child_node, node_classes.Pass) # empty function is the same as function with a single "pass" statement if pass_is_abstract: return True def is_generator(self): """return true if this is a generator function""" - # XXX should be flagged, not computed - return next(self.nodes_of_class((Yield, YieldFrom), - skip_klass=(Function, Lambda)), False) + yield_nodes = (node_classes.Yield, node_classes.YieldFrom) + return next(self.nodes_of_class(yield_nodes, + skip_klass=(FunctionDef, Lambda)), False) def infer_call_result(self, caller, context=None): """infer what a function is returning when called""" if self.is_generator(): - yield Generator() + result = bases.Generator() + result.parent = self + yield result return # This is really a gigantic hack to work around metaclass generators # that return transient class-generating functions. Pylint's AST structure @@ -818,25 +952,29 @@ def infer_call_result(self, caller, context=None): len(self.args.args) == 1 and self.args.vararg is not None): metaclass = next(caller.args[0].infer(context)) - if isinstance(metaclass, Class): - c = Class('temporary_class', None) + if isinstance(metaclass, ClassDef): + c = ClassDef('temporary_class', None) c.hide = True c.parent = self - bases = [next(b.infer(context)) for b in caller.args[1:]] - c.bases = [base for base in bases if base != YES] + class_bases = [next(b.infer(context)) for b in caller.args[1:]] + c.bases = [base for base in class_bases if base != util.YES] c._metaclass = metaclass yield c return - returns = self.nodes_of_class(Return, skip_klass=Function) + returns = self.nodes_of_class(node_classes.Return, skip_klass=FunctionDef) for returnnode in returns: if returnnode.value is None: - yield Const(None) + yield node_classes.Const(None) else: try: - for infered in returnnode.value.infer(context): - yield infered - except InferenceError: - yield YES + for inferred in returnnode.value.infer(context): + yield inferred + except exceptions.InferenceError: + yield util.YES + + +class AsyncFunctionDef(FunctionDef): + """Asynchronous function created with the `async` keyword.""" def _rec_get_names(args, names=None): @@ -844,16 +982,13 @@ def _rec_get_names(args, names=None): if names is None: names = [] for arg in args: - if isinstance(arg, Tuple): + if isinstance(arg, node_classes.Tuple): _rec_get_names(arg.elts, names) else: names.append(arg.name) return names -# Class ###################################################################### - - def _is_metaclass(klass, seen=None): """ Return if the given class can be used as a metaclass. @@ -865,30 +1000,31 @@ def _is_metaclass(klass, seen=None): for base in klass.bases: try: for baseobj in base.infer(): - if baseobj in seen: + baseobj_name = baseobj.qname() + if baseobj_name in seen: continue else: - seen.add(baseobj) - if isinstance(baseobj, Instance): + seen.add(baseobj_name) + if isinstance(baseobj, bases.Instance): # not abstract return False - if baseobj is YES: + if baseobj is util.YES: continue if baseobj is klass: continue - if not isinstance(baseobj, Class): + if not isinstance(baseobj, ClassDef): continue if baseobj._type == 'metaclass': return True if _is_metaclass(baseobj, seen): return True - except InferenceError: + except exceptions.InferenceError: continue return False def _class_type(klass, ancestors=None): - """return a Class node type to differ metaclass, interface and exception + """return a ClassDef node type to differ metaclass and exception from 'regular' classes """ # XXX we have to store ancestors in case we have a ancestor loop @@ -896,18 +1032,17 @@ def _class_type(klass, ancestors=None): return klass._type if _is_metaclass(klass): klass._type = 'metaclass' - elif klass.name.endswith('Interface'): - klass._type = 'interface' elif klass.name.endswith('Exception'): klass._type = 'exception' else: if ancestors is None: ancestors = set() - if klass in ancestors: + klass_name = klass.qname() + if klass_name in ancestors: # XXX we are in loop ancestors, and have found no type klass._type = 'class' return 'class' - ancestors.add(klass) + ancestors.add(klass_name) for base in klass.ancestors(recurs=False): name = _class_type(base, ancestors) if name != 'class': @@ -921,14 +1056,8 @@ def _class_type(klass, ancestors=None): klass._type = 'class' return klass._type -def _iface_hdlr(iface_node): - """a handler function used by interfaces to handle suspicious - interface nodes - """ - return True - -class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): +class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, bases.Statement): # some of the attributes below are set by the builder module or # by a raw factories @@ -939,26 +1068,38 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): decorators = None special_attributes = set(('__name__', '__doc__', '__dict__', '__module__', '__bases__', '__mro__', '__subclasses__')) - blockstart_tolineno = None _type = None _metaclass_hack = False hide = False type = property(_class_type, doc="class'type, possible values are 'class' | " - "'metaclass' | 'interface' | 'exception'") + "'metaclass' | 'exception'") def __init__(self, name, doc): - self.instance_attrs = {} - self.locals = {} + self._instance_attrs = {} + self._locals = {} self.bases = [] self.body = [] self.name = name self.doc = doc + @property + def instance_attrs(self): + util.attribute_to_function_warning('instance_attrs', 2.0, 'get_attributes') + return self._instance_attrs + @instance_attrs.setter + def instance_attrs(self, _instance_attrs): + util.attribute_to_function_warning('instance_attrs', 2.0, 'get_attributes') + self._instance_attrs = _instance_attrs + @instance_attrs.deleter + def instance_attrs(self): + util.attribute_to_function_warning('instance_attrs', 2.0, 'get_attributes') + del self._instance_attrs + def _newstyle_impl(self, context=None): if context is None: - context = InferenceContext() + context = contextmod.InferenceContext() if self._newstyle is not None: return self._newstyle for base in self.ancestors(recurs=False, context=context): @@ -968,7 +1109,7 @@ def _newstyle_impl(self, context=None): klass = self._explicit_metaclass() # could be any callable, we'd need to infer the result of klass(name, # bases, dict). punt if it's not a class node. - if klass is not None and isinstance(klass, Class): + if klass is not None and isinstance(klass, ClassDef): self._newstyle = klass._newstyle_impl(context) if self._newstyle is None: self._newstyle = False @@ -979,7 +1120,7 @@ def _newstyle_impl(self, context=None): doc="boolean indicating if it's a new style class" "or not") - @cachedproperty + @decorators_mod.cachedproperty def blockstart_tolineno(self): if self.bases: return self.bases[-1].tolineno @@ -1011,32 +1152,52 @@ def is_subtype_of(self, type_name, context=None): if anc.qname() == type_name: return True + def _infer_type_call(self, caller, context): + name_node = next(caller.args[0].infer(context)) + if (isinstance(name_node, node_classes.Const) and + isinstance(name_node.value, six.string_types)): + name = name_node.value + else: + return util.YES + + result = ClassDef(name, None) + + # Get the bases of the class. + class_bases = next(caller.args[1].infer(context)) + if isinstance(class_bases, (node_classes.Tuple, node_classes.List)): + result.bases = class_bases.itered() + else: + # There is currently no AST node that can represent an 'unknown' + # node (YES is not an AST node), therefore we simply return YES here + # although we know at least the name of the class. + return util.YES + + # Get the members of the class + try: + members = next(caller.args[2].infer(context)) + except exceptions.InferenceError: + members = None + + if members and isinstance(members, node_classes.Dict): + for attr, value in members.items: + if (isinstance(attr, node_classes.Const) and + isinstance(attr.value, six.string_types)): + result._locals[attr.value] = [value] + + result.parent = caller.parent + return result + def infer_call_result(self, caller, context=None): """infer what a class is returning when called""" - if self.is_subtype_of('%s.type' % (BUILTINS,), context) and len(caller.args) == 3: - name_node = next(caller.args[0].infer(context)) - if (isinstance(name_node, Const) and - isinstance(name_node.value, six.string_types)): - name = name_node.value - else: - yield YES - return - result = Class(name, None) - bases = next(caller.args[1].infer(context)) - if isinstance(bases, (Tuple, List)): - result.bases = bases.itered() - else: - # There is currently no AST node that can represent an 'unknown' - # node (YES is not an AST node), therefore we simply return YES here - # although we know at least the name of the class. - yield YES - return - result.parent = caller.parent + if (self.is_subtype_of('%s.type' % (BUILTINS,), context) + and len(caller.args) == 3): + result = self._infer_type_call(caller, context) yield result else: - yield Instance(self) + yield bases.Instance(self) def scope_lookup(self, node, name, offset=0): + # pylint: disable=redefined-variable-type if any(node == base or base.parent_of(node) for base in self.bases): # Handle the case where we have either a name @@ -1060,11 +1221,10 @@ def scope_lookup(self, node, name, offset=0): frame = self return frame._scope_lookup(node, name, offset) - # list of parent class as a list of string (i.e. names as they appear - # in the class definition) XXX bw compat + @property def basenames(self): + """Get the list of parent class names, as they appear in the class definition.""" return [bnode.as_string() for bnode in self.bases] - basenames = property(basenames) def ancestors(self, recurs=True, context=None): """return an iterator on the node base classes in a prefixed @@ -1078,8 +1238,8 @@ def ancestors(self, recurs=True, context=None): # FIXME: inference make infinite loops possible here yielded = set([self]) if context is None: - context = InferenceContext() - if sys.version_info[0] >= 3: + context = contextmod.InferenceContext() + if six.PY3: if not self.bases and self.qname() != 'builtins.object': yield builtin_lookup("object")[1][0] return @@ -1088,15 +1248,14 @@ def ancestors(self, recurs=True, context=None): with context.restore_path(): try: for baseobj in stmt.infer(context): - if not isinstance(baseobj, Class): - if isinstance(baseobj, Instance): + if not isinstance(baseobj, ClassDef): + if isinstance(baseobj, bases.Instance): baseobj = baseobj._proxied else: - # duh ? continue if not baseobj.hide: if baseobj in yielded: - continue # cf xxx above + continue yielded.add(baseobj) yield baseobj if recurs: @@ -1106,18 +1265,28 @@ def ancestors(self, recurs=True, context=None): # This class is the ancestor of itself. break if grandpa in yielded: - continue # cf xxx above + continue yielded.add(grandpa) yield grandpa - except InferenceError: - # XXX log error ? + except exceptions.InferenceError: continue def local_attr_ancestors(self, name, context=None): """return an iterator on astroid representation of parent classes which have defined in their locals """ - for astroid in self.ancestors(context=context): + if self.newstyle and all(n.newstyle for n in self.ancestors(context)): + # Look up in the mro if we can. This will result in the + # attribute being looked up just as Python does it. + try: + ancestors = self.mro(context)[1:] + except exceptions.MroError: + # Fallback to use ancestors, we can't determine + # a sane MRO. + ancestors = self.ancestors(context=context) + else: + ancestors = self.ancestors(context=context) + for astroid in ancestors: if name in astroid: yield astroid @@ -1126,12 +1295,13 @@ def instance_attr_ancestors(self, name, context=None): which have defined in their instance attribute dictionary """ for astroid in self.ancestors(context=context): - if name in astroid.instance_attrs: + if name in astroid._instance_attrs: yield astroid def has_base(self, node): return node in self.bases + @remove_nodes(node_classes.DelAttr) def local_attr(self, name, context=None): """return the list of assign node associated to name in this class locals or in its parents @@ -1141,14 +1311,13 @@ def local_attr(self, name, context=None): its parent classes """ try: - return self.locals[name] + return self._locals[name] except KeyError: - # get if from the first parent implementing it if any for class_node in self.local_attr_ancestors(name, context): - return class_node.locals[name] - raise NotFoundError(name) - local_attr = remove_nodes(local_attr, DelAttr) + return class_node._locals[name] + raise exceptions.NotFoundError(name) + @remove_nodes(node_classes.DelAttr) def instance_attr(self, name, context=None): """return the astroid nodes associated to name in this class instance attributes dictionary and in its parents @@ -1157,20 +1326,24 @@ def instance_attr(self, name, context=None): if no attribute with this name has been find in this class or its parent classes """ - # Return a copy, so we don't modify self.instance_attrs, + # Return a copy, so we don't modify self._instance_attrs, # which could lead to infinite loop. - values = list(self.instance_attrs.get(name, [])) + values = list(self._instance_attrs.get(name, [])) # get all values from parents for class_node in self.instance_attr_ancestors(name, context): - values += class_node.instance_attrs[name] + values += class_node._instance_attrs[name] if not values: - raise NotFoundError(name) + raise exceptions.NotFoundError(name) return values - instance_attr = remove_nodes(instance_attr, DelAttr) + + def instantiate_class(self): + """return Instance of ClassDef node, else return self""" + return bases.Instance(self) def instanciate_class(self): - """return Instance of Class node, else return self""" - return Instance(self) + """return Instance of ClassDef node, else return self""" + util.rename_warning('instanciate_class()', 2.0, 'instantiate_class()') + return self.instantiate_class() def getattr(self, name, context=None): """this method doesn't look in the instance_attrs dictionary since it's @@ -1179,25 +1352,27 @@ def getattr(self, name, context=None): It may return a YES object if the attribute has not been actually found but a __getattr__ or __getattribute__ method is defined """ - values = self.locals.get(name, []) + values = self._locals.get(name, []) if name in self.special_attributes: if name == '__module__': - return [cf(self.root().qname())] + values - # FIXME: do we really need the actual list of ancestors? - # returning [Tuple()] + values don't break any test - # this is ticket http://www.logilab.org/ticket/52785 - # XXX need proper meta class handling + MRO implementation - if name == '__bases__' or (name == '__mro__' and self.newstyle): - node = Tuple() - node.items = self.ancestors(recurs=True, context=context) + return [node_classes.const_factory(self.root().qname())] + values + if name == '__bases__': + node = node_classes.Tuple() + elts = list(self._inferred_bases(context)) + node.elts = elts return [node] + values + if name == '__mro__' and self.newstyle: + mro = self.mro() + node = node_classes.Tuple() + node.elts = mro + return [node] return std_special_attributes(self, name) - # don't modify the list in self.locals! + # don't modify the list in self._locals! values = list(values) for classnode in self.ancestors(recurs=True, context=context): - values += classnode.locals.get(name, []) + values += classnode._locals.get(name, []) if not values: - raise NotFoundError(name) + raise exceptions.NotFoundError(name) return values def igetattr(self, name, context=None): @@ -1206,46 +1381,50 @@ def igetattr(self, name, context=None): """ # set lookup name since this is necessary to infer on import nodes for # instance - context = copy_context(context) + context = contextmod.copy_context(context) context.lookupname = name try: - for infered in _infer_stmts(self.getattr(name, context), context, - frame=self): + for inferred in bases._infer_stmts(self.getattr(name, context), + context, frame=self): # yield YES object instead of descriptors when necessary - if not isinstance(infered, Const) and isinstance(infered, Instance): + if (not isinstance(inferred, node_classes.Const) + and isinstance(inferred, bases.Instance)): try: - infered._proxied.getattr('__get__', context) - except NotFoundError: - yield infered + inferred._proxied.getattr('__get__', context) + except exceptions.NotFoundError: + yield inferred else: - yield YES + yield util.YES else: - yield function_to_method(infered, self) - except NotFoundError: + yield function_to_method(inferred, self) + except exceptions.NotFoundError: if not name.startswith('__') and self.has_dynamic_getattr(context): # class handle some dynamic attributes, return a YES object - yield YES + yield util.YES else: - raise InferenceError(name) + raise exceptions.InferenceError(name) def has_dynamic_getattr(self, context=None): - """return True if the class has a custom __getattr__ or - __getattribute__ method """ - # need to explicitly handle optparse.Values (setattr is not detected) - if self.name == 'Values' and self.root().name == 'optparse': - return True + Check if the current instance has a custom __getattr__ + or a custom __getattribute__. + + If any such method is found and it is not from + builtins, nor from an extension module, then the function + will return True. + """ + def _valid_getattr(node): + root = node.root() + return root.name != BUILTINS and getattr(root, 'pure_python', None) + try: - self.getattr('__getattr__', context) - return True - except NotFoundError: + return _valid_getattr(self.getattr('__getattr__', context)[0]) + except exceptions.NotFoundError: #if self.newstyle: XXX cause an infinite recursion error try: getattribute = self.getattr('__getattribute__', context)[0] - if getattribute.root().name != BUILTINS: - # class has a custom __getattribute__ defined - return True - except NotFoundError: + return _valid_getattr(getattribute) + except exceptions.NotFoundError: pass return False @@ -1254,7 +1433,7 @@ def methods(self): its ancestors """ done = {} - for astroid in chain(iter((self,)), self.ancestors()): + for astroid in itertools.chain(iter((self,)), self.ancestors()): for meth in astroid.mymethods(): if meth.name in done: continue @@ -1264,31 +1443,19 @@ def methods(self): def mymethods(self): """return an iterator on all methods defined in the class""" for member in self.values(): - if isinstance(member, Function): + if isinstance(member, FunctionDef): yield member - def interfaces(self, herited=True, handler_func=_iface_hdlr): - """return an iterator on interfaces implemented by the given - class node + def implicit_metaclass(self): + """Get the implicit metaclass of the current class + + For newstyle classes, this will return an instance of builtins.type. + For oldstyle classes, it will simply return None, since there's + no implicit metaclass there. """ - # FIXME: what if __implements__ = (MyIFace, MyParent.__implements__)... - try: - implements = Instance(self).getattr('__implements__')[0] - except NotFoundError: - return - if not herited and not implements.frame() is self: - return - found = set() - missing = False - for iface in unpack_infer(implements): - if iface is YES: - missing = True - continue - if not iface in found and handler_func(iface): - found.add(iface) - yield iface - if missing: - raise InferenceError() + + if self.newstyle: + return builtin_lookup('type')[1][0] _metaclass = None def _explicit_metaclass(self): @@ -1304,29 +1471,29 @@ def _explicit_metaclass(self): for base in self.bases: try: for baseobj in base.infer(): - if isinstance(baseobj, Class) and baseobj.hide: + if isinstance(baseobj, ClassDef) and baseobj.hide: self._metaclass = baseobj._metaclass self._metaclass_hack = True break - except InferenceError: + except exceptions.InferenceError: pass if self._metaclass: # Expects this from Py3k TreeRebuilder try: return next(node for node in self._metaclass.infer() - if node is not YES) - except (InferenceError, StopIteration): + if node is not util.YES) + except (exceptions.InferenceError, StopIteration): return None - if sys.version_info >= (3, ): + if six.PY3: return None - if '__metaclass__' in self.locals: - assignment = self.locals['__metaclass__'][-1] + if '__metaclass__' in self._locals: + assignment = self._locals['__metaclass__'][-1] elif self.bases: return None - elif '__metaclass__' in self.root().locals: - assignments = [ass for ass in self.root().locals['__metaclass__'] + elif '__metaclass__' in self.root()._locals: + assignments = [ass for ass in self.root()._locals['__metaclass__'] if ass.lineno < self.lineno] if not assignments: return None @@ -1335,34 +1502,42 @@ def _explicit_metaclass(self): return None try: - infered = next(assignment.infer()) - except InferenceError: + inferred = next(assignment.infer()) + except exceptions.InferenceError: return - if infered is YES: # don't expose this + if inferred is util.YES: # don't expose this return None - return infered + return inferred + + def _find_metaclass(self, seen=None): + if seen is None: + seen = set() + seen.add(self) + + klass = self._explicit_metaclass() + if klass is None: + for parent in self.ancestors(): + if parent not in seen: + klass = parent._find_metaclass(seen) + if klass is not None: + break + return klass def metaclass(self): - """ Return the metaclass of this class. + """Return the metaclass of this class. If this class does not define explicitly a metaclass, then the first defined metaclass in ancestors will be used instead. """ - klass = self._explicit_metaclass() - if klass is None: - for parent in self.ancestors(): - klass = parent.metaclass() - if klass is not None: - break - return klass + return self._find_metaclass() def has_metaclass_hack(self): return self._metaclass_hack def _islots(self): """ Return an iterator with the inferred slots. """ - if '__slots__' not in self.locals: + if '__slots__' not in self._locals: return for slots in self.igetattr('__slots__'): # check if __slots__ is a valid type @@ -1370,12 +1545,12 @@ def _islots(self): try: slots.getattr(meth) break - except NotFoundError: + except exceptions.NotFoundError: continue else: continue - if isinstance(slots, Const): + if isinstance(slots, node_classes.Const): # a string. Ignore the following checks, # but yield the node, only if it has a value if slots.value: @@ -1385,30 +1560,50 @@ def _islots(self): # we can't obtain the values, maybe a .deque? continue - if isinstance(slots, Dict): + if isinstance(slots, node_classes.Dict): values = [item[0] for item in slots.items] else: values = slots.itered() - if values is YES: + if values is util.YES: continue + if not values: + # Stop the iteration, because the class + # has an empty list of slots. + raise StopIteration(values) for elt in values: try: - for infered in elt.infer(): - if infered is YES: + for inferred in elt.infer(): + if inferred is util.YES: continue - if (not isinstance(infered, Const) or - not isinstance(infered.value, + if (not isinstance(inferred, node_classes.Const) or + not isinstance(inferred.value, six.string_types)): continue - if not infered.value: + if not inferred.value: continue - yield infered - except InferenceError: + yield inferred + except exceptions.InferenceError: continue + def _slots(self): + if not self.newstyle: + raise NotImplementedError( + "The concept of slots is undefined for old-style classes.") + + slots = self._islots() + try: + first = next(slots) + except StopIteration as exc: + # The class doesn't have a __slots__ definition or empty slots. + if exc.args and exc.args[0] not in ('', None): + return exc.args[0] + return None + # pylint: disable=unsupported-binary-operation; false positive + return [first] + list(slots) + # Cached, because inferring them all the time is expensive - @cached + @decorators_mod.cached def slots(self): """Get all the slots for this node. @@ -1417,19 +1612,30 @@ def slots(self): Also, it will return None in the case the slots weren't inferred. Otherwise, it will return a list of slot names. """ + def grouped_slots(): + # Not interested in object, since it can't have slots. + for cls in self.mro()[:-1]: + try: + cls_slots = cls._slots() + except NotImplementedError: + continue + if cls_slots is not None: + for slot in cls_slots: + yield slot + else: + yield None + if not self.newstyle: raise NotImplementedError( "The concept of slots is undefined for old-style classes.") - slots = self._islots() - try: - first = next(slots) - except StopIteration: - # The class doesn't have a __slots__ definition. + slots = list(grouped_slots()) + if not all(slot is not None for slot in slots): return None - return [first] + list(slots) - def _inferred_bases(self, recurs=True, context=None): + return sorted(slots, key=lambda item: item.value) + + def _inferred_bases(self, context=None): # TODO(cpopa): really similar with .ancestors, # but the difference is when one base is inferred, # only the first object is wanted. That's because @@ -1445,8 +1651,8 @@ def _inferred_bases(self, recurs=True, context=None): # only in SomeClass. if context is None: - context = InferenceContext() - if sys.version_info[0] >= 3: + context = contextmod.InferenceContext() + if six.PY3: if not self.bases and self.qname() != 'builtins.object': yield builtin_lookup("object")[1][0] return @@ -1454,15 +1660,17 @@ def _inferred_bases(self, recurs=True, context=None): for stmt in self.bases: try: baseobj = next(stmt.infer(context=context)) - except InferenceError: - # XXX log error ? + except exceptions.InferenceError: continue - if isinstance(baseobj, Instance): + if isinstance(baseobj, bases.Instance): baseobj = baseobj._proxied - if not isinstance(baseobj, Class): + if not isinstance(baseobj, ClassDef): continue if not baseobj.hide: yield baseobj + else: + for base in baseobj.bases: + yield base def mro(self, context=None): """Get the method resolution order, using C3 linearization. @@ -1476,9 +1684,33 @@ def mro(self, context=None): "Could not obtain mro for old-style classes.") bases = list(self._inferred_bases(context=context)) - unmerged_mro = ([[self]] + - [base.mro() for base in bases if base is not self] + - [bases]) - + bases_mro = [] + for base in bases: + try: + mro = base.mro(context=context) + bases_mro.append(mro) + except NotImplementedError: + # Some classes have in their ancestors both newstyle and + # old style classes. For these we can't retrieve the .mro, + # although in Python it's possible, since the class we are + # currently working is in fact new style. + # So, we fallback to ancestors here. + ancestors = list(base.ancestors(context=context)) + bases_mro.append(ancestors) + + unmerged_mro = ([[self]] + bases_mro + [bases]) _verify_duplicates_mro(unmerged_mro) return _c3_merge(unmerged_mro) + +def get_locals(node): + '''Stub function for forwards compatibility.''' + return node._locals + +def get_attributes(node): + '''Stub function for forwards compatibility.''' + return node._instance_attrs + +# Backwards-compatibility aliases +Class = node_classes.proxy_alias('Class', ClassDef) +Function = node_classes.proxy_alias('Function', FunctionDef) +GenExpr = node_classes.proxy_alias('GenExpr', GeneratorExp) diff --git a/pymode/libs/astroid/test_utils.py b/pymode/libs/astroid/test_utils.py index 19bd7b96..9e45abcf 100644 --- a/pymode/libs/astroid/test_utils.py +++ b/pymode/libs/astroid/test_utils.py @@ -1,7 +1,6 @@ """Utility functions for test code that uses astroid ASTs as input.""" import functools import sys -import textwrap from astroid import nodes from astroid import builder @@ -14,7 +13,6 @@ # when calling extract_node. _STATEMENT_SELECTOR = '#@' - def _extract_expressions(node): """Find expressions in a call to _TRANSIENT_FUNCTION and extract them. @@ -28,7 +26,7 @@ def _extract_expressions(node): :yields: The sequence of wrapped expressions on the modified tree expression can be found. """ - if (isinstance(node, nodes.CallFunc) + if (isinstance(node, nodes.Call) and isinstance(node.func, nodes.Name) and node.func.name == _TRANSIENT_FUNCTION): real_expr = node.args[0] @@ -68,7 +66,7 @@ def _find_statement_by_line(node, line): can be found. :rtype: astroid.bases.NodeNG or None """ - if isinstance(node, (nodes.Class, nodes.Function)): + if isinstance(node, (nodes.ClassDef, nodes.FunctionDef)): # This is an inaccuracy in the AST: the nodes that can be # decorated do not carry explicit information on which line # the actual definition (class/def), but .fromline seems to @@ -142,7 +140,7 @@ def extract_node(code, module_name=''): :rtype: astroid.bases.NodeNG, or a list of nodes. """ def _extract(node): - if isinstance(node, nodes.Discard): + if isinstance(node, nodes.Expr): return node.value else: return node @@ -152,7 +150,7 @@ def _extract(node): if line.strip().endswith(_STATEMENT_SELECTOR): requested_lines.append(idx + 1) - tree = build_module(code, module_name=module_name) + tree = builder.parse(code, module_name=module_name) extracted = [] if requested_lines: for line in requested_lines: @@ -171,21 +169,6 @@ def _extract(node): return extracted -def build_module(code, module_name='', path=None): - """Parses a string module with a builder. - :param code: The code for the module. - :type code: str - :param module_name: The name for the module - :type module_name: str - :param path: The path for the module - :type module_name: str - :returns: The module AST. - :rtype: astroid.bases.NodeNG - """ - code = textwrap.dedent(code) - return builder.AstroidBuilder(None).string_build(code, modname=module_name, path=path) - - def require_version(minver=None, maxver=None): """ Compare version of python interpreter to the given one. Skip the test if older. diff --git a/pymode/libs/astroid/tests/__init__.py b/pymode/libs/astroid/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymode/libs/astroid/tests/resources.py b/pymode/libs/astroid/tests/resources.py new file mode 100644 index 00000000..7988d053 --- /dev/null +++ b/pymode/libs/astroid/tests/resources.py @@ -0,0 +1,72 @@ +# Copyright 2014 Google, Inc. All rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +import os +import sys + +import pkg_resources + +from astroid import builder +from astroid import MANAGER +from astroid.bases import BUILTINS + + +DATA_DIR = 'testdata/python{}/'.format(sys.version_info[0]) + +def find(name): + return pkg_resources.resource_filename( + 'astroid.tests', os.path.normpath(os.path.join(DATA_DIR, name))) + + +def build_file(path, modname=None): + return builder.AstroidBuilder().file_build(find(path), modname) + + +class SysPathSetup(object): + def setUp(self): + sys.path.insert(0, find('')) + + def tearDown(self): + del sys.path[0] + datadir = find('') + for key in list(sys.path_importer_cache): + if key.startswith(datadir): + del sys.path_importer_cache[key] + + +class AstroidCacheSetupMixin(object): + """Mixin for handling the astroid cache problems. + + When clearing the astroid cache, some tests fails due to + cache inconsistencies, where some objects had a different + builtins object referenced. + This saves the builtins module and makes sure to add it + back to the astroid_cache after the tests finishes. + The builtins module is special, since some of the + transforms for a couple of its objects (str, bytes etc) + are executed only once, so astroid_bootstrapping will be + useless for retrieving the original builtins module. + """ + + @classmethod + def setUpClass(cls): + cls._builtins = MANAGER.astroid_cache.get(BUILTINS) + + @classmethod + def tearDownClass(cls): + if cls._builtins: + MANAGER.astroid_cache[BUILTINS] = cls._builtins diff --git a/pymode/libs/astroid/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.egg b/pymode/libs/astroid/tests/testdata/python2/data/MyPyPa-0.1.0-py2.5.egg new file mode 100644 index 0000000000000000000000000000000000000000..f62599c7b10469b9eadabb31e9a42128a769c7cd GIT binary patch literal 1222 zcmWIWW@Zs#U|`^2V0WyvGL3P1+yvw;0%Bnx&aEt{EJ)OkkI&4@EQycTE2vDq{ik!f z_N!x_=Tq1;5?GSh74)7l32E~11UgKbs2I`Asd9`*h35;;UlWH_UOO6HYHA|00t?gX z;%fG=d001TTw@jTTsqTI^OvXQ%%iGRmNP4titd`3CYJVV<;$19c1~rT%G&wsg1;w&+sQ$d~(!s_JietmCU zt#fD2clU>H2n{g5U>x$C3CR?Y$GxZZOpXTX?t_}->h7-V>F4IJAM76*_t<%}h{;@`zS&LcdYtZG(rN*BxefrA0=WeO(-#dQ{Rhs@f zH_wS}{_3UWW$+{@h&$+WP|)W|+K-EkK5y#YsHJsMzvH~8uJ>8Sljx37u41p@1UiHr zh(TV1JEkPRAU-FxEHww@oYQM{R_J&&P!l5%%OYz|Ni9gtOG(X3u8hyg z%*!qYneiB(Zb4+-Rhb34#ffRD7&;CGJDSu1Rc;4j6deKHkRbf*tLy3GspENt7ZMAb zgAA@1KltQ*#&>JbhqXK_cs!mootAjf_@v3ZxLCMbYpqDoC(%!zyp4=LU)pK&sW`Zl zTj+A*ET_MF{{A`qcZZC(x6!BW3qNg1;w&+sQ$d~(!s_JietmCU zt#fD2clU>H2n{g5U>x$C3CR?Y$GxZZOpXTX?t_}->h7-V>F4IJAM76*_t<%}h{;@`zS&LcdYtZG(rN*BxefrA0=WeO(-#dQ{Rhs@f zH_wS}{_3UWW$+{@h&$+WP|)W|+K-EkK5y#YsHJsMzvH~8uJ>8Sljx37u41p@1UiHr zh(TV1JEkPRAU-FxEHww@oYQM{R_J&&P!l5%%OYz|Ni9gtOG(X3u8hyg z%*!qYneiB(Zb4+-Rhb34#ffRD7&;CGJDSu1Rc;4j6deKHkRbf*tLy3GspENt7ZMAb zgAA@1KltQ*#&>JbhqXK_cs!mootAjf_@v3ZxLCMbYpqDoC(%!zyp4=LU)pK&sW`Zl zTj+A*ET_MF{{A`qcZZC(x6!BW3qN> (2) +c = ~b +c = not b +d = [c] +e = d[:] +e = d[a:b:c] +raise_string(*args, **kwargs) +print >> stream, 'bonjour' +print >> stream, 'salut', + +def make_class(any, base=data.module.YO, *args, **kwargs): + """check base is correctly resolved to Concrete0""" + + + class Aaaa(base): + """dynamic class""" + + + return Aaaa +from os.path import abspath +import os as myos + + +class A: + pass + + + +class A(A): + pass + + +def generator(): + """A generator.""" + yield + +def not_a_generator(): + """A function that contains generator, but is not one.""" + + def generator(): + yield + genl = lambda : (yield) + +def with_metaclass(meta, *bases): + return meta('NewBase', bases, {}) + + +class NotMetaclass(with_metaclass(Metaclass)): + pass + + diff --git a/pymode/libs/astroid/tests/testdata/python2/data/noendingnewline.py b/pymode/libs/astroid/tests/testdata/python2/data/noendingnewline.py new file mode 100644 index 00000000..e1d6e4a1 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/noendingnewline.py @@ -0,0 +1,36 @@ +import unittest + + +class TestCase(unittest.TestCase): + + def setUp(self): + unittest.TestCase.setUp(self) + + + def tearDown(self): + unittest.TestCase.tearDown(self) + + def testIt(self): + self.a = 10 + self.xxx() + + + def xxx(self): + if False: + pass + print 'a' + + if False: + pass + pass + + if False: + pass + print 'rara' + + +if __name__ == '__main__': + print 'test2' + unittest.main() + + diff --git a/pymode/libs/astroid/tests/testdata/python2/data/nonregr.py b/pymode/libs/astroid/tests/testdata/python2/data/nonregr.py new file mode 100644 index 00000000..813469fe --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/nonregr.py @@ -0,0 +1,57 @@ +from __future__ import generators, print_function + +try: + enumerate = enumerate +except NameError: + + def enumerate(iterable): + """emulates the python2.3 enumerate() function""" + i = 0 + for val in iterable: + yield i, val + i += 1 + +def toto(value): + for k, v in value: + print(v.get('yo')) + + +import imp +fp, mpath, desc = imp.find_module('optparse',a) +s_opt = imp.load_module('std_optparse', fp, mpath, desc) + +class OptionParser(s_opt.OptionParser): + + def parse_args(self, args=None, values=None, real_optparse=False): + if real_optparse: + pass +## return super(OptionParser, self).parse_args() + else: + import optcomp + optcomp.completion(self) + + +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 Ccc(Aaa): + """docstring""" + + class Ddd(Aaa): + """docstring""" + pass + + class Eee(Ddd): + """docstring""" + pass diff --git a/pymode/libs/astroid/tests/testdata/python2/data/notall.py b/pymode/libs/astroid/tests/testdata/python2/data/notall.py new file mode 100644 index 00000000..7be27b18 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/notall.py @@ -0,0 +1,7 @@ +name = 'a' +_bla = 2 +other = 'o' +class Aaa: pass + +def func(): print('yo') + diff --git a/pymode/libs/astroid/tests/testdata/python2/data/package/__init__.py b/pymode/libs/astroid/tests/testdata/python2/data/package/__init__.py new file mode 100644 index 00000000..575d18b1 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/package/__init__.py @@ -0,0 +1,4 @@ +"""package's __init__ file""" + + +from . import subpackage diff --git a/pymode/libs/astroid/tests/testdata/python2/data/package/absimport.py b/pymode/libs/astroid/tests/testdata/python2/data/package/absimport.py new file mode 100644 index 00000000..33ed117c --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/package/absimport.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import, print_function +import import_package_subpackage_module # fail +print(import_package_subpackage_module) + +from . import hello as hola + diff --git a/pymode/libs/astroid/tests/testdata/python2/data/package/hello.py b/pymode/libs/astroid/tests/testdata/python2/data/package/hello.py new file mode 100644 index 00000000..b154c844 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/package/hello.py @@ -0,0 +1,2 @@ +"""hello module""" + diff --git a/pymode/libs/astroid/tests/testdata/python2/data/package/import_package_subpackage_module.py b/pymode/libs/astroid/tests/testdata/python2/data/package/import_package_subpackage_module.py new file mode 100644 index 00000000..ad442c16 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/package/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 astroid 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 + astroid = self._check_file(filepath, modname, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 450, in _check_file + self.check_astroid_module(astroid, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 494, in check_astroid_module + self.astroid_events(astroid, [checker for checker in checkers + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events + self.astroid_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events + self.astroid_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 508, in astroid_events + checker.visit(astroid) + File "/usr/lib/python2.4/site-packages/logilab/astroid/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 15:59:32 syt Exp $' +import package.subpackage.module diff --git a/pymode/libs/astroid/tests/testdata/python2/data/package/subpackage/__init__.py b/pymode/libs/astroid/tests/testdata/python2/data/package/subpackage/__init__.py new file mode 100644 index 00000000..dc4782e6 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/package/subpackage/__init__.py @@ -0,0 +1 @@ +"""package.subpackage""" diff --git a/pymode/libs/astroid/tests/testdata/python2/data/package/subpackage/module.py b/pymode/libs/astroid/tests/testdata/python2/data/package/subpackage/module.py new file mode 100644 index 00000000..4b7244ba --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/package/subpackage/module.py @@ -0,0 +1 @@ +"""package.subpackage.module""" diff --git a/pymode/libs/astroid/tests/testdata/python2/data/recursion.py b/pymode/libs/astroid/tests/testdata/python2/data/recursion.py new file mode 100644 index 00000000..a34dad32 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/recursion.py @@ -0,0 +1,3 @@ +""" For issue #25 """ +class Base(object): + pass \ No newline at end of file diff --git a/pymode/libs/astroid/tests/testdata/python2/data/suppliermodule_test.py b/pymode/libs/astroid/tests/testdata/python2/data/suppliermodule_test.py new file mode 100644 index 00000000..ddacb477 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/suppliermodule_test.py @@ -0,0 +1,13 @@ +""" file suppliermodule.py """ + +class NotImplemented(Exception): + pass + +class Interface: + def get_value(self): + raise NotImplemented() + + def set_value(self, value): + raise NotImplemented() + +class DoNothing : pass diff --git a/pymode/libs/astroid/tests/testdata/python2/data/unicode_package/__init__.py b/pymode/libs/astroid/tests/testdata/python2/data/unicode_package/__init__.py new file mode 100644 index 00000000..713e5591 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python2/data/unicode_package/__init__.py @@ -0,0 +1 @@ +x = "șțîâ" \ No newline at end of file diff --git a/pymode/libs/astroid/tests/testdata/python2/data/unicode_package/core/__init__.py b/pymode/libs/astroid/tests/testdata/python2/data/unicode_package/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymode/libs/astroid/tests/testdata/python3/data/MyPyPa-0.1.0-py2.5.egg b/pymode/libs/astroid/tests/testdata/python3/data/MyPyPa-0.1.0-py2.5.egg new file mode 100644 index 0000000000000000000000000000000000000000..f62599c7b10469b9eadabb31e9a42128a769c7cd GIT binary patch literal 1222 zcmWIWW@Zs#U|`^2V0WyvGL3P1+yvw;0%Bnx&aEt{EJ)OkkI&4@EQycTE2vDq{ik!f z_N!x_=Tq1;5?GSh74)7l32E~11UgKbs2I`Asd9`*h35;;UlWH_UOO6HYHA|00t?gX z;%fG=d001TTw@jTTsqTI^OvXQ%%iGRmNP4titd`3CYJVV<;$19c1~rT%G&wsg1;w&+sQ$d~(!s_JietmCU zt#fD2clU>H2n{g5U>x$C3CR?Y$GxZZOpXTX?t_}->h7-V>F4IJAM76*_t<%}h{;@`zS&LcdYtZG(rN*BxefrA0=WeO(-#dQ{Rhs@f zH_wS}{_3UWW$+{@h&$+WP|)W|+K-EkK5y#YsHJsMzvH~8uJ>8Sljx37u41p@1UiHr zh(TV1JEkPRAU-FxEHww@oYQM{R_J&&P!l5%%OYz|Ni9gtOG(X3u8hyg z%*!qYneiB(Zb4+-Rhb34#ffRD7&;CGJDSu1Rc;4j6deKHkRbf*tLy3GspENt7ZMAb zgAA@1KltQ*#&>JbhqXK_cs!mootAjf_@v3ZxLCMbYpqDoC(%!zyp4=LU)pK&sW`Zl zTj+A*ET_MF{{A`qcZZC(x6!BW3qNg1;w&+sQ$d~(!s_JietmCU zt#fD2clU>H2n{g5U>x$C3CR?Y$GxZZOpXTX?t_}->h7-V>F4IJAM76*_t<%}h{;@`zS&LcdYtZG(rN*BxefrA0=WeO(-#dQ{Rhs@f zH_wS}{_3UWW$+{@h&$+WP|)W|+K-EkK5y#YsHJsMzvH~8uJ>8Sljx37u41p@1UiHr zh(TV1JEkPRAU-FxEHww@oYQM{R_J&&P!l5%%OYz|Ni9gtOG(X3u8hyg z%*!qYneiB(Zb4+-Rhb34#ffRD7&;CGJDSu1Rc;4j6deKHkRbf*tLy3GspENt7ZMAb zgAA@1KltQ*#&>JbhqXK_cs!mootAjf_@v3ZxLCMbYpqDoC(%!zyp4=LU)pK&sW`Zl zTj+A*ET_MF{{A`qcZZC(x6!BW3qN> (2) +c = ~b +c = not b +d = [c] +e = d[:] +e = d[a:b:c] +raise_string(*args, **kwargs) +print('bonjour', file=stream) +print('salut', end=' ', file=stream) + +def make_class(any, base=data.module.YO, *args, **kwargs): + """check base is correctly resolved to Concrete0""" + + + class Aaaa(base): + """dynamic class""" + + + return Aaaa +from os.path import abspath +import os as myos + + +class A: + pass + + + +class A(A): + pass + + +def generator(): + """A generator.""" + yield + +def not_a_generator(): + """A function that contains generator, but is not one.""" + + def generator(): + yield + genl = lambda : (yield) + +def with_metaclass(meta, *bases): + return meta('NewBase', bases, {}) + + +class NotMetaclass(with_metaclass(Metaclass)): + pass + + diff --git a/pymode/libs/astroid/tests/testdata/python3/data/noendingnewline.py b/pymode/libs/astroid/tests/testdata/python3/data/noendingnewline.py new file mode 100644 index 00000000..e17b92cc --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/noendingnewline.py @@ -0,0 +1,36 @@ +import unittest + + +class TestCase(unittest.TestCase): + + def setUp(self): + unittest.TestCase.setUp(self) + + + def tearDown(self): + unittest.TestCase.tearDown(self) + + def testIt(self): + self.a = 10 + self.xxx() + + + def xxx(self): + if False: + pass + print('a') + + if False: + pass + pass + + if False: + pass + print('rara') + + +if __name__ == '__main__': + print('test2') + unittest.main() + + diff --git a/pymode/libs/astroid/tests/testdata/python3/data/nonregr.py b/pymode/libs/astroid/tests/testdata/python3/data/nonregr.py new file mode 100644 index 00000000..78765c85 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/nonregr.py @@ -0,0 +1,57 @@ + + +try: + enumerate = enumerate +except NameError: + + def enumerate(iterable): + """emulates the python2.3 enumerate() function""" + i = 0 + for val in iterable: + yield i, val + i += 1 + +def toto(value): + for k, v in value: + print(v.get('yo')) + + +import imp +fp, mpath, desc = imp.find_module('optparse',a) +s_opt = imp.load_module('std_optparse', fp, mpath, desc) + +class OptionParser(s_opt.OptionParser): + + def parse_args(self, args=None, values=None, real_optparse=False): + if real_optparse: + pass +## return super(OptionParser, self).parse_args() + else: + import optcomp + optcomp.completion(self) + + +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 Ccc(Aaa): + """docstring""" + + class Ddd(Aaa): + """docstring""" + pass + + class Eee(Ddd): + """docstring""" + pass diff --git a/pymode/libs/astroid/tests/testdata/python3/data/notall.py b/pymode/libs/astroid/tests/testdata/python3/data/notall.py new file mode 100644 index 00000000..9d35aa3a --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/notall.py @@ -0,0 +1,8 @@ + +name = 'a' +_bla = 2 +other = 'o' +class Aaa: pass + +def func(): print('yo') + diff --git a/pymode/libs/astroid/tests/testdata/python3/data/package/__init__.py b/pymode/libs/astroid/tests/testdata/python3/data/package/__init__.py new file mode 100644 index 00000000..575d18b1 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/package/__init__.py @@ -0,0 +1,4 @@ +"""package's __init__ file""" + + +from . import subpackage diff --git a/pymode/libs/astroid/tests/testdata/python3/data/package/absimport.py b/pymode/libs/astroid/tests/testdata/python3/data/package/absimport.py new file mode 100644 index 00000000..33ed117c --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/package/absimport.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import, print_function +import import_package_subpackage_module # fail +print(import_package_subpackage_module) + +from . import hello as hola + diff --git a/pymode/libs/astroid/tests/testdata/python3/data/package/hello.py b/pymode/libs/astroid/tests/testdata/python3/data/package/hello.py new file mode 100644 index 00000000..b154c844 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/package/hello.py @@ -0,0 +1,2 @@ +"""hello module""" + diff --git a/pymode/libs/astroid/tests/testdata/python3/data/package/import_package_subpackage_module.py b/pymode/libs/astroid/tests/testdata/python3/data/package/import_package_subpackage_module.py new file mode 100644 index 00000000..ad442c16 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/package/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 astroid 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 + astroid = self._check_file(filepath, modname, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 450, in _check_file + self.check_astroid_module(astroid, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 494, in check_astroid_module + self.astroid_events(astroid, [checker for checker in checkers + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events + self.astroid_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events + self.astroid_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 508, in astroid_events + checker.visit(astroid) + File "/usr/lib/python2.4/site-packages/logilab/astroid/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 15:59:32 syt Exp $' +import package.subpackage.module diff --git a/pymode/libs/astroid/tests/testdata/python3/data/package/subpackage/__init__.py b/pymode/libs/astroid/tests/testdata/python3/data/package/subpackage/__init__.py new file mode 100644 index 00000000..dc4782e6 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/package/subpackage/__init__.py @@ -0,0 +1 @@ +"""package.subpackage""" diff --git a/pymode/libs/astroid/tests/testdata/python3/data/package/subpackage/module.py b/pymode/libs/astroid/tests/testdata/python3/data/package/subpackage/module.py new file mode 100644 index 00000000..4b7244ba --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/package/subpackage/module.py @@ -0,0 +1 @@ +"""package.subpackage.module""" diff --git a/pymode/libs/astroid/tests/testdata/python3/data/recursion.py b/pymode/libs/astroid/tests/testdata/python3/data/recursion.py new file mode 100644 index 00000000..a34dad32 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/recursion.py @@ -0,0 +1,3 @@ +""" For issue #25 """ +class Base(object): + pass \ No newline at end of file diff --git a/pymode/libs/astroid/tests/testdata/python3/data/suppliermodule_test.py b/pymode/libs/astroid/tests/testdata/python3/data/suppliermodule_test.py new file mode 100644 index 00000000..ddacb477 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/suppliermodule_test.py @@ -0,0 +1,13 @@ +""" file suppliermodule.py """ + +class NotImplemented(Exception): + pass + +class Interface: + def get_value(self): + raise NotImplemented() + + def set_value(self, value): + raise NotImplemented() + +class DoNothing : pass diff --git a/pymode/libs/astroid/tests/testdata/python3/data/unicode_package/__init__.py b/pymode/libs/astroid/tests/testdata/python3/data/unicode_package/__init__.py new file mode 100644 index 00000000..713e5591 --- /dev/null +++ b/pymode/libs/astroid/tests/testdata/python3/data/unicode_package/__init__.py @@ -0,0 +1 @@ +x = "șțîâ" \ No newline at end of file diff --git a/pymode/libs/astroid/tests/testdata/python3/data/unicode_package/core/__init__.py b/pymode/libs/astroid/tests/testdata/python3/data/unicode_package/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymode/libs/astroid/tests/unittest_brain.py b/pymode/libs/astroid/tests/unittest_brain.py new file mode 100644 index 00000000..57d77ba4 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_brain.py @@ -0,0 +1,501 @@ +# Copyright 2013 Google Inc. All Rights Reserved. +# +# This file is part of astroid. +# +# logilab-astng is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# logilab-astng 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with logilab-astng. If not, see . +"""Tests for basic functionality in astroid.brain.""" +import sys +import unittest + +import six + +from astroid import MANAGER +from astroid import bases +from astroid import builder +from astroid import nodes +from astroid import test_utils +from astroid import util +import astroid + + +try: + import nose # pylint: disable=unused-import + HAS_NOSE = True +except ImportError: + HAS_NOSE = False + +try: + import multiprocessing # pylint: disable=unused-import + HAS_MULTIPROCESSING = True +except ImportError: + HAS_MULTIPROCESSING = False + +try: + import enum # pylint: disable=unused-import + HAS_ENUM = True +except ImportError: + HAS_ENUM = False + +try: + import dateutil # pylint: disable=unused-import + HAS_DATEUTIL = True +except ImportError: + HAS_DATEUTIL = False + +try: + import numpy # pylint: disable=unused-import + HAS_NUMPY = True +except ImportError: + HAS_NUMPY = False + +try: + import pytest # pylint: disable=unused-import + HAS_PYTEST = True +except ImportError: + HAS_PYTEST = False + + +class HashlibTest(unittest.TestCase): + def test_hashlib(self): + """Tests that brain extensions for hashlib work.""" + hashlib_module = MANAGER.ast_from_module_name('hashlib') + for class_name in ['md5', 'sha1']: + class_obj = hashlib_module[class_name] + self.assertIn('update', class_obj) + self.assertIn('digest', class_obj) + self.assertIn('hexdigest', class_obj) + self.assertIn('block_size', class_obj) + self.assertIn('digest_size', class_obj) + self.assertEqual(len(class_obj['__init__'].args.args), 2) + self.assertEqual(len(class_obj['__init__'].args.defaults), 1) + self.assertEqual(len(class_obj['update'].args.args), 2) + self.assertEqual(len(class_obj['digest'].args.args), 1) + self.assertEqual(len(class_obj['hexdigest'].args.args), 1) + + +class NamedTupleTest(unittest.TestCase): + + def test_namedtuple_base(self): + klass = test_utils.extract_node(""" + from collections import namedtuple + + class X(namedtuple("X", ["a", "b", "c"])): + pass + """) + self.assertEqual( + [anc.name for anc in klass.ancestors()], + ['X', 'tuple', 'object']) + for anc in klass.ancestors(): + self.assertFalse(anc.parent is None) + + def test_namedtuple_inference(self): + klass = test_utils.extract_node(""" + from collections import namedtuple + + name = "X" + fields = ["a", "b", "c"] + class X(namedtuple(name, fields)): + pass + """) + for base in klass.ancestors(): + if base.name == 'X': + break + self.assertSetEqual({"a", "b", "c"}, set(base._instance_attrs)) + + def test_namedtuple_inference_failure(self): + klass = test_utils.extract_node(""" + from collections import namedtuple + + def foo(fields): + return __(namedtuple("foo", fields)) + """) + self.assertIs(util.YES, next(klass.infer())) + + @unittest.skipIf(sys.version_info[0] > 2, + 'namedtuple inference is broken on Python 3') + def test_namedtuple_advanced_inference(self): + # urlparse return an object of class ParseResult, which has a + # namedtuple call and a mixin as base classes + result = test_utils.extract_node(""" + import urlparse + + result = __(urlparse.urlparse('gopher://')) + """) + instance = next(result.infer()) + self.assertEqual(len(instance.getattr('scheme')), 1) + self.assertEqual(len(instance.getattr('port')), 1) + with self.assertRaises(astroid.NotFoundError): + instance.getattr('foo') + self.assertEqual(len(instance.getattr('geturl')), 1) + self.assertEqual(instance.name, 'ParseResult') + + def test_namedtuple_instance_attrs(self): + result = test_utils.extract_node(''' + from collections import namedtuple + namedtuple('a', 'a b c')(1, 2, 3) #@ + ''') + inferred = next(result.infer()) + for name, attr in inferred._instance_attrs.items(): + self.assertEqual(attr[0].attrname, name) + + def test_namedtuple_uninferable_fields(self): + node = test_utils.extract_node(''' + x = [A] * 2 + from collections import namedtuple + l = namedtuple('a', x) + l(1) + ''') + inferred = next(node.infer()) + self.assertIs(util.YES, inferred) + + +class ModuleExtenderTest(unittest.TestCase): + def testExtensionModules(self): + transformer = MANAGER._transform + for extender, _ in transformer.transforms[nodes.Module]: + n = nodes.Module('__main__', None) + extender(n) + + +@unittest.skipUnless(HAS_NOSE, "This test requires nose library.") +class NoseBrainTest(unittest.TestCase): + + def test_nose_tools(self): + methods = test_utils.extract_node(""" + from nose.tools import assert_equal + from nose.tools import assert_equals + from nose.tools import assert_true + assert_equal = assert_equal #@ + assert_true = assert_true #@ + assert_equals = assert_equals #@ + """) + assert_equal = next(methods[0].value.infer()) + assert_true = next(methods[1].value.infer()) + assert_equals = next(methods[2].value.infer()) + + self.assertIsInstance(assert_equal, astroid.BoundMethod) + self.assertIsInstance(assert_true, astroid.BoundMethod) + self.assertIsInstance(assert_equals, astroid.BoundMethod) + self.assertEqual(assert_equal.qname(), + 'unittest.case.TestCase.assertEqual') + self.assertEqual(assert_true.qname(), + 'unittest.case.TestCase.assertTrue') + self.assertEqual(assert_equals.qname(), + 'unittest.case.TestCase.assertEqual') + + +class SixBrainTest(unittest.TestCase): + + def test_attribute_access(self): + ast_nodes = test_utils.extract_node(''' + import six + six.moves.http_client #@ + six.moves.urllib_parse #@ + six.moves.urllib_error #@ + six.moves.urllib.request #@ + ''') + http_client = next(ast_nodes[0].infer()) + self.assertIsInstance(http_client, nodes.Module) + self.assertEqual(http_client.name, + 'http.client' if six.PY3 else 'httplib') + + urllib_parse = next(ast_nodes[1].infer()) + if six.PY3: + self.assertIsInstance(urllib_parse, nodes.Module) + self.assertEqual(urllib_parse.name, 'urllib.parse') + else: + # On Python 2, this is a fake module, the same behaviour + # being mimicked in brain's tip for six.moves. + self.assertIsInstance(urllib_parse, astroid.Instance) + urljoin = next(urllib_parse.igetattr('urljoin')) + urlencode = next(urllib_parse.igetattr('urlencode')) + if six.PY2: + # In reality it's a function, but our implementations + # transforms it into a method. + self.assertIsInstance(urljoin, astroid.BoundMethod) + self.assertEqual(urljoin.qname(), 'urlparse.urljoin') + self.assertIsInstance(urlencode, astroid.BoundMethod) + self.assertEqual(urlencode.qname(), 'urllib.urlencode') + else: + self.assertIsInstance(urljoin, nodes.FunctionDef) + self.assertEqual(urljoin.qname(), 'urllib.parse.urljoin') + self.assertIsInstance(urlencode, nodes.FunctionDef) + self.assertEqual(urlencode.qname(), 'urllib.parse.urlencode') + + urllib_error = next(ast_nodes[2].infer()) + if six.PY3: + self.assertIsInstance(urllib_error, nodes.Module) + self.assertEqual(urllib_error.name, 'urllib.error') + else: + # On Python 2, this is a fake module, the same behaviour + # being mimicked in brain's tip for six.moves. + self.assertIsInstance(urllib_error, astroid.Instance) + urlerror = next(urllib_error.igetattr('URLError')) + self.assertIsInstance(urlerror, nodes.ClassDef) + content_too_short = next(urllib_error.igetattr('ContentTooShortError')) + self.assertIsInstance(content_too_short, nodes.ClassDef) + + urllib_request = next(ast_nodes[3].infer()) + if six.PY3: + self.assertIsInstance(urllib_request, nodes.Module) + self.assertEqual(urllib_request.name, 'urllib.request') + else: + self.assertIsInstance(urllib_request, astroid.Instance) + urlopen = next(urllib_request.igetattr('urlopen')) + urlretrieve = next(urllib_request.igetattr('urlretrieve')) + if six.PY2: + # In reality it's a function, but our implementations + # transforms it into a method. + self.assertIsInstance(urlopen, astroid.BoundMethod) + self.assertEqual(urlopen.qname(), 'urllib2.urlopen') + self.assertIsInstance(urlretrieve, astroid.BoundMethod) + self.assertEqual(urlretrieve.qname(), 'urllib.urlretrieve') + else: + self.assertIsInstance(urlopen, nodes.FunctionDef) + self.assertEqual(urlopen.qname(), 'urllib.request.urlopen') + self.assertIsInstance(urlretrieve, nodes.FunctionDef) + self.assertEqual(urlretrieve.qname(), 'urllib.request.urlretrieve') + + def test_from_imports(self): + ast_node = test_utils.extract_node(''' + from six.moves import http_client + http_client.HTTPSConnection #@ + ''') + inferred = next(ast_node.infer()) + self.assertIsInstance(inferred, nodes.ClassDef) + if six.PY3: + qname = 'http.client.HTTPSConnection' + else: + qname = 'httplib.HTTPSConnection' + self.assertEqual(inferred.qname(), qname) + + +@unittest.skipUnless(HAS_MULTIPROCESSING, + 'multiprocesing is required for this test, but ' + 'on some platforms it is missing ' + '(Jython for instance)') +class MultiprocessingBrainTest(unittest.TestCase): + + def test_multiprocessing_module_attributes(self): + # Test that module attributes are working, + # especially on Python 3.4+, where they are obtained + # from a context. + module = test_utils.extract_node(""" + import multiprocessing + """) + module = module.do_import_module('multiprocessing') + cpu_count = next(module.igetattr('cpu_count')) + if sys.version_info < (3, 4): + self.assertIsInstance(cpu_count, nodes.FunctionDef) + else: + self.assertIsInstance(cpu_count, astroid.BoundMethod) + + def test_module_name(self): + module = test_utils.extract_node(""" + import multiprocessing + multiprocessing.SyncManager() + """) + inferred_sync_mgr = next(module.infer()) + module = inferred_sync_mgr.root() + self.assertEqual(module.name, 'multiprocessing.managers') + + def test_multiprocessing_manager(self): + # Test that we have the proper attributes + # for a multiprocessing.managers.SyncManager + module = builder.parse(""" + import multiprocessing + manager = multiprocessing.Manager() + queue = manager.Queue() + joinable_queue = manager.JoinableQueue() + event = manager.Event() + rlock = manager.RLock() + bounded_semaphore = manager.BoundedSemaphore() + condition = manager.Condition() + barrier = manager.Barrier() + pool = manager.Pool() + list = manager.list() + dict = manager.dict() + value = manager.Value() + array = manager.Array() + namespace = manager.Namespace() + """) + queue = next(module['queue'].infer()) + self.assertEqual(queue.qname(), + "{}.Queue".format(six.moves.queue.__name__)) + + joinable_queue = next(module['joinable_queue'].infer()) + self.assertEqual(joinable_queue.qname(), + "{}.Queue".format(six.moves.queue.__name__)) + + event = next(module['event'].infer()) + event_name = "threading.{}".format("Event" if six.PY3 else "_Event") + self.assertEqual(event.qname(), event_name) + + rlock = next(module['rlock'].infer()) + rlock_name = "threading._RLock" + self.assertEqual(rlock.qname(), rlock_name) + + bounded_semaphore = next(module['bounded_semaphore'].infer()) + semaphore_name = "threading.{}".format( + "BoundedSemaphore" if six.PY3 else "_BoundedSemaphore") + self.assertEqual(bounded_semaphore.qname(), semaphore_name) + + pool = next(module['pool'].infer()) + pool_name = "multiprocessing.pool.Pool" + self.assertEqual(pool.qname(), pool_name) + + for attr in ('list', 'dict'): + obj = next(module[attr].infer()) + self.assertEqual(obj.qname(), + "{}.{}".format(bases.BUILTINS, attr)) + + array = next(module['array'].infer()) + self.assertEqual(array.qname(), "array.array") + + manager = next(module['manager'].infer()) + # Verify that we have these attributes + self.assertTrue(manager.getattr('start')) + self.assertTrue(manager.getattr('shutdown')) + + +@unittest.skipUnless(HAS_ENUM, + 'The enum module was only added in Python 3.4. Support for ' + 'older Python versions may be available through the enum34 ' + 'compatibility module.') +class EnumBrainTest(unittest.TestCase): + + def test_simple_enum(self): + module = builder.parse(""" + import enum + + class MyEnum(enum.Enum): + one = "one" + two = "two" + + def mymethod(self, x): + return 5 + + """) + + enum = next(module['MyEnum'].infer()) + one = enum['one'] + self.assertEqual(one.pytype(), '.MyEnum.one') + + property_type = '{}.property'.format(bases.BUILTINS) + for propname in ('name', 'value'): + prop = next(iter(one.getattr(propname))) + self.assertIn(property_type, prop.decoratornames()) + + meth = one.getattr('mymethod')[0] + self.assertIsInstance(meth, astroid.FunctionDef) + + def test_looks_like_enum_false_positive(self): + # Test that a class named Enumeration is not considered a builtin enum. + module = builder.parse(''' + class Enumeration(object): + def __init__(self, name, enum_list): + pass + test = 42 + ''') + enum = module['Enumeration'] + test = next(enum.igetattr('test')) + self.assertEqual(test.value, 42) + + def test_enum_multiple_base_classes(self): + module = builder.parse(""" + import enum + + class Mixin: + pass + + class MyEnum(Mixin, enum.Enum): + one = 1 + """) + enum = next(module['MyEnum'].infer()) + one = enum['one'] + + clazz = one.getattr('__class__')[0] + self.assertTrue(clazz.is_subtype_of('.Mixin'), + 'Enum instance should share base classes with generating class') + + def test_int_enum(self): + module = builder.parse(""" + import enum + + class MyEnum(enum.IntEnum): + one = 1 + """) + + enum = next(module['MyEnum'].infer()) + one = enum['one'] + + clazz = one.getattr('__class__')[0] + int_type = '{}.{}'.format(bases.BUILTINS, 'int') + self.assertTrue(clazz.is_subtype_of(int_type), + 'IntEnum based enums should be a subtype of int') + + def test_enum_func_form_is_class_not_instance(self): + cls, instance = test_utils.extract_node(''' + from enum import Enum + f = Enum('Audience', ['a', 'b', 'c']) + f #@ + f() #@ + ''') + inferred_cls = next(cls.infer()) + self.assertIsInstance(inferred_cls, nodes.ClassDef) + inferred_instance = next(instance.infer()) + self.assertIsInstance(inferred_instance, bases.Instance) + + +@unittest.skipUnless(HAS_DATEUTIL, "This test requires the dateutil library.") +class DateutilBrainTest(unittest.TestCase): + def test_parser(self): + module = builder.parse(""" + from dateutil.parser import parse + d = parse('2000-01-01') + """) + d_type = next(module['d'].infer()) + self.assertEqual(d_type.qname(), "datetime.datetime") + + +@unittest.skipUnless(HAS_NUMPY, "This test requires the numpy library.") +class NumpyBrainTest(unittest.TestCase): + + def test_numpy(self): + node = test_utils.extract_node(''' + import numpy + numpy.ones #@ + ''') + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.FunctionDef) + + +@unittest.skipUnless(HAS_PYTEST, "This test requires the pytest library.") +class PytestBrainTest(unittest.TestCase): + + def test_pytest(self): + ast_node = test_utils.extract_node(''' + import pytest + pytest #@ + ''') + module = next(ast_node.infer()) + self.assertIn('deprecated_call', module) + self.assertIn('exit', module) + self.assertIn('fail', module) + self.assertIn('fixture', module) + self.assertIn('mark', module) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_builder.py b/pymode/libs/astroid/tests/unittest_builder.py new file mode 100644 index 00000000..920f36e8 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_builder.py @@ -0,0 +1,774 @@ +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +"""tests for the astroid builder and rebuilder module""" + +import os +import sys +import unittest + +import six + +from astroid import builder +from astroid import exceptions +from astroid import manager +from astroid import nodes +from astroid import test_utils +from astroid import util +from astroid.tests import resources + +MANAGER = manager.AstroidManager() +BUILTINS = six.moves.builtins.__name__ + + +class FromToLineNoTest(unittest.TestCase): + + def setUp(self): + self.astroid = resources.build_file('data/format.py') + + def test_callfunc_lineno(self): + stmts = self.astroid.body + # on line 4: + # function('aeozrijz\ + # earzer', hop) + discard = stmts[0] + self.assertIsInstance(discard, nodes.Expr) + self.assertEqual(discard.fromlineno, 4) + self.assertEqual(discard.tolineno, 5) + callfunc = discard.value + self.assertIsInstance(callfunc, nodes.Call) + self.assertEqual(callfunc.fromlineno, 4) + self.assertEqual(callfunc.tolineno, 5) + name = callfunc.func + self.assertIsInstance(name, nodes.Name) + self.assertEqual(name.fromlineno, 4) + self.assertEqual(name.tolineno, 4) + strarg = callfunc.args[0] + self.assertIsInstance(strarg, nodes.Const) + if hasattr(sys, 'pypy_version_info'): + lineno = 4 + else: + lineno = 5 # no way for this one in CPython (is 4 actually) + self.assertEqual(strarg.fromlineno, lineno) + self.assertEqual(strarg.tolineno, lineno) + namearg = callfunc.args[1] + self.assertIsInstance(namearg, nodes.Name) + self.assertEqual(namearg.fromlineno, 5) + self.assertEqual(namearg.tolineno, 5) + # on line 10: + # fonction(1, + # 2, + # 3, + # 4) + discard = stmts[2] + self.assertIsInstance(discard, nodes.Expr) + self.assertEqual(discard.fromlineno, 10) + self.assertEqual(discard.tolineno, 13) + callfunc = discard.value + self.assertIsInstance(callfunc, nodes.Call) + self.assertEqual(callfunc.fromlineno, 10) + self.assertEqual(callfunc.tolineno, 13) + name = callfunc.func + self.assertIsInstance(name, nodes.Name) + self.assertEqual(name.fromlineno, 10) + self.assertEqual(name.tolineno, 10) + for i, arg in enumerate(callfunc.args): + self.assertIsInstance(arg, nodes.Const) + self.assertEqual(arg.fromlineno, 10+i) + self.assertEqual(arg.tolineno, 10+i) + + def test_function_lineno(self): + stmts = self.astroid.body + # on line 15: + # def definition(a, + # b, + # c): + # return a + b + c + function = stmts[3] + self.assertIsInstance(function, nodes.FunctionDef) + self.assertEqual(function.fromlineno, 15) + self.assertEqual(function.tolineno, 18) + return_ = function.body[0] + self.assertIsInstance(return_, nodes.Return) + self.assertEqual(return_.fromlineno, 18) + self.assertEqual(return_.tolineno, 18) + if sys.version_info < (3, 0): + self.assertEqual(function.blockstart_tolineno, 17) + else: + self.skipTest('FIXME http://bugs.python.org/issue10445 ' + '(no line number on function args)') + + def test_decorated_function_lineno(self): + astroid = builder.parse(''' + @decorator + def function( + arg): + print (arg) + ''', __name__) + function = astroid['function'] + self.assertEqual(function.fromlineno, 3) # XXX discussable, but that's what is expected by pylint right now + self.assertEqual(function.tolineno, 5) + self.assertEqual(function.decorators.fromlineno, 2) + self.assertEqual(function.decorators.tolineno, 2) + if sys.version_info < (3, 0): + self.assertEqual(function.blockstart_tolineno, 4) + else: + self.skipTest('FIXME http://bugs.python.org/issue10445 ' + '(no line number on function args)') + + + def test_class_lineno(self): + stmts = self.astroid.body + # on line 20: + # class debile(dict, + # object): + # pass + class_ = stmts[4] + self.assertIsInstance(class_, nodes.ClassDef) + self.assertEqual(class_.fromlineno, 20) + self.assertEqual(class_.tolineno, 22) + self.assertEqual(class_.blockstart_tolineno, 21) + pass_ = class_.body[0] + self.assertIsInstance(pass_, nodes.Pass) + self.assertEqual(pass_.fromlineno, 22) + self.assertEqual(pass_.tolineno, 22) + + def test_if_lineno(self): + stmts = self.astroid.body + # on line 20: + # if aaaa: pass + # else: + # aaaa,bbbb = 1,2 + # aaaa,bbbb = bbbb,aaaa + if_ = stmts[5] + self.assertIsInstance(if_, nodes.If) + self.assertEqual(if_.fromlineno, 24) + self.assertEqual(if_.tolineno, 27) + self.assertEqual(if_.blockstart_tolineno, 24) + self.assertEqual(if_.orelse[0].fromlineno, 26) + self.assertEqual(if_.orelse[1].tolineno, 27) + + def test_for_while_lineno(self): + for code in (''' + for a in range(4): + print (a) + break + else: + print ("bouh") + ''', ''' + while a: + print (a) + break + else: + print ("bouh") + '''): + astroid = builder.parse(code, __name__) + stmt = astroid.body[0] + self.assertEqual(stmt.fromlineno, 2) + self.assertEqual(stmt.tolineno, 6) + self.assertEqual(stmt.blockstart_tolineno, 2) + self.assertEqual(stmt.orelse[0].fromlineno, 6) # XXX + self.assertEqual(stmt.orelse[0].tolineno, 6) + + def test_try_except_lineno(self): + astroid = builder.parse(''' + try: + print (a) + except: + pass + else: + print ("bouh") + ''', __name__) + try_ = astroid.body[0] + self.assertEqual(try_.fromlineno, 2) + self.assertEqual(try_.tolineno, 7) + self.assertEqual(try_.blockstart_tolineno, 2) + self.assertEqual(try_.orelse[0].fromlineno, 7) # XXX + self.assertEqual(try_.orelse[0].tolineno, 7) + hdlr = try_.handlers[0] + self.assertEqual(hdlr.fromlineno, 4) + self.assertEqual(hdlr.tolineno, 5) + self.assertEqual(hdlr.blockstart_tolineno, 4) + + + def test_try_finally_lineno(self): + astroid = builder.parse(''' + try: + print (a) + finally: + print ("bouh") + ''', __name__) + try_ = astroid.body[0] + self.assertEqual(try_.fromlineno, 2) + self.assertEqual(try_.tolineno, 5) + self.assertEqual(try_.blockstart_tolineno, 2) + self.assertEqual(try_.finalbody[0].fromlineno, 5) # XXX + self.assertEqual(try_.finalbody[0].tolineno, 5) + + + def test_try_finally_25_lineno(self): + astroid = builder.parse(''' + try: + print (a) + except: + pass + finally: + print ("bouh") + ''', __name__) + try_ = astroid.body[0] + self.assertEqual(try_.fromlineno, 2) + self.assertEqual(try_.tolineno, 7) + self.assertEqual(try_.blockstart_tolineno, 2) + self.assertEqual(try_.finalbody[0].fromlineno, 7) # XXX + self.assertEqual(try_.finalbody[0].tolineno, 7) + + + def test_with_lineno(self): + astroid = builder.parse(''' + from __future__ import with_statement + with file("/tmp/pouet") as f: + print (f) + ''', __name__) + with_ = astroid.body[1] + self.assertEqual(with_.fromlineno, 3) + self.assertEqual(with_.tolineno, 4) + self.assertEqual(with_.blockstart_tolineno, 3) + + +class BuilderTest(unittest.TestCase): + + def setUp(self): + self.builder = builder.AstroidBuilder() + + def test_data_build_null_bytes(self): + with self.assertRaises(exceptions.AstroidBuildingException): + self.builder.string_build('\x00') + + def test_data_build_invalid_x_escape(self): + with self.assertRaises(exceptions.AstroidBuildingException): + self.builder.string_build('"\\x1"') + + def test_missing_newline(self): + """check that a file with no trailing new line is parseable""" + resources.build_file('data/noendingnewline.py') + + def test_missing_file(self): + with self.assertRaises(exceptions.AstroidBuildingException): + resources.build_file('data/inexistant.py') + + def test_inspect_build0(self): + """test astroid tree build from a living object""" + builtin_ast = MANAGER.ast_from_module_name(BUILTINS) + if six.PY2: + fclass = builtin_ast['file'] + self.assertIn('name', fclass) + self.assertIn('mode', fclass) + self.assertIn('read', fclass) + self.assertTrue(fclass.newstyle) + self.assertTrue(fclass.pytype(), '%s.type' % BUILTINS) + self.assertIsInstance(fclass['read'], nodes.FunctionDef) + # check builtin function has args.args == None + dclass = builtin_ast['dict'] + self.assertIsNone(dclass['has_key'].args.args) + # just check type and object are there + builtin_ast.getattr('type') + objectastroid = builtin_ast.getattr('object')[0] + self.assertIsInstance(objectastroid.getattr('__new__')[0], nodes.FunctionDef) + # check open file alias + builtin_ast.getattr('open') + # check 'help' is there (defined dynamically by site.py) + builtin_ast.getattr('help') + # check property has __init__ + pclass = builtin_ast['property'] + self.assertIn('__init__', pclass) + self.assertIsInstance(builtin_ast['None'], nodes.Const) + self.assertIsInstance(builtin_ast['True'], nodes.Const) + self.assertIsInstance(builtin_ast['False'], nodes.Const) + if six.PY3: + self.assertIsInstance(builtin_ast['Exception'], nodes.ClassDef) + self.assertIsInstance(builtin_ast['NotImplementedError'], nodes.ClassDef) + else: + self.assertIsInstance(builtin_ast['Exception'], nodes.ImportFrom) + self.assertIsInstance(builtin_ast['NotImplementedError'], nodes.ImportFrom) + + def test_inspect_build1(self): + time_ast = MANAGER.ast_from_module_name('time') + self.assertTrue(time_ast) + self.assertEqual(time_ast['time'].args.defaults, []) + + if os.name == 'java': + test_inspect_build1 = unittest.expectedFailure(test_inspect_build1) + + def test_inspect_build2(self): + """test astroid tree build from a living object""" + try: + from mx import DateTime + except ImportError: + self.skipTest('test skipped: mxDateTime is not available') + else: + dt_ast = self.builder.inspect_build(DateTime) + dt_ast.getattr('DateTime') + # this one is failing since DateTimeType.__module__ = 'builtins' ! + #dt_ast.getattr('DateTimeType') + + def test_inspect_build3(self): + self.builder.inspect_build(unittest) + + @test_utils.require_version(maxver='3.0') + def test_inspect_build_instance(self): + """test astroid tree build from a living object""" + import exceptions + builtin_ast = self.builder.inspect_build(exceptions) + fclass = builtin_ast['OSError'] + # things like OSError.strerror are now (2.5) data descriptors on the + # class instead of entries in the __dict__ of an instance + container = fclass + self.assertIn('errno', container) + self.assertIn('strerror', container) + self.assertIn('filename', container) + + def test_inspect_build_type_object(self): + builtin_ast = MANAGER.ast_from_module_name(BUILTINS) + + inferred = list(builtin_ast.igetattr('object')) + self.assertEqual(len(inferred), 1) + inferred = inferred[0] + self.assertEqual(inferred.name, 'object') + inferred.as_string() # no crash test + + inferred = list(builtin_ast.igetattr('type')) + self.assertEqual(len(inferred), 1) + inferred = inferred[0] + self.assertEqual(inferred.name, 'type') + inferred.as_string() # no crash test + + def test_inspect_transform_module(self): + # ensure no cached version of the time module + MANAGER._mod_file_cache.pop(('time', None), None) + MANAGER.astroid_cache.pop('time', None) + def transform_time(node): + if node.name == 'time': + node.transformed = True + MANAGER.register_transform(nodes.Module, transform_time) + try: + time_ast = MANAGER.ast_from_module_name('time') + self.assertTrue(getattr(time_ast, 'transformed', False)) + finally: + MANAGER.unregister_transform(nodes.Module, transform_time) + + def test_package_name(self): + """test base properties and method of a astroid module""" + datap = resources.build_file('data/__init__.py', 'data') + self.assertEqual(datap.name, 'data') + self.assertEqual(datap.package, 1) + datap = resources.build_file('data/__init__.py', 'data.__init__') + self.assertEqual(datap.name, 'data') + self.assertEqual(datap.package, 1) + + def test_yield_parent(self): + """check if we added discard nodes as yield parent (w/ compiler)""" + code = """ + def yiell(): #@ + yield 0 + if noe: + yield more + """ + func = test_utils.extract_node(code) + self.assertIsInstance(func, nodes.FunctionDef) + stmt = func.body[0] + self.assertIsInstance(stmt, nodes.Expr) + self.assertIsInstance(stmt.value, nodes.Yield) + self.assertIsInstance(func.body[1].body[0], nodes.Expr) + self.assertIsInstance(func.body[1].body[0].value, nodes.Yield) + + def test_object(self): + obj_ast = self.builder.inspect_build(object) + self.assertIn('__setattr__', obj_ast) + + def test_newstyle_detection(self): + data = ''' + class A: + "old style" + + class B(A): + "old style" + + class C(object): + "new style" + + class D(C): + "new style" + + __metaclass__ = type + + class E(A): + "old style" + + class F: + "new style" + ''' + mod_ast = builder.parse(data, __name__) + if six.PY3: + self.assertTrue(mod_ast['A'].newstyle) + self.assertTrue(mod_ast['B'].newstyle) + self.assertTrue(mod_ast['E'].newstyle) + else: + self.assertFalse(mod_ast['A'].newstyle) + self.assertFalse(mod_ast['B'].newstyle) + self.assertFalse(mod_ast['E'].newstyle) + self.assertTrue(mod_ast['C'].newstyle) + self.assertTrue(mod_ast['D'].newstyle) + self.assertTrue(mod_ast['F'].newstyle) + + def test_globals(self): + data = ''' + CSTE = 1 + + def update_global(): + global CSTE + CSTE += 1 + + def global_no_effect(): + global CSTE2 + print (CSTE) + ''' + astroid = builder.parse(data, __name__) + self.assertEqual(len(astroid.getattr('CSTE')), 2) + self.assertIsInstance(astroid.getattr('CSTE')[0], nodes.AssignName) + self.assertEqual(astroid.getattr('CSTE')[0].fromlineno, 2) + self.assertEqual(astroid.getattr('CSTE')[1].fromlineno, 6) + with self.assertRaises(exceptions.NotFoundError): + astroid.getattr('CSTE2') + with self.assertRaises(exceptions.InferenceError): + next(astroid['global_no_effect'].ilookup('CSTE2')) + + @unittest.skipIf(os.name == 'java', + 'This test is skipped on Jython, because the ' + 'socket object is patched later on with the ' + 'methods we are looking for. Since we do not ' + 'understand setattr in for loops yet, we skip this') + def test_socket_build(self): + import socket + astroid = self.builder.module_build(socket) + # XXX just check the first one. Actually 3 objects are inferred (look at + # the socket module) but the last one as those attributes dynamically + # set and astroid is missing this. + for fclass in astroid.igetattr('socket'): + self.assertIn('connect', fclass) + self.assertIn('send', fclass) + self.assertIn('close', fclass) + break + + def test_gen_expr_var_scope(self): + data = 'l = list(n for n in range(10))\n' + astroid = builder.parse(data, __name__) + # n unavailable outside gen expr scope + self.assertNotIn('n', astroid) + # test n is inferable anyway + n = test_utils.get_name_node(astroid, 'n') + self.assertIsNot(n.scope(), astroid) + self.assertEqual([i.__class__ for i in n.infer()], + [util.YES.__class__]) + + def test_no_future_imports(self): + mod = builder.parse("import sys") + self.assertEqual(set(), mod._future_imports) + + def test_future_imports(self): + mod = builder.parse("from __future__ import print_function") + self.assertEqual(set(['print_function']), mod._future_imports) + + def test_two_future_imports(self): + mod = builder.parse(""" + from __future__ import print_function + from __future__ import absolute_import + """) + self.assertEqual(set(['print_function', 'absolute_import']), mod._future_imports) + + def test_inferred_build(self): + code = ''' + class A: pass + A.type = "class" + + def A_assign_type(self): + print (self) + A.assign_type = A_assign_type + ''' + astroid = builder.parse(code) + lclass = list(astroid.igetattr('A')) + self.assertEqual(len(lclass), 1) + lclass = lclass[0] + self.assertIn('assign_type', lclass._locals) + self.assertIn('type', lclass._locals) + + def test_augassign_attr(self): + builder.parse(""" + class Counter: + v = 0 + def inc(self): + self.v += 1 + """, __name__) + # TODO: Check self.v += 1 generate AugAssign(AssAttr(...)), + # not AugAssign(GetAttr(AssName...)) + + def test_inferred_dont_pollute(self): + code = ''' + def func(a=None): + a.custom_attr = 0 + def func2(a={}): + a.custom_attr = 0 + ''' + builder.parse(code) + nonetype = nodes.const_factory(None) + self.assertNotIn('custom_attr', nonetype._locals) + self.assertNotIn('custom_attr', nonetype._instance_attrs) + nonetype = nodes.const_factory({}) + self.assertNotIn('custom_attr', nonetype._locals) + self.assertNotIn('custom_attr', nonetype._instance_attrs) + + def test_asstuple(self): + code = 'a, b = range(2)' + astroid = builder.parse(code) + self.assertIn('b', astroid._locals) + code = ''' + def visit_if(self, node): + node.test, body = node.tests[0] + ''' + astroid = builder.parse(code) + self.assertIn('body', astroid['visit_if']._locals) + + def test_build_constants(self): + '''test expected values of constants after rebuilding''' + code = ''' + def func(): + return None + return + return 'None' + ''' + astroid = builder.parse(code) + none, nothing, chain = [ret.value for ret in astroid.body[0].body] + self.assertIsInstance(none, nodes.Const) + self.assertIsNone(none.value) + self.assertIsNone(nothing) + self.assertIsInstance(chain, nodes.Const) + self.assertEqual(chain.value, 'None') + + def test_not_implemented(self): + node = test_utils.extract_node(''' + NotImplemented #@ + ''') + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, NotImplemented) + + +class FileBuildTest(unittest.TestCase): + def setUp(self): + self.module = resources.build_file('data/module.py', 'data.module') + + def test_module_base_props(self): + """test base properties and method of a astroid module""" + module = self.module + self.assertEqual(module.name, 'data.module') + self.assertEqual(module.doc, "test module for astroid\n") + self.assertEqual(module.fromlineno, 0) + self.assertIsNone(module.parent) + self.assertEqual(module.frame(), module) + self.assertEqual(module.root(), module) + self.assertEqual(module.source_file, os.path.abspath(resources.find('data/module.py'))) + self.assertEqual(module.pure_python, 1) + self.assertEqual(module.package, 0) + self.assertFalse(module.is_statement) + self.assertEqual(module.statement(), module) + self.assertEqual(module.statement(), module) + + def test_module_locals(self): + """test the 'locals' dictionary of a astroid module""" + module = self.module + _locals = module._locals + self.assertIs(_locals, module._globals) + keys = sorted(_locals.keys()) + should = ['MY_DICT', 'NameNode', 'YO', 'YOUPI', + '__revision__', 'global_access', 'modutils', 'four_args', + 'os', 'redirect'] + should.sort() + self.assertEqual(keys, sorted(should)) + + def test_function_base_props(self): + """test base properties and method of a astroid function""" + module = self.module + function = module['global_access'] + self.assertEqual(function.name, 'global_access') + self.assertEqual(function.doc, 'function test') + self.assertEqual(function.fromlineno, 11) + self.assertTrue(function.parent) + self.assertEqual(function.frame(), function) + self.assertEqual(function.parent.frame(), module) + self.assertEqual(function.root(), module) + self.assertEqual([n.name for n in function.args.args], ['key', 'val']) + self.assertEqual(function.type, 'function') + + def test_function_locals(self): + """test the 'locals' dictionary of a astroid function""" + _locals = self.module['global_access']._locals + self.assertEqual(len(_locals), 4) + keys = sorted(_locals.keys()) + self.assertEqual(keys, ['i', 'key', 'local', 'val']) + + def test_class_base_props(self): + """test base properties and method of a astroid class""" + module = self.module + klass = module['YO'] + self.assertEqual(klass.name, 'YO') + self.assertEqual(klass.doc, 'hehe') + self.assertEqual(klass.fromlineno, 25) + self.assertTrue(klass.parent) + self.assertEqual(klass.frame(), klass) + self.assertEqual(klass.parent.frame(), module) + self.assertEqual(klass.root(), module) + self.assertEqual(klass.basenames, []) + if six.PY3: + self.assertTrue(klass.newstyle) + else: + self.assertFalse(klass.newstyle) + + def test_class_locals(self): + """test the 'locals' dictionary of a astroid class""" + module = self.module + klass1 = module['YO'] + locals1 = klass1._locals + keys = sorted(locals1.keys()) + self.assertEqual(keys, ['__init__', 'a']) + klass2 = module['YOUPI'] + locals2 = klass2._locals + keys = locals2.keys() + self.assertEqual(sorted(keys), + ['__init__', 'class_attr', 'class_method', + 'method', 'static_method']) + + def test_class_instance_attrs(self): + module = self.module + klass1 = module['YO'] + klass2 = module['YOUPI'] + self.assertEqual(list(klass1._instance_attrs.keys()), ['yo']) + self.assertEqual(list(klass2._instance_attrs.keys()), ['member']) + + def test_class_basenames(self): + module = self.module + klass1 = module['YO'] + klass2 = module['YOUPI'] + self.assertEqual(klass1.basenames, []) + self.assertEqual(klass2.basenames, ['YO']) + + def test_method_base_props(self): + """test base properties and method of a astroid method""" + klass2 = self.module['YOUPI'] + # "normal" method + method = klass2['method'] + self.assertEqual(method.name, 'method') + self.assertEqual([n.name for n in method.args.args], ['self']) + self.assertEqual(method.doc, 'method test') + self.assertEqual(method.fromlineno, 47) + self.assertEqual(method.type, 'method') + # class method + method = klass2['class_method'] + self.assertEqual([n.name for n in method.args.args], ['cls']) + self.assertEqual(method.type, 'classmethod') + # static method + method = klass2['static_method'] + self.assertEqual(method.args.args, []) + self.assertEqual(method.type, 'staticmethod') + + def test_method_locals(self): + """test the 'locals' dictionary of a astroid method""" + method = self.module['YOUPI']['method'] + _locals = method._locals + keys = sorted(_locals) + if sys.version_info < (3, 0): + self.assertEqual(len(_locals), 5) + self.assertEqual(keys, ['a', 'autre', 'b', 'local', 'self']) + else:# ListComp variables are no more accessible outside + self.assertEqual(len(_locals), 3) + self.assertEqual(keys, ['autre', 'local', 'self']) + + +class ModuleBuildTest(resources.SysPathSetup, FileBuildTest): + + def setUp(self): + super(ModuleBuildTest, self).setUp() + abuilder = builder.AstroidBuilder() + try: + import data.module + except ImportError: + # Make pylint happy. + self.skipTest('Unable to load data.module') + else: + self.module = abuilder.module_build(data.module, 'data.module') + +@unittest.skipIf(six.PY3, "guess_encoding not used on Python 3") +class TestGuessEncoding(unittest.TestCase): + def setUp(self): + self.guess_encoding = builder._guess_encoding + + def testEmacs(self): + e = self.guess_encoding('# -*- coding: UTF-8 -*-') + self.assertEqual(e, 'UTF-8') + e = self.guess_encoding('# -*- coding:UTF-8 -*-') + self.assertEqual(e, 'UTF-8') + e = self.guess_encoding(''' + ### -*- coding: ISO-8859-1 -*- + ''') + self.assertEqual(e, 'ISO-8859-1') + e = self.guess_encoding(''' + + ### -*- coding: ISO-8859-1 -*- + ''') + self.assertIsNone(e) + + def testVim(self): + e = self.guess_encoding('# vim:fileencoding=UTF-8') + self.assertEqual(e, 'UTF-8') + e = self.guess_encoding(''' + ### vim:fileencoding=ISO-8859-1 + ''') + self.assertEqual(e, 'ISO-8859-1') + e = self.guess_encoding(''' + + ### vim:fileencoding= ISO-8859-1 + ''') + self.assertIsNone(e) + + def test_wrong_coding(self): + # setting "coding" varaible + e = self.guess_encoding("coding = UTF-8") + self.assertIsNone(e) + # setting a dictionnary entry + e = self.guess_encoding("coding:UTF-8") + self.assertIsNone(e) + # setting an arguement + e = self.guess_encoding("def do_something(a_word_with_coding=None):") + self.assertIsNone(e) + + def testUTF8(self): + e = self.guess_encoding('\xef\xbb\xbf any UTF-8 data') + self.assertEqual(e, 'UTF-8') + e = self.guess_encoding(' any UTF-8 data \xef\xbb\xbf') + self.assertIsNone(e) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_inference.py b/pymode/libs/astroid/tests/unittest_inference.py new file mode 100644 index 00000000..86497727 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_inference.py @@ -0,0 +1,2130 @@ +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +"""tests for the astroid inference capabilities +""" +import sys +from functools import partial +import unittest +import warnings + +import six + +from astroid import InferenceError, builder, nodes +from astroid.builder import parse +from astroid.inference import infer_end as inference_infer_end +from astroid.bases import Instance, BoundMethod, UnboundMethod,\ + path_wrapper, BUILTINS +from astroid import arguments +from astroid import objects +from astroid import test_utils +from astroid import util +from astroid.tests import resources + + +def get_node_of_class(start_from, klass): + return next(start_from.nodes_of_class(klass)) + +builder = builder.AstroidBuilder() + +if sys.version_info < (3, 0): + EXC_MODULE = 'exceptions' +else: + EXC_MODULE = BUILTINS + + +class InferenceUtilsTest(unittest.TestCase): + + def test_path_wrapper(self): + def infer_default(self, *args): + raise InferenceError + infer_default = path_wrapper(infer_default) + infer_end = path_wrapper(inference_infer_end) + with self.assertRaises(InferenceError): + next(infer_default(1)) + self.assertEqual(next(infer_end(1)), 1) + + +def _assertInferElts(node_type, self, node, elts): + inferred = next(node.infer()) + self.assertIsInstance(inferred, node_type) + self.assertEqual(sorted(elt.value for elt in inferred.elts), + elts) + +def partialmethod(func, arg): + """similar to functools.partial but return a lambda instead of a class so returned value may be + turned into a method. + """ + return lambda *args, **kwargs: func(arg, *args, **kwargs) + +class InferenceTest(resources.SysPathSetup, unittest.TestCase): + + # additional assertInfer* method for builtin types + + def assertInferConst(self, node, expected): + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, expected) + + def assertInferDict(self, node, expected): + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Dict) + + elts = set([(key.value, value.value) + for (key, value) in inferred.items]) + self.assertEqual(sorted(elts), sorted(expected.items())) + + assertInferTuple = partialmethod(_assertInferElts, nodes.Tuple) + assertInferList = partialmethod(_assertInferElts, nodes.List) + assertInferSet = partialmethod(_assertInferElts, nodes.Set) + assertInferFrozenSet = partialmethod(_assertInferElts, objects.FrozenSet) + + CODE = ''' + class C(object): + "new style" + attr = 4 + + def meth1(self, arg1, optarg=0): + var = object() + print ("yo", arg1, optarg) + self.iattr = "hop" + return var + + def meth2(self): + self.meth1(*self.meth3) + + def meth3(self, d=attr): + b = self.attr + c = self.iattr + return b, c + + ex = Exception("msg") + v = C().meth1(1) + m_unbound = C.meth1 + m_bound = C().meth1 + a, b, c = ex, 1, "bonjour" + [d, e, f] = [ex, 1.0, ("bonjour", v)] + g, h = f + i, (j, k) = "glup", f + + a, b= b, a # Gasp ! + ''' + + ast = parse(CODE, __name__) + + def test_infer_abstract_property_return_values(self): + module = parse(''' + import abc + + class A(object): + @abc.abstractproperty + def test(self): + return 42 + + a = A() + x = a.test + ''') + inferred = next(module['x'].infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, 42) + + def test_module_inference(self): + inferred = self.ast.infer() + obj = next(inferred) + self.assertEqual(obj.name, __name__) + self.assertEqual(obj.root().name, __name__) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_class_inference(self): + inferred = self.ast['C'].infer() + obj = next(inferred) + self.assertEqual(obj.name, 'C') + self.assertEqual(obj.root().name, __name__) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_function_inference(self): + inferred = self.ast['C']['meth1'].infer() + obj = next(inferred) + self.assertEqual(obj.name, 'meth1') + self.assertEqual(obj.root().name, __name__) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_builtin_name_inference(self): + inferred = self.ast['C']['meth1']['var'].infer() + var = next(inferred) + self.assertEqual(var.name, 'object') + self.assertEqual(var.root().name, BUILTINS) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_tupleassign_name_inference(self): + inferred = self.ast['a'].infer() + exc = next(inferred) + self.assertIsInstance(exc, Instance) + self.assertEqual(exc.name, 'Exception') + self.assertEqual(exc.root().name, EXC_MODULE) + self.assertRaises(StopIteration, partial(next, inferred)) + inferred = self.ast['b'].infer() + const = next(inferred) + self.assertIsInstance(const, nodes.Const) + self.assertEqual(const.value, 1) + self.assertRaises(StopIteration, partial(next, inferred)) + inferred = self.ast['c'].infer() + const = next(inferred) + self.assertIsInstance(const, nodes.Const) + self.assertEqual(const.value, "bonjour") + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_listassign_name_inference(self): + inferred = self.ast['d'].infer() + exc = next(inferred) + self.assertIsInstance(exc, Instance) + self.assertEqual(exc.name, 'Exception') + self.assertEqual(exc.root().name, EXC_MODULE) + self.assertRaises(StopIteration, partial(next, inferred)) + inferred = self.ast['e'].infer() + const = next(inferred) + self.assertIsInstance(const, nodes.Const) + self.assertEqual(const.value, 1.0) + self.assertRaises(StopIteration, partial(next, inferred)) + inferred = self.ast['f'].infer() + const = next(inferred) + self.assertIsInstance(const, nodes.Tuple) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_advanced_tupleassign_name_inference1(self): + inferred = self.ast['g'].infer() + const = next(inferred) + self.assertIsInstance(const, nodes.Const) + self.assertEqual(const.value, "bonjour") + self.assertRaises(StopIteration, partial(next, inferred)) + inferred = self.ast['h'].infer() + var = next(inferred) + self.assertEqual(var.name, 'object') + self.assertEqual(var.root().name, BUILTINS) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_advanced_tupleassign_name_inference2(self): + inferred = self.ast['i'].infer() + const = next(inferred) + self.assertIsInstance(const, nodes.Const) + self.assertEqual(const.value, u"glup") + self.assertRaises(StopIteration, partial(next, inferred)) + inferred = self.ast['j'].infer() + const = next(inferred) + self.assertIsInstance(const, nodes.Const) + self.assertEqual(const.value, "bonjour") + self.assertRaises(StopIteration, partial(next, inferred)) + inferred = self.ast['k'].infer() + var = next(inferred) + self.assertEqual(var.name, 'object') + self.assertEqual(var.root().name, BUILTINS) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_swap_assign_inference(self): + inferred = self.ast._locals['a'][1].infer() + const = next(inferred) + self.assertIsInstance(const, nodes.Const) + self.assertEqual(const.value, 1) + self.assertRaises(StopIteration, partial(next, inferred)) + inferred = self.ast._locals['b'][1].infer() + exc = next(inferred) + self.assertIsInstance(exc, Instance) + self.assertEqual(exc.name, 'Exception') + self.assertEqual(exc.root().name, EXC_MODULE) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_getattr_inference1(self): + inferred = self.ast['ex'].infer() + exc = next(inferred) + self.assertIsInstance(exc, Instance) + self.assertEqual(exc.name, 'Exception') + self.assertEqual(exc.root().name, EXC_MODULE) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_getattr_inference2(self): + inferred = get_node_of_class(self.ast['C']['meth2'], nodes.Attribute).infer() + meth1 = next(inferred) + self.assertEqual(meth1.name, 'meth1') + self.assertEqual(meth1.root().name, __name__) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_getattr_inference3(self): + inferred = self.ast['C']['meth3']['b'].infer() + const = next(inferred) + self.assertIsInstance(const, nodes.Const) + self.assertEqual(const.value, 4) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_getattr_inference4(self): + inferred = self.ast['C']['meth3']['c'].infer() + const = next(inferred) + self.assertIsInstance(const, nodes.Const) + self.assertEqual(const.value, "hop") + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_callfunc_inference(self): + inferred = self.ast['v'].infer() + meth1 = next(inferred) + self.assertIsInstance(meth1, Instance) + self.assertEqual(meth1.name, 'object') + self.assertEqual(meth1.root().name, BUILTINS) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_unbound_method_inference(self): + inferred = self.ast['m_unbound'].infer() + meth1 = next(inferred) + self.assertIsInstance(meth1, UnboundMethod) + self.assertEqual(meth1.name, 'meth1') + self.assertEqual(meth1.parent.frame().name, 'C') + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_bound_method_inference(self): + inferred = self.ast['m_bound'].infer() + meth1 = next(inferred) + self.assertIsInstance(meth1, BoundMethod) + self.assertEqual(meth1.name, 'meth1') + self.assertEqual(meth1.parent.frame().name, 'C') + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_args_default_inference1(self): + optarg = test_utils.get_name_node(self.ast['C']['meth1'], 'optarg') + inferred = optarg.infer() + obj1 = next(inferred) + self.assertIsInstance(obj1, nodes.Const) + self.assertEqual(obj1.value, 0) + obj1 = next(inferred) + self.assertIs(obj1, util.YES, obj1) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_args_default_inference2(self): + inferred = self.ast['C']['meth3'].ilookup('d') + obj1 = next(inferred) + self.assertIsInstance(obj1, nodes.Const) + self.assertEqual(obj1.value, 4) + obj1 = next(inferred) + self.assertIs(obj1, util.YES, obj1) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_inference_restrictions(self): + inferred = test_utils.get_name_node(self.ast['C']['meth1'], 'arg1').infer() + obj1 = next(inferred) + self.assertIs(obj1, util.YES, obj1) + self.assertRaises(StopIteration, partial(next, inferred)) + + def test_ancestors_inference(self): + code = ''' + class A(object): #@ + pass + + class A(A): #@ + pass + ''' + a1, a2 = test_utils.extract_node(code, __name__) + a2_ancestors = list(a2.ancestors()) + self.assertEqual(len(a2_ancestors), 2) + self.assertIs(a2_ancestors[0], a1) + + def test_ancestors_inference2(self): + code = ''' + class A(object): #@ + pass + + class B(A): #@ + pass + + class A(B): #@ + pass + ''' + a1, b, a2 = test_utils.extract_node(code, __name__) + a2_ancestors = list(a2.ancestors()) + self.assertEqual(len(a2_ancestors), 3) + self.assertIs(a2_ancestors[0], b) + self.assertIs(a2_ancestors[1], a1) + + def test_f_arg_f(self): + code = ''' + def f(f=1): + return f + + a = f() + ''' + ast = parse(code, __name__) + a = ast['a'] + a_inferred = a.inferred() + self.assertEqual(a_inferred[0].value, 1) + self.assertEqual(len(a_inferred), 1) + + def test_infered_warning(self): + code = ''' + def f(f=1): + return f + + a = f() + ''' + ast = parse(code, __name__) + a = ast['a'] + + warnings.simplefilter('always') + with warnings.catch_warnings(record=True) as w: + a.infered() + self.assertIsInstance(w[0].message, PendingDeprecationWarning) + + def test_exc_ancestors(self): + code = ''' + def f(): + raise __(NotImplementedError) + ''' + error = test_utils.extract_node(code, __name__) + nie = error.inferred()[0] + self.assertIsInstance(nie, nodes.ClassDef) + nie_ancestors = [c.name for c in nie.ancestors()] + if sys.version_info < (3, 0): + self.assertEqual(nie_ancestors, ['RuntimeError', 'StandardError', 'Exception', 'BaseException', 'object']) + else: + self.assertEqual(nie_ancestors, ['RuntimeError', 'Exception', 'BaseException', 'object']) + + def test_except_inference(self): + code = ''' + try: + print (hop) + except NameError as ex: + ex1 = ex + except Exception as ex: + ex2 = ex + raise + ''' + ast = parse(code, __name__) + ex1 = ast['ex1'] + ex1_infer = ex1.infer() + ex1 = next(ex1_infer) + self.assertIsInstance(ex1, Instance) + self.assertEqual(ex1.name, 'NameError') + self.assertRaises(StopIteration, partial(next, ex1_infer)) + ex2 = ast['ex2'] + ex2_infer = ex2.infer() + ex2 = next(ex2_infer) + self.assertIsInstance(ex2, Instance) + self.assertEqual(ex2.name, 'Exception') + self.assertRaises(StopIteration, partial(next, ex2_infer)) + + def test_del1(self): + code = ''' + del undefined_attr + ''' + delete = test_utils.extract_node(code, __name__) + self.assertRaises(InferenceError, delete.infer) + + def test_del2(self): + code = ''' + a = 1 + b = a + del a + c = a + a = 2 + d = a + ''' + ast = parse(code, __name__) + n = ast['b'] + n_infer = n.infer() + inferred = next(n_infer) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, 1) + self.assertRaises(StopIteration, partial(next, n_infer)) + n = ast['c'] + n_infer = n.infer() + self.assertRaises(InferenceError, partial(next, n_infer)) + n = ast['d'] + n_infer = n.infer() + inferred = next(n_infer) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, 2) + self.assertRaises(StopIteration, partial(next, n_infer)) + + def test_builtin_types(self): + code = ''' + l = [1] + t = (2,) + d = {} + s = '' + s2 = '_' + ''' + ast = parse(code, __name__) + n = ast['l'] + inferred = next(n.infer()) + self.assertIsInstance(inferred, nodes.List) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred.getitem(0).value, 1) + self.assertIsInstance(inferred._proxied, nodes.ClassDef) + self.assertEqual(inferred._proxied.name, 'list') + self.assertIn('append', inferred._proxied._locals) + n = ast['t'] + inferred = next(n.infer()) + self.assertIsInstance(inferred, nodes.Tuple) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred.getitem(0).value, 2) + self.assertIsInstance(inferred._proxied, nodes.ClassDef) + self.assertEqual(inferred._proxied.name, 'tuple') + n = ast['d'] + inferred = next(n.infer()) + self.assertIsInstance(inferred, nodes.Dict) + self.assertIsInstance(inferred, Instance) + self.assertIsInstance(inferred._proxied, nodes.ClassDef) + self.assertEqual(inferred._proxied.name, 'dict') + self.assertIn('get', inferred._proxied._locals) + n = ast['s'] + inferred = next(n.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred.name, 'str') + self.assertIn('lower', inferred._proxied._locals) + n = ast['s2'] + inferred = next(n.infer()) + self.assertEqual(inferred.getitem(0).value, '_') + + code = 's = {1}' + ast = parse(code, __name__) + n = ast['s'] + inferred = next(n.infer()) + self.assertIsInstance(inferred, nodes.Set) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred.name, 'set') + self.assertIn('remove', inferred._proxied._locals) + + @test_utils.require_version(maxver='3.0') + def test_unicode_type(self): + code = '''u = u""''' + ast = parse(code, __name__) + n = ast['u'] + inferred = next(n.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred.name, 'unicode') + self.assertIn('lower', inferred._proxied._locals) + + @unittest.expectedFailure + def test_descriptor_are_callable(self): + code = ''' + class A: + statm = staticmethod(open) + clsm = classmethod('whatever') + ''' + ast = parse(code, __name__) + statm = next(ast['A'].igetattr('statm')) + self.assertTrue(statm.callable()) + clsm = next(ast['A'].igetattr('clsm')) + self.assertFalse(clsm.callable()) + + def test_bt_ancestor_crash(self): + code = ''' + class Warning(Warning): + pass + ''' + ast = parse(code, __name__) + w = ast['Warning'] + ancestors = w.ancestors() + ancestor = next(ancestors) + self.assertEqual(ancestor.name, 'Warning') + self.assertEqual(ancestor.root().name, EXC_MODULE) + ancestor = next(ancestors) + self.assertEqual(ancestor.name, 'Exception') + self.assertEqual(ancestor.root().name, EXC_MODULE) + ancestor = next(ancestors) + self.assertEqual(ancestor.name, 'BaseException') + self.assertEqual(ancestor.root().name, EXC_MODULE) + ancestor = next(ancestors) + self.assertEqual(ancestor.name, 'object') + self.assertEqual(ancestor.root().name, BUILTINS) + self.assertRaises(StopIteration, partial(next, ancestors)) + + def test_qqch(self): + code = ''' + from astroid.modutils import load_module_from_name + xxx = load_module_from_name('__pkginfo__') + ''' + ast = parse(code, __name__) + xxx = ast['xxx'] + self.assertSetEqual({n.__class__ for n in xxx.inferred()}, + {nodes.Const, util.YES.__class__}) + + def test_method_argument(self): + code = ''' + class ErudiEntitySchema: + """a entity has a type, a set of subject and or object relations""" + def __init__(self, e_type, **kwargs): + kwargs['e_type'] = e_type.capitalize().encode() + + def meth(self, e_type, *args, **kwargs): + kwargs['e_type'] = e_type.capitalize().encode() + print(args) + ''' + ast = parse(code, __name__) + arg = test_utils.get_name_node(ast['ErudiEntitySchema']['__init__'], 'e_type') + self.assertEqual([n.__class__ for n in arg.infer()], + [util.YES.__class__]) + arg = test_utils.get_name_node(ast['ErudiEntitySchema']['__init__'], 'kwargs') + self.assertEqual([n.__class__ for n in arg.infer()], + [nodes.Dict]) + arg = test_utils.get_name_node(ast['ErudiEntitySchema']['meth'], 'e_type') + self.assertEqual([n.__class__ for n in arg.infer()], + [util.YES.__class__]) + arg = test_utils.get_name_node(ast['ErudiEntitySchema']['meth'], 'args') + self.assertEqual([n.__class__ for n in arg.infer()], + [nodes.Tuple]) + arg = test_utils.get_name_node(ast['ErudiEntitySchema']['meth'], 'kwargs') + self.assertEqual([n.__class__ for n in arg.infer()], + [nodes.Dict]) + + def test_tuple_then_list(self): + code = ''' + def test_view(rql, vid, tags=()): + tags = list(tags) + __(tags).append(vid) + ''' + name = test_utils.extract_node(code, __name__) + it = name.infer() + tags = next(it) + self.assertIsInstance(tags, nodes.List) + self.assertEqual(tags.elts, []) + with self.assertRaises(StopIteration): + next(it) + + def test_mulassign_inference(self): + code = ''' + def first_word(line): + """Return the first word of a line""" + + return line.split()[0] + + def last_word(line): + """Return last word of a line""" + + return line.split()[-1] + + def process_line(word_pos): + """Silly function: returns (ok, callable) based on argument. + + For test purpose only. + """ + + if word_pos > 0: + return (True, first_word) + elif word_pos < 0: + return (True, last_word) + else: + return (False, None) + + if __name__ == '__main__': + + line_number = 0 + for a_line in file('test_callable.py'): + tupletest = process_line(line_number) + (ok, fct) = process_line(line_number) + if ok: + fct(a_line) + ''' + ast = parse(code, __name__) + self.assertEqual(len(list(ast['process_line'].infer_call_result(None))), 3) + self.assertEqual(len(list(ast['tupletest'].infer())), 3) + values = ['FunctionDef(first_word)', 'FunctionDef(last_word)', 'Const(NoneType)'] + self.assertEqual([str(inferred) + for inferred in ast['fct'].infer()], values) + + def test_float_complex_ambiguity(self): + code = ''' + 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() + ''' + func, retval = test_utils.extract_node(code, __name__) + self.assertEqual( + [i.value for i in func.ilookup('something')], + [1.0, 1.0j]) + self.assertEqual( + [i.value for i in retval.infer()], + [1.0, 1.0j]) + + def test_lookup_cond_branches(self): + code = ''' + def no_conjugate_member(magic_flag): + """should not raise E1101 on something.conjugate""" + something = 1.0 + if magic_flag: + something = 1.0j + return something.conjugate() + ''' + ast = parse(code, __name__) + values = [i.value for i in test_utils.get_name_node(ast, 'something', -1).infer()] + self.assertEqual(values, [1.0, 1.0j]) + + + def test_simple_subscript(self): + code = ''' + class A(object): + def __getitem__(self, index): + return index + 42 + [1, 2, 3][0] #@ + (1, 2, 3)[1] #@ + (1, 2, 3)[-1] #@ + [1, 2, 3][0] + (2, )[0] + (3, )[-1] #@ + e = {'key': 'value'} + e['key'] #@ + "first"[0] #@ + list([1, 2, 3])[-1] #@ + tuple((4, 5, 6))[2] #@ + A()[0] #@ + A()[-1] #@ + ''' + ast_nodes = test_utils.extract_node(code, __name__) + expected = [1, 2, 3, 6, 'value', 'f', 3, 6, 42, 41] + for node, expected_value in zip(ast_nodes, expected): + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, expected_value) + + def test_invalid_subscripts(self): + ast_nodes = test_utils.extract_node(''' + class NoGetitem(object): + pass + class InvalidGetitem(object): + def __getitem__(self): pass + class InvalidGetitem2(object): + __getitem__ = 42 + NoGetitem()[4] #@ + InvalidGetitem()[5] #@ + InvalidGetitem2()[10] #@ + [1, 2, 3][None] #@ + 'lala'['bala'] #@ + ''') + for node in ast_nodes[:3]: + self.assertRaises(InferenceError, next, node.infer()) + for node in ast_nodes[3:]: + self.assertEqual(next(node.infer()), util.YES) + + def test_bytes_subscript(self): + node = test_utils.extract_node('''b'a'[0]''') + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Const) + if six.PY2: + self.assertEqual(inferred.value, 'a') + else: + self.assertEqual(inferred.value, 97) + + #def test_simple_tuple(self): + #"""test case for a simple tuple value""" + ## XXX tuple inference is not implemented ... + #code = """ +#a = (1,) +#b = (22,) +#some = a + b +#""" + #ast = builder.string_build(code, __name__, __file__) + #self.assertEqual(ast['some'].infer.next().as_string(), "(1, 22)") + + def test_simple_for(self): + code = ''' + for a in [1, 2, 3]: + print (a) + for b,c in [(1,2), (3,4)]: + print (b) + print (c) + + print ([(d,e) for e,d in ([1,2], [3,4])]) + ''' + ast = parse(code, __name__) + self.assertEqual([i.value for i in + test_utils.get_name_node(ast, 'a', -1).infer()], [1, 2, 3]) + self.assertEqual([i.value for i in + test_utils.get_name_node(ast, 'b', -1).infer()], [1, 3]) + self.assertEqual([i.value for i in + test_utils.get_name_node(ast, 'c', -1).infer()], [2, 4]) + self.assertEqual([i.value for i in + test_utils.get_name_node(ast, 'd', -1).infer()], [2, 4]) + self.assertEqual([i.value for i in + test_utils.get_name_node(ast, 'e', -1).infer()], [1, 3]) + + def test_simple_for_genexpr(self): + code = ''' + print ((d,e) for e,d in ([1,2], [3,4])) + ''' + ast = parse(code, __name__) + self.assertEqual([i.value for i in + test_utils.get_name_node(ast, 'd', -1).infer()], [2, 4]) + self.assertEqual([i.value for i in + test_utils.get_name_node(ast, 'e', -1).infer()], [1, 3]) + + + def test_builtin_help(self): + code = ''' + help() + ''' + # XXX failing since __builtin__.help assignment has + # been moved into a function... + node = test_utils.extract_node(code, __name__) + inferred = list(node.func.infer()) + self.assertEqual(len(inferred), 1, inferred) + self.assertIsInstance(inferred[0], Instance) + self.assertEqual(inferred[0].name, "_Helper") + + def test_builtin_open(self): + code = ''' + open("toto.txt") + ''' + node = test_utils.extract_node(code, __name__).func + inferred = list(node.infer()) + self.assertEqual(len(inferred), 1) + if hasattr(sys, 'pypy_version_info'): + self.assertIsInstance(inferred[0], nodes.ClassDef) + self.assertEqual(inferred[0].name, 'file') + else: + self.assertIsInstance(inferred[0], nodes.FunctionDef) + self.assertEqual(inferred[0].name, 'open') + + def test_callfunc_context_func(self): + code = ''' + def mirror(arg=None): + return arg + + un = mirror(1) + ''' + ast = parse(code, __name__) + inferred = list(ast.igetattr('un')) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.Const) + self.assertEqual(inferred[0].value, 1) + + def test_callfunc_context_lambda(self): + code = ''' + mirror = lambda x=None: x + + un = mirror(1) + ''' + ast = parse(code, __name__) + inferred = list(ast.igetattr('mirror')) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.Lambda) + inferred = list(ast.igetattr('un')) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.Const) + self.assertEqual(inferred[0].value, 1) + + def test_factory_method(self): + code = ''' + class Super(object): + @classmethod + def instance(cls): + return cls() + + class Sub(Super): + def method(self): + print ('method called') + + sub = Sub.instance() + ''' + ast = parse(code, __name__) + inferred = list(ast.igetattr('sub')) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], Instance) + self.assertEqual(inferred[0]._proxied.name, 'Sub') + + + def test_import_as(self): + code = ''' + import os.path as osp + print (osp.dirname(__file__)) + + from os.path import exists as e + assert e(__file__) + + from new import code as make_code + print (make_code) + ''' + ast = parse(code, __name__) + inferred = list(ast.igetattr('osp')) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.Module) + self.assertEqual(inferred[0].name, 'os.path') + inferred = list(ast.igetattr('e')) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.FunctionDef) + self.assertEqual(inferred[0].name, 'exists') + if sys.version_info >= (3, 0): + self.skipTest(' module has been removed') + inferred = list(ast.igetattr('make_code')) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], Instance) + self.assertEqual(str(inferred[0]), + 'Instance of %s.type' % BUILTINS) + + def _test_const_inferred(self, node, value): + inferred = list(node.infer()) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.Const) + self.assertEqual(inferred[0].value, value) + + def test_unary_not(self): + for code in ('a = not (1,); b = not ()', + 'a = not {1:2}; b = not {}'): + ast = builder.string_build(code, __name__, __file__) + self._test_const_inferred(ast['a'], False) + self._test_const_inferred(ast['b'], True) + + @test_utils.require_version(minver='3.5') + def test_matmul(self): + node = test_utils.extract_node(''' + class Array: + def __matmul__(self, other): + return 42 + Array() @ Array() #@ + ''') + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, 42) + + def test_binary_op_int_add(self): + ast = builder.string_build('a = 1 + 2', __name__, __file__) + self._test_const_inferred(ast['a'], 3) + + def test_binary_op_int_sub(self): + ast = builder.string_build('a = 1 - 2', __name__, __file__) + self._test_const_inferred(ast['a'], -1) + + def test_binary_op_float_div(self): + ast = builder.string_build('a = 1 / 2.', __name__, __file__) + self._test_const_inferred(ast['a'], 1 / 2.) + + def test_binary_op_str_mul(self): + ast = builder.string_build('a = "*" * 40', __name__, __file__) + self._test_const_inferred(ast['a'], "*" * 40) + + def test_binary_op_bitand(self): + ast = builder.string_build('a = 23&20', __name__, __file__) + self._test_const_inferred(ast['a'], 23&20) + + def test_binary_op_bitor(self): + ast = builder.string_build('a = 23|8', __name__, __file__) + self._test_const_inferred(ast['a'], 23|8) + + def test_binary_op_bitxor(self): + ast = builder.string_build('a = 23^9', __name__, __file__) + self._test_const_inferred(ast['a'], 23^9) + + def test_binary_op_shiftright(self): + ast = builder.string_build('a = 23 >>1', __name__, __file__) + self._test_const_inferred(ast['a'], 23>>1) + + def test_binary_op_shiftleft(self): + ast = builder.string_build('a = 23 <<1', __name__, __file__) + self._test_const_inferred(ast['a'], 23<<1) + + + def test_binary_op_list_mul(self): + for code in ('a = [[]] * 2', 'a = 2 * [[]]'): + ast = builder.string_build(code, __name__, __file__) + inferred = list(ast['a'].infer()) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.List) + self.assertEqual(len(inferred[0].elts), 2) + self.assertIsInstance(inferred[0].elts[0], nodes.List) + self.assertIsInstance(inferred[0].elts[1], nodes.List) + + def test_binary_op_list_mul_none(self): + 'test correct handling on list multiplied by None' + ast = builder.string_build('a = [1] * None\nb = [1] * "r"') + inferred = ast['a'].inferred() + self.assertEqual(len(inferred), 1) + self.assertEqual(inferred[0], util.YES) + inferred = ast['b'].inferred() + self.assertEqual(len(inferred), 1) + self.assertEqual(inferred[0], util.YES) + + def test_binary_op_list_mul_int(self): + 'test correct handling on list multiplied by int when there are more than one' + code = ''' + from ctypes import c_int + seq = [c_int()] * 4 + ''' + ast = parse(code, __name__) + inferred = ast['seq'].inferred() + self.assertEqual(len(inferred), 1) + listval = inferred[0] + self.assertIsInstance(listval, nodes.List) + self.assertEqual(len(listval.itered()), 4) + + def test_binary_op_tuple_add(self): + ast = builder.string_build('a = (1,) + (2,)', __name__, __file__) + inferred = list(ast['a'].infer()) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.Tuple) + self.assertEqual(len(inferred[0].elts), 2) + self.assertEqual(inferred[0].elts[0].value, 1) + self.assertEqual(inferred[0].elts[1].value, 2) + + def test_binary_op_custom_class(self): + code = ''' + class myarray: + def __init__(self, array): + self.array = array + def __mul__(self, x): + return myarray([2,4,6]) + def astype(self): + return "ASTYPE" + + def randint(maximum): + if maximum is not None: + return myarray([1,2,3]) * 2 + else: + return int(5) + + x = randint(1) + ''' + ast = parse(code, __name__) + inferred = list(ast.igetattr('x')) + self.assertEqual(len(inferred), 2) + value = [str(v) for v in inferred] + # The __name__ trick here makes it work when invoked directly + # (__name__ == '__main__') and through pytest (__name__ == + # 'unittest_inference') + self.assertEqual(value, ['Instance of %s.myarray' % __name__, + 'Instance of %s.int' % BUILTINS]) + + def test_nonregr_lambda_arg(self): + code = ''' + def f(g = lambda: None): + __(g()).x +''' + callfuncnode = test_utils.extract_node(code) + inferred = list(callfuncnode.infer()) + self.assertEqual(len(inferred), 2, inferred) + inferred.remove(util.YES) + self.assertIsInstance(inferred[0], nodes.Const) + self.assertIsNone(inferred[0].value) + + def test_nonregr_getitem_empty_tuple(self): + code = ''' + def f(x): + a = ()[x] + ''' + ast = parse(code, __name__) + inferred = list(ast['f'].ilookup('a')) + self.assertEqual(len(inferred), 1) + self.assertEqual(inferred[0], util.YES) + + def test_nonregr_instance_attrs(self): + """non regression for instance_attrs infinite loop : pylint / #4""" + + code = """ + class Foo(object): + + def set_42(self): + self.attr = 42 + + class Bar(Foo): + + def __init__(self): + self.attr = 41 + """ + ast = parse(code, __name__) + foo_class = ast['Foo'] + bar_class = ast['Bar'] + bar_self = ast['Bar']['__init__']['self'] + assattr = bar_class._instance_attrs['attr'][0] + self.assertEqual(len(foo_class._instance_attrs['attr']), 1) + self.assertEqual(len(bar_class._instance_attrs['attr']), 1) + self.assertEqual(bar_class._instance_attrs, {'attr': [assattr]}) + # call 'instance_attr' via 'Instance.getattr' to trigger the bug: + instance = bar_self.inferred()[0] + instance.getattr('attr') + self.assertEqual(len(bar_class._instance_attrs['attr']), 1) + self.assertEqual(len(foo_class._instance_attrs['attr']), 1) + self.assertEqual(bar_class._instance_attrs, {'attr': [assattr]}) + + def test_python25_generator_exit(self): + # pylint: disable=redefined-variable-type + buffer = six.StringIO() + sys.stderr = buffer + try: + data = "b = {}[str(0)+''].a" + ast = builder.string_build(data, __name__, __file__) + list(ast['b'].infer()) + output = buffer.getvalue() + finally: + sys.stderr = sys.__stderr__ + # I have no idea how to test for this in another way... + msg = ("Exception exceptions.RuntimeError: " + "'generator ignored GeneratorExit' in " + "ignored") + self.assertNotIn("RuntimeError", output, msg) + + def test_python25_no_relative_import(self): + ast = resources.build_file('data/package/absimport.py') + self.assertTrue(ast.absolute_import_activated(), True) + inferred = next(test_utils.get_name_node(ast, 'import_package_subpackage_module').infer()) + # failed to import since absolute_import is activated + self.assertIs(inferred, util.YES) + + def test_nonregr_absolute_import(self): + ast = resources.build_file('data/absimp/string.py', 'data.absimp.string') + self.assertTrue(ast.absolute_import_activated(), True) + inferred = next(test_utils.get_name_node(ast, 'string').infer()) + self.assertIsInstance(inferred, nodes.Module) + self.assertEqual(inferred.name, 'string') + self.assertIn('ascii_letters', inferred._locals) + + def test_mechanize_open(self): + try: + import mechanize # pylint: disable=unused-variable + except ImportError: + self.skipTest('require mechanize installed') + data = ''' + from mechanize import Browser + print(Browser) + b = Browser() + ''' + ast = parse(data, __name__) + browser = next(test_utils.get_name_node(ast, 'Browser').infer()) + self.assertIsInstance(browser, nodes.ClassDef) + bopen = list(browser.igetattr('open')) + self.skipTest('the commit said: "huum, see that later"') + self.assertEqual(len(bopen), 1) + self.assertIsInstance(bopen[0], nodes.FunctionDef) + self.assertTrue(bopen[0].callable()) + b = next(test_utils.get_name_node(ast, 'b').infer()) + self.assertIsInstance(b, Instance) + bopen = list(b.igetattr('open')) + self.assertEqual(len(bopen), 1) + self.assertIsInstance(bopen[0], BoundMethod) + self.assertTrue(bopen[0].callable()) + + def test_property(self): + code = ''' + from smtplib import SMTP + class SendMailController(object): + + @property + def smtp(self): + return SMTP(mailhost, port) + + @property + def me(self): + return self + + my_smtp = SendMailController().smtp + my_me = SendMailController().me + ''' + decorators = set(['%s.property' % BUILTINS]) + ast = parse(code, __name__) + self.assertEqual(ast['SendMailController']['smtp'].decoratornames(), + decorators) + propinferred = list(ast.body[2].value.infer()) + self.assertEqual(len(propinferred), 1) + propinferred = propinferred[0] + self.assertIsInstance(propinferred, Instance) + self.assertEqual(propinferred.name, 'SMTP') + self.assertEqual(propinferred.root().name, 'smtplib') + self.assertEqual(ast['SendMailController']['me'].decoratornames(), + decorators) + propinferred = list(ast.body[3].value.infer()) + self.assertEqual(len(propinferred), 1) + propinferred = propinferred[0] + self.assertIsInstance(propinferred, Instance) + self.assertEqual(propinferred.name, 'SendMailController') + self.assertEqual(propinferred.root().name, __name__) + + def test_im_func_unwrap(self): + code = ''' + class EnvBasedTC: + def pactions(self): + pass + pactions = EnvBasedTC.pactions.im_func + print (pactions) + + class EnvBasedTC2: + pactions = EnvBasedTC.pactions.im_func + print (pactions) + ''' + ast = parse(code, __name__) + pactions = test_utils.get_name_node(ast, 'pactions') + inferred = list(pactions.infer()) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.FunctionDef) + pactions = test_utils.get_name_node(ast['EnvBasedTC2'], 'pactions') + inferred = list(pactions.infer()) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.FunctionDef) + + def test_augassign(self): + code = ''' + a = 1 + a += 2 + print (a) + ''' + ast = parse(code, __name__) + inferred = list(test_utils.get_name_node(ast, 'a').infer()) + + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.Const) + self.assertEqual(inferred[0].value, 3) + + def test_nonregr_func_arg(self): + code = ''' + def foo(self, bar): + def baz(): + pass + def qux(): + return baz + spam = bar(None, qux) + print (spam) + ''' + ast = parse(code, __name__) + inferred = list(test_utils.get_name_node(ast['foo'], 'spam').infer()) + self.assertEqual(len(inferred), 1) + self.assertIs(inferred[0], util.YES) + + def test_nonregr_func_global(self): + code = ''' + active_application = None + + def get_active_application(): + global active_application + return active_application + + class Application(object): + def __init__(self): + global active_application + active_application = self + + class DataManager(object): + def __init__(self, app=None): + self.app = get_active_application() + def test(self): + p = self.app + print (p) + ''' + ast = parse(code, __name__) + inferred = list(Instance(ast['DataManager']).igetattr('app')) + self.assertEqual(len(inferred), 2, inferred) # None / Instance(Application) + inferred = list(test_utils.get_name_node(ast['DataManager']['test'], 'p').infer()) + self.assertEqual(len(inferred), 2, inferred) + for node in inferred: + if isinstance(node, Instance) and node.name == 'Application': + break + else: + self.fail('expected to find an instance of Application in %s' % inferred) + + def test_list_inference(self): + """#20464""" + code = ''' + from unknown import Unknown + A = [] + B = [] + + def test(): + xyz = [ + Unknown + ] + A + B + return xyz + + Z = test() + ''' + ast = parse(code, __name__) + inferred = next(ast['Z'].infer()) + self.assertIsInstance(inferred, nodes.List) + self.assertEqual(len(inferred.elts), 1) + self.assertIs(inferred.elts[0], util.YES) + + def test__new__(self): + code = ''' + class NewTest(object): + "doc" + def __new__(cls, arg): + self = object.__new__(cls) + self.arg = arg + return self + + n = NewTest() + ''' + ast = parse(code, __name__) + self.assertRaises(InferenceError, list, ast['NewTest'].igetattr('arg')) + n = next(ast['n'].infer()) + inferred = list(n.igetattr('arg')) + self.assertEqual(len(inferred), 1, inferred) + + def test__new__bound_methods(self): + node = test_utils.extract_node(''' + class cls(object): pass + cls().__new__(cls) #@ + ''') + inferred = next(node.infer()) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred._proxied, node.root()['cls']) + + def test_two_parents_from_same_module(self): + code = ''' + from data import nonregr + class Xxx(nonregr.Aaa, nonregr.Ccc): + "doc" + ''' + ast = parse(code, __name__) + parents = list(ast['Xxx'].ancestors()) + self.assertEqual(len(parents), 3, parents) # Aaa, Ccc, object + + def test_pluggable_inference(self): + code = ''' + from collections import namedtuple + A = namedtuple('A', ['a', 'b']) + B = namedtuple('B', 'a b') + ''' + ast = parse(code, __name__) + aclass = ast['A'].inferred()[0] + self.assertIsInstance(aclass, nodes.ClassDef) + self.assertIn('a', aclass._instance_attrs) + self.assertIn('b', aclass._instance_attrs) + bclass = ast['B'].inferred()[0] + self.assertIsInstance(bclass, nodes.ClassDef) + self.assertIn('a', bclass._instance_attrs) + self.assertIn('b', bclass._instance_attrs) + + def test_infer_arguments(self): + code = ''' + class A(object): + def first(self, arg1, arg2): + return arg1 + @classmethod + def method(cls, arg1, arg2): + return arg2 + @classmethod + def empty(cls): + return 2 + @staticmethod + def static(arg1, arg2): + return arg1 + def empty_method(self): + return [] + x = A().first(1, []) + y = A.method(1, []) + z = A.static(1, []) + empty = A.empty() + empty_list = A().empty_method() + ''' + ast = parse(code, __name__) + int_node = ast['x'].inferred()[0] + self.assertIsInstance(int_node, nodes.Const) + self.assertEqual(int_node.value, 1) + list_node = ast['y'].inferred()[0] + self.assertIsInstance(list_node, nodes.List) + int_node = ast['z'].inferred()[0] + self.assertIsInstance(int_node, nodes.Const) + self.assertEqual(int_node.value, 1) + empty = ast['empty'].inferred()[0] + self.assertIsInstance(empty, nodes.Const) + self.assertEqual(empty.value, 2) + empty_list = ast['empty_list'].inferred()[0] + self.assertIsInstance(empty_list, nodes.List) + + def test_infer_variable_arguments(self): + code = ''' + def test(*args, **kwargs): + vararg = args + kwarg = kwargs + ''' + ast = parse(code, __name__) + func = ast['test'] + vararg = func.body[0].value + kwarg = func.body[1].value + + kwarg_inferred = kwarg.inferred()[0] + self.assertIsInstance(kwarg_inferred, nodes.Dict) + self.assertIs(kwarg_inferred.parent, func.args) + + vararg_inferred = vararg.inferred()[0] + self.assertIsInstance(vararg_inferred, nodes.Tuple) + self.assertIs(vararg_inferred.parent, func.args) + + def test_infer_nested(self): + code = """ + def nested(): + from threading import Thread + + class NestedThread(Thread): + def __init__(self): + Thread.__init__(self) + """ + # Test that inferring Thread.__init__ looks up in + # the nested scope. + ast = parse(code, __name__) + callfunc = next(ast.nodes_of_class(nodes.Call)) + func = callfunc.func + inferred = func.inferred()[0] + self.assertIsInstance(inferred, UnboundMethod) + + def test_instance_binary_operations(self): + code = """ + class A(object): + def __mul__(self, other): + return 42 + a = A() + b = A() + sub = a - b + mul = a * b + """ + ast = parse(code, __name__) + sub = ast['sub'].inferred()[0] + mul = ast['mul'].inferred()[0] + self.assertIs(sub, util.YES) + self.assertIsInstance(mul, nodes.Const) + self.assertEqual(mul.value, 42) + + def test_instance_binary_operations_parent(self): + code = """ + class A(object): + def __mul__(self, other): + return 42 + class B(A): + pass + a = B() + b = B() + sub = a - b + mul = a * b + """ + ast = parse(code, __name__) + sub = ast['sub'].inferred()[0] + mul = ast['mul'].inferred()[0] + self.assertIs(sub, util. YES) + self.assertIsInstance(mul, nodes.Const) + self.assertEqual(mul.value, 42) + + def test_instance_binary_operations_multiple_methods(self): + code = """ + class A(object): + def __mul__(self, other): + return 42 + class B(A): + def __mul__(self, other): + return [42] + a = B() + b = B() + sub = a - b + mul = a * b + """ + ast = parse(code, __name__) + sub = ast['sub'].inferred()[0] + mul = ast['mul'].inferred()[0] + self.assertIs(sub, util.YES) + self.assertIsInstance(mul, nodes.List) + self.assertIsInstance(mul.elts[0], nodes.Const) + self.assertEqual(mul.elts[0].value, 42) + + def test_infer_call_result_crash(self): + code = """ + class A(object): + def __mul__(self, other): + return type.__new__() + + a = A() + b = A() + c = a * b + """ + ast = parse(code, __name__) + node = ast['c'] + self.assertEqual(node.inferred(), [util.YES]) + + def test_infer_empty_nodes(self): + # Should not crash when trying to infer EmptyNodes. + node = nodes.EmptyNode() + self.assertEqual(node.inferred(), [util.YES]) + + def test_infinite_loop_for_decorators(self): + # Issue https://bitbucket.org/logilab/astroid/issue/50 + # A decorator that returns itself leads to an infinite loop. + code = """ + def decorator(): + def wrapper(): + return decorator() + return wrapper + + @decorator() + def do_a_thing(): + pass + """ + ast = parse(code, __name__) + node = ast['do_a_thing'] + self.assertEqual(node.type, 'function') + + def test_no_infinite_ancestor_loop(self): + klass = test_utils.extract_node(""" + import datetime + + def method(self): + datetime.datetime = something() + + class something(datetime.datetime): #@ + pass + """) + self.assertIn( + 'object', + [base.name for base in klass.ancestors()]) + + def test_stop_iteration_leak(self): + code = """ + class Test: + def __init__(self): + self.config = {0: self.config[0]} + self.config[0].test() #@ + """ + ast = test_utils.extract_node(code, __name__) + expr = ast.func.expr + self.assertRaises(InferenceError, next, expr.infer()) + + def test_tuple_builtin_inference(self): + code = """ + var = (1, 2) + tuple() #@ + tuple([1]) #@ + tuple({2}) #@ + tuple("abc") #@ + tuple({1: 2}) #@ + tuple(var) #@ + tuple(tuple([1])) #@ + + tuple(None) #@ + tuple(1) #@ + tuple(1, 2) #@ + """ + ast = test_utils.extract_node(code, __name__) + + self.assertInferTuple(ast[0], []) + self.assertInferTuple(ast[1], [1]) + self.assertInferTuple(ast[2], [2]) + self.assertInferTuple(ast[3], ["a", "b", "c"]) + self.assertInferTuple(ast[4], [1]) + self.assertInferTuple(ast[5], [1, 2]) + self.assertInferTuple(ast[6], [1]) + + for node in ast[7:]: + inferred = next(node.infer()) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred.qname(), "{}.tuple".format(BUILTINS)) + + def test_frozenset_builtin_inference(self): + code = """ + var = (1, 2) + frozenset() #@ + frozenset([1, 2, 1]) #@ + frozenset({2, 3, 1}) #@ + frozenset("abcab") #@ + frozenset({1: 2}) #@ + frozenset(var) #@ + frozenset(tuple([1])) #@ + + frozenset(set(tuple([4, 5, set([2])]))) #@ + frozenset(None) #@ + frozenset(1) #@ + frozenset(1, 2) #@ + """ + ast = test_utils.extract_node(code, __name__) + + self.assertInferFrozenSet(ast[0], []) + self.assertInferFrozenSet(ast[1], [1, 2]) + self.assertInferFrozenSet(ast[2], [1, 2, 3]) + self.assertInferFrozenSet(ast[3], ["a", "b", "c"]) + self.assertInferFrozenSet(ast[4], [1]) + self.assertInferFrozenSet(ast[5], [1, 2]) + self.assertInferFrozenSet(ast[6], [1]) + + for node in ast[7:]: + infered = next(node.infer()) + self.assertIsInstance(infered, Instance) + self.assertEqual(infered.qname(), "{}.frozenset".format(BUILTINS)) + + def test_set_builtin_inference(self): + code = """ + var = (1, 2) + set() #@ + set([1, 2, 1]) #@ + set({2, 3, 1}) #@ + set("abcab") #@ + set({1: 2}) #@ + set(var) #@ + set(tuple([1])) #@ + + set(set(tuple([4, 5, set([2])]))) #@ + set(None) #@ + set(1) #@ + set(1, 2) #@ + """ + ast = test_utils.extract_node(code, __name__) + + self.assertInferSet(ast[0], []) + self.assertInferSet(ast[1], [1, 2]) + self.assertInferSet(ast[2], [1, 2, 3]) + self.assertInferSet(ast[3], ["a", "b", "c"]) + self.assertInferSet(ast[4], [1]) + self.assertInferSet(ast[5], [1, 2]) + self.assertInferSet(ast[6], [1]) + + for node in ast[7:]: + inferred = next(node.infer()) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred.qname(), "{}.set".format(BUILTINS)) + + def test_list_builtin_inference(self): + code = """ + var = (1, 2) + list() #@ + list([1, 2, 1]) #@ + list({2, 3, 1}) #@ + list("abcab") #@ + list({1: 2}) #@ + list(var) #@ + list(tuple([1])) #@ + + list(list(tuple([4, 5, list([2])]))) #@ + list(None) #@ + list(1) #@ + list(1, 2) #@ + """ + ast = test_utils.extract_node(code, __name__) + self.assertInferList(ast[0], []) + self.assertInferList(ast[1], [1, 1, 2]) + self.assertInferList(ast[2], [1, 2, 3]) + self.assertInferList(ast[3], ["a", "a", "b", "b", "c"]) + self.assertInferList(ast[4], [1]) + self.assertInferList(ast[5], [1, 2]) + self.assertInferList(ast[6], [1]) + + for node in ast[7:]: + inferred = next(node.infer()) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred.qname(), "{}.list".format(BUILTINS)) + + @test_utils.require_version('3.0') + def test_builtin_inference_py3k(self): + code = """ + list(b"abc") #@ + tuple(b"abc") #@ + set(b"abc") #@ + """ + ast = test_utils.extract_node(code, __name__) + self.assertInferList(ast[0], [97, 98, 99]) + self.assertInferTuple(ast[1], [97, 98, 99]) + self.assertInferSet(ast[2], [97, 98, 99]) + + def test_dict_inference(self): + code = """ + dict() #@ + dict(a=1, b=2, c=3) #@ + dict([(1, 2), (2, 3)]) #@ + dict([[1, 2], [2, 3]]) #@ + dict([(1, 2), [2, 3]]) #@ + dict([('a', 2)], b=2, c=3) #@ + dict({1: 2}) #@ + dict({'c': 2}, a=4, b=5) #@ + def func(): + return dict(a=1, b=2) + func() #@ + var = {'x': 2, 'y': 3} + dict(var, a=1, b=2) #@ + + dict([1, 2, 3]) #@ + dict([(1, 2), (1, 2, 3)]) #@ + dict({1: 2}, {1: 2}) #@ + dict({1: 2}, (1, 2)) #@ + dict({1: 2}, (1, 2), a=4) #@ + dict([(1, 2), ([4, 5], 2)]) #@ + dict([None, None]) #@ + + def using_unknown_kwargs(**kwargs): + return dict(**kwargs) + using_unknown_kwargs(a=1, b=2) #@ + """ + ast = test_utils.extract_node(code, __name__) + self.assertInferDict(ast[0], {}) + self.assertInferDict(ast[1], {'a': 1, 'b': 2, 'c': 3}) + for i in range(2, 5): + self.assertInferDict(ast[i], {1: 2, 2: 3}) + self.assertInferDict(ast[5], {'a': 2, 'b': 2, 'c': 3}) + self.assertInferDict(ast[6], {1: 2}) + self.assertInferDict(ast[7], {'c': 2, 'a': 4, 'b': 5}) + self.assertInferDict(ast[8], {'a': 1, 'b': 2}) + self.assertInferDict(ast[9], {'x': 2, 'y': 3, 'a': 1, 'b': 2}) + + for node in ast[10:]: + inferred = next(node.infer()) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred.qname(), "{}.dict".format(BUILTINS)) + + def test_dict_inference_kwargs(self): + ast_node = test_utils.extract_node('''dict(a=1, b=2, **{'c': 3})''') + self.assertInferDict(ast_node, {'a': 1, 'b': 2, 'c': 3}) + + @test_utils.require_version('3.5') + def test_dict_inference_for_multiple_starred(self): + pairs = [ + ('dict(a=1, **{"b": 2}, **{"c":3})', {'a':1, 'b':2, 'c':3}), + ('dict(a=1, **{"b": 2}, d=4, **{"c":3})', {'a':1, 'b':2, 'c':3, 'd':4}), + ('dict({"a":1}, b=2, **{"c":3})', {'a':1, 'b':2, 'c':3}), + ] + for code, expected_value in pairs: + node = test_utils.extract_node(code) + self.assertInferDict(node, expected_value) + + def test_dict_invalid_args(self): + invalid_values = [ + 'dict(*1)', + 'dict(**lala)', + 'dict(**[])', + ] + for invalid in invalid_values: + ast_node = test_utils.extract_node(invalid) + inferred = next(ast_node.infer()) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred.qname(), "{}.dict".format(BUILTINS)) + + def test_str_methods(self): + code = """ + ' '.decode() #@ + + ' '.encode() #@ + ' '.join('abcd') #@ + ' '.replace('a', 'b') #@ + ' '.format('a') #@ + ' '.capitalize() #@ + ' '.title() #@ + ' '.lower() #@ + ' '.upper() #@ + ' '.swapcase() #@ + ' '.strip() #@ + ' '.rstrip() #@ + ' '.lstrip() #@ + ' '.rjust() #@ + ' '.ljust() #@ + ' '.center() #@ + + ' '.index() #@ + ' '.find() #@ + ' '.count() #@ + """ + ast = test_utils.extract_node(code, __name__) + self.assertInferConst(ast[0], u'') + for i in range(1, 16): + self.assertInferConst(ast[i], '') + for i in range(16, 19): + self.assertInferConst(ast[i], 0) + + def test_unicode_methods(self): + code = """ + u' '.encode() #@ + + u' '.decode() #@ + u' '.join('abcd') #@ + u' '.replace('a', 'b') #@ + u' '.format('a') #@ + u' '.capitalize() #@ + u' '.title() #@ + u' '.lower() #@ + u' '.upper() #@ + u' '.swapcase() #@ + u' '.strip() #@ + u' '.rstrip() #@ + u' '.lstrip() #@ + u' '.rjust() #@ + u' '.ljust() #@ + u' '.center() #@ + + u' '.index() #@ + u' '.find() #@ + u' '.count() #@ + """ + ast = test_utils.extract_node(code, __name__) + self.assertInferConst(ast[0], '') + for i in range(1, 16): + self.assertInferConst(ast[i], u'') + for i in range(16, 19): + self.assertInferConst(ast[i], 0) + + def test_scope_lookup_same_attributes(self): + code = ''' + import collections + class Second(collections.Counter): + def collections(self): + return "second" + + ''' + ast = parse(code, __name__) + bases = ast['Second'].bases[0] + inferred = next(bases.infer()) + self.assertTrue(inferred) + self.assertIsInstance(inferred, nodes.ClassDef) + self.assertEqual(inferred.qname(), 'collections.Counter') + + +class ArgumentsTest(unittest.TestCase): + + @staticmethod + def _get_dict_value(inferred): + items = inferred.items + return sorted((key.value, value.value) for key, value in items) + + @staticmethod + def _get_tuple_value(inferred): + elts = inferred.elts + return tuple(elt.value for elt in elts) + + def test_args(self): + expected_values = [(), (1, ), (2, 3), (4, 5), + (3, ), (), (3, 4, 5), + (), (), (4, ), (4, 5), + (), (3, ), (), (), (3, ), (42, )] + ast_nodes = test_utils.extract_node(''' + def func(*args): + return args + func() #@ + func(1) #@ + func(2, 3) #@ + func(*(4, 5)) #@ + def func(a, b, *args): + return args + func(1, 2, 3) #@ + func(1, 2) #@ + func(1, 2, 3, 4, 5) #@ + def func(a, b, c=42, *args): + return args + func(1, 2) #@ + func(1, 2, 3) #@ + func(1, 2, 3, 4) #@ + func(1, 2, 3, 4, 5) #@ + func = lambda a, b, *args: args + func(1, 2) #@ + func(1, 2, 3) #@ + func = lambda a, b=42, *args: args + func(1) #@ + func(1, 2) #@ + func(1, 2, 3) #@ + func(1, 2, *(42, )) #@ + ''') + for node, expected_value in zip(ast_nodes, expected_values): + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Tuple) + self.assertEqual(self._get_tuple_value(inferred), expected_value) + + @test_utils.require_version('3.5') + def test_multiple_starred_args(self): + expected_values = [ + (1, 2, 3), + (1, 4, 2, 3, 5, 6, 7), + ] + ast_nodes = test_utils.extract_node(''' + def func(a, b, *args): + return args + func(1, 2, *(1, ), *(2, 3)) #@ + func(1, 2, *(1, ), 4, *(2, 3), 5, *(6, 7)) #@ + ''') + for node, expected_value in zip(ast_nodes, expected_values): + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Tuple) + self.assertEqual(self._get_tuple_value(inferred), expected_value) + + def test_defaults(self): + expected_values = [42, 3, 41, 42] + ast_nodes = test_utils.extract_node(''' + def func(a, b, c=42, *args): + return c + func(1, 2) #@ + func(1, 2, 3) #@ + func(1, 2, c=41) #@ + func(1, 2, 42, 41) #@ + ''') + for node, expected_value in zip(ast_nodes, expected_values): + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, expected_value) + + @test_utils.require_version('3.0') + def test_kwonly_args(self): + expected_values = [24, 24, 42, 23, 24, 24, 54] + ast_nodes = test_utils.extract_node(''' + def test(*, f, b): return f + test(f=24, b=33) #@ + def test(a, *, f): return f + test(1, f=24) #@ + def test(a, *, f=42): return f + test(1) #@ + test(1, f=23) #@ + def test(a, b, c=42, *args, f=24): + return f + test(1, 2, 3) #@ + test(1, 2, 3, 4) #@ + test(1, 2, 3, 4, 5, f=54) #@ + ''') + for node, expected_value in zip(ast_nodes, expected_values): + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, expected_value) + + def test_kwargs(self): + expected = [ + [('a', 1), ('b', 2), ('c', 3)], + [('a', 1)], + [('a', 'b')], + ] + ast_nodes = test_utils.extract_node(''' + def test(**kwargs): + return kwargs + test(a=1, b=2, c=3) #@ + test(a=1) #@ + test(**{'a': 'b'}) #@ + ''') + for node, expected_value in zip(ast_nodes, expected): + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Dict) + value = self._get_dict_value(inferred) + self.assertEqual(value, expected_value) + + def test_kwargs_and_other_named_parameters(self): + ast_nodes = test_utils.extract_node(''' + def test(a=42, b=24, **kwargs): + return kwargs + test(42, 24, c=3, d=4) #@ + test(49, b=24, d=4) #@ + test(a=42, b=33, c=3, d=42) #@ + test(a=42, **{'c':42}) #@ + ''') + expected_values = [ + [('c', 3), ('d', 4)], + [('d', 4)], + [('c', 3), ('d', 42)], + [('c', 42)], + ] + for node, expected_value in zip(ast_nodes, expected_values): + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Dict) + value = self._get_dict_value(inferred) + self.assertEqual(value, expected_value) + + def test_kwargs_access_by_name(self): + expected_values = [42, 42, 42, 24] + ast_nodes = test_utils.extract_node(''' + def test(**kwargs): + return kwargs['f'] + test(f=42) #@ + test(**{'f': 42}) #@ + test(**dict(f=42)) #@ + def test(f=42, **kwargs): + return kwargs['l'] + test(l=24) #@ + ''') + for ast_node, value in zip(ast_nodes, expected_values): + inferred = next(ast_node.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, value) + + def test_infer_call_result_invalid_dunder_call_on_instance(self): + ast_nodes = test_utils.extract_node(''' + class A: + __call__ = 42 + class B: + __call__ = A() + class C: + __call = None + A() #@ + B() #@ + C() #@ + ''') + for node in ast_nodes: + inferred = next(node.infer()) + self.assertRaises(InferenceError, next, inferred.infer_call_result(node)) + + + def test_subscript_inference_error(self): + # Used to raise StopIteration + ast_node = test_utils.extract_node(''' + class AttributeDict(dict): + def __getitem__(self, name): + return self + flow = AttributeDict() + flow['app'] = AttributeDict() + flow['app']['config'] = AttributeDict() + flow['app']['config']['doffing'] = AttributeDict() #@ + ''') + self.assertIsNone(util.safe_infer(ast_node.targets[0])) + + def test_classmethod_inferred_by_context(self): + ast_node = test_utils.extract_node(''' + class Super(object): + def instance(cls): + return cls() + instance = classmethod(instance) + + class Sub(Super): + def method(self): + return self + + # should see the Sub.instance() is returning a Sub + # instance, not a Super instance + Sub.instance().method() #@ + ''') + inferred = next(ast_node.infer()) + self.assertIsInstance(inferred, Instance) + self.assertEqual(inferred.name, 'Sub') + + @test_utils.require_version('3.5') + def test_multiple_kwargs(self): + expected_value = [ + ('a', 1), + ('b', 2), + ('c', 3), + ('d', 4), + ('f', 42), + ] + ast_node = test_utils.extract_node(''' + def test(**kwargs): + return kwargs + test(a=1, b=2, **{'c': 3}, **{'d': 4}, f=42) #@ + ''') + inferred = next(ast_node.infer()) + self.assertIsInstance(inferred, nodes.Dict) + value = self._get_dict_value(inferred) + self.assertEqual(value, expected_value) + + def test_kwargs_are_overriden(self): + ast_nodes = test_utils.extract_node(''' + def test(f): + return f + test(f=23, **{'f': 34}) #@ + def test(f=None): + return f + test(f=23, **{'f':23}) #@ + ''') + for ast_node in ast_nodes: + inferred = next(ast_node.infer()) + self.assertEqual(inferred, util.YES) + + def test_fail_to_infer_args(self): + ast_nodes = test_utils.extract_node(''' + def test(a, **kwargs): return a + test(*missing) #@ + test(*object) #@ + test(*1) #@ + + + def test(**kwargs): return kwargs + test(**miss) #@ + test(**(1, 2)) #@ + test(**1) #@ + test(**{misss:1}) #@ + test(**{object:1}) #@ + test(**{1:1}) #@ + test(**{'a':1, 'a':1}) #@ + + def test(a): return a + test() #@ + test(1, 2, 3) #@ + + from unknown import unknown + test(*unknown) #@ + def test(*args): return args + test(*unknown) #@ + ''') + for node in ast_nodes: + inferred = next(node.infer()) + self.assertEqual(inferred, util.YES) + +class CallSiteTest(unittest.TestCase): + + @staticmethod + def _call_site_from_call(call): + return arguments.CallSite.from_call(call) + + def _test_call_site_pair(self, code, expected_args, expected_keywords): + ast_node = test_utils.extract_node(code) + call_site = self._call_site_from_call(ast_node) + self.assertEqual(len(call_site.positional_arguments), len(expected_args)) + self.assertEqual([arg.value for arg in call_site.positional_arguments], + expected_args) + self.assertEqual(len(call_site.keyword_arguments), len(expected_keywords)) + for keyword, value in expected_keywords.items(): + self.assertIn(keyword, call_site.keyword_arguments) + self.assertEqual(call_site.keyword_arguments[keyword].value, value) + + def _test_call_site(self, pairs): + for pair in pairs: + self._test_call_site_pair(*pair) + + @test_utils.require_version('3.5') + def test_call_site_starred_args(self): + pairs = [ + ( + "f(*(1, 2), *(2, 3), *(3, 4), **{'a':1}, **{'b': 2})", + [1, 2, 2, 3, 3, 4], + {'a': 1, 'b': 2} + ), + ( + "f(1, 2, *(3, 4), 5, *(6, 7), f=24, **{'c':3})", + [1, 2, 3, 4, 5, 6, 7], + {'f':24, 'c': 3}, + ), + # Too many fs passed into. + ( + "f(f=24, **{'f':24})", [], {}, + ), + ] + self._test_call_site(pairs) + + def test_call_site(self): + pairs = [ + ( + "f(1, 2)", [1, 2], {} + ), + ( + "f(1, 2, *(1, 2))", [1, 2, 1, 2], {} + ), + ( + "f(a=1, b=2, c=3)", [], {'a':1, 'b':2, 'c':3} + ) + ] + self._test_call_site(pairs) + + def _test_call_site_valid_arguments(self, values, invalid): + for value in values: + ast_node = test_utils.extract_node(value) + call_site = self._call_site_from_call(ast_node) + self.assertEqual(call_site.has_invalid_arguments(), invalid) + + def test_call_site_valid_arguments(self): + values = [ + "f(*lala)", "f(*1)", "f(*object)", + ] + self._test_call_site_valid_arguments(values, invalid=True) + values = [ + "f()", "f(*(1, ))", "f(1, 2, *(2, 3))", + ] + self._test_call_site_valid_arguments(values, invalid=False) + + def test_duplicated_keyword_arguments(self): + ast_node = test_utils.extract_node('f(f=24, **{"f": 25})') + site = self._call_site_from_call(ast_node) + self.assertIn('f', site.duplicated_keywords) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_lookup.py b/pymode/libs/astroid/tests/unittest_lookup.py new file mode 100644 index 00000000..bd1786d5 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_lookup.py @@ -0,0 +1,352 @@ +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +"""tests for the astroid variable lookup capabilities +""" +import functools +import sys +import unittest + +from astroid import builder +from astroid import exceptions +from astroid import nodes +from astroid import scoped_nodes +from astroid import test_utils +from astroid import util +from astroid.tests import resources + + +class LookupTest(resources.SysPathSetup, unittest.TestCase): + + def setUp(self): + super(LookupTest, self).setUp() + self.module = resources.build_file('data/module.py', 'data.module') + self.module2 = resources.build_file('data/module2.py', 'data.module2') + self.nonregr = resources.build_file('data/nonregr.py', 'data.nonregr') + + def test_limit(self): + code = ''' + l = [a + for a,b in list] + + a = 1 + b = a + a = None + + def func(): + c = 1 + ''' + astroid = builder.parse(code, __name__) + # a & b + a = next(astroid.nodes_of_class(nodes.Name)) + self.assertEqual(a.lineno, 2) + if sys.version_info < (3, 0): + self.assertEqual(len(astroid.lookup('b')[1]), 1) + self.assertEqual(len(astroid.lookup('a')[1]), 1) + b = astroid._locals['b'][1] + else: + self.assertEqual(len(astroid.lookup('b')[1]), 1) + self.assertEqual(len(astroid.lookup('a')[1]), 1) + b = astroid._locals['b'][0] + + stmts = a.lookup('a')[1] + self.assertEqual(len(stmts), 1) + self.assertEqual(b.lineno, 6) + b_infer = b.infer() + b_value = next(b_infer) + self.assertEqual(b_value.value, 1) + # c + self.assertRaises(StopIteration, functools.partial(next, b_infer)) + func = astroid._locals['func'][0] + self.assertEqual(len(func.lookup('c')[1]), 1) + + def test_module(self): + astroid = builder.parse('pass', __name__) + # built-in objects + none = next(astroid.ilookup('None')) + self.assertIsNone(none.value) + obj = next(astroid.ilookup('object')) + self.assertIsInstance(obj, nodes.ClassDef) + self.assertEqual(obj.name, 'object') + self.assertRaises(exceptions.InferenceError, + functools.partial(next, astroid.ilookup('YOAA'))) + + # XXX + self.assertEqual(len(list(self.nonregr.ilookup('enumerate'))), 2) + + def test_class_ancestor_name(self): + code = ''' + class A: + pass + + class A(A): + pass + ''' + astroid = builder.parse(code, __name__) + cls1 = astroid._locals['A'][0] + cls2 = astroid._locals['A'][1] + name = next(cls2.nodes_of_class(nodes.Name)) + self.assertEqual(next(name.infer()), cls1) + + ### backport those test to inline code + def test_method(self): + method = self.module['YOUPI']['method'] + my_dict = next(method.ilookup('MY_DICT')) + self.assertTrue(isinstance(my_dict, nodes.Dict), my_dict) + none = next(method.ilookup('None')) + self.assertIsNone(none.value) + self.assertRaises(exceptions.InferenceError, + functools.partial(next, method.ilookup('YOAA'))) + + def test_function_argument_with_default(self): + make_class = self.module2['make_class'] + base = next(make_class.ilookup('base')) + self.assertTrue(isinstance(base, nodes.ClassDef), base.__class__) + self.assertEqual(base.name, 'YO') + self.assertEqual(base.root().name, 'data.module') + + def test_class(self): + klass = self.module['YOUPI'] + my_dict = next(klass.ilookup('MY_DICT')) + self.assertIsInstance(my_dict, nodes.Dict) + none = next(klass.ilookup('None')) + self.assertIsNone(none.value) + obj = next(klass.ilookup('object')) + self.assertIsInstance(obj, nodes.ClassDef) + self.assertEqual(obj.name, 'object') + self.assertRaises(exceptions.InferenceError, + functools.partial(next, klass.ilookup('YOAA'))) + + def test_inner_classes(self): + ddd = list(self.nonregr['Ccc'].ilookup('Ddd')) + self.assertEqual(ddd[0].name, 'Ddd') + + def test_loopvar_hiding(self): + astroid = builder.parse(""" + x = 10 + for x in range(5): + print (x) + + if x > 0: + print ('#' * x) + """, __name__) + xnames = [n for n in astroid.nodes_of_class(nodes.Name) if n.name == 'x'] + # inside the loop, only one possible assignment + self.assertEqual(len(xnames[0].lookup('x')[1]), 1) + # outside the loop, two possible assignments + self.assertEqual(len(xnames[1].lookup('x')[1]), 2) + self.assertEqual(len(xnames[2].lookup('x')[1]), 2) + + def test_list_comps(self): + astroid = builder.parse(""" + print ([ i for i in range(10) ]) + print ([ i for i in range(10) ]) + print ( list( i for i in range(10) ) ) + """, __name__) + xnames = [n for n in astroid.nodes_of_class(nodes.Name) if n.name == 'i'] + self.assertEqual(len(xnames[0].lookup('i')[1]), 1) + self.assertEqual(xnames[0].lookup('i')[1][0].lineno, 2) + self.assertEqual(len(xnames[1].lookup('i')[1]), 1) + self.assertEqual(xnames[1].lookup('i')[1][0].lineno, 3) + self.assertEqual(len(xnames[2].lookup('i')[1]), 1) + self.assertEqual(xnames[2].lookup('i')[1][0].lineno, 4) + + def test_list_comp_target(self): + """test the list comprehension target""" + astroid = builder.parse(""" + ten = [ var for var in range(10) ] + var + """) + var = astroid.body[1].value + if sys.version_info < (3, 0): + self.assertEqual(var.inferred(), [util.YES]) + else: + self.assertRaises(exceptions.UnresolvableName, var.inferred) + + def test_dict_comps(self): + astroid = builder.parse(""" + print ({ i: j for i in range(10) for j in range(10) }) + print ({ i: j for i in range(10) for j in range(10) }) + """, __name__) + xnames = [n for n in astroid.nodes_of_class(nodes.Name) if n.name == 'i'] + self.assertEqual(len(xnames[0].lookup('i')[1]), 1) + self.assertEqual(xnames[0].lookup('i')[1][0].lineno, 2) + self.assertEqual(len(xnames[1].lookup('i')[1]), 1) + self.assertEqual(xnames[1].lookup('i')[1][0].lineno, 3) + + xnames = [n for n in astroid.nodes_of_class(nodes.Name) if n.name == 'j'] + self.assertEqual(len(xnames[0].lookup('i')[1]), 1) + self.assertEqual(xnames[0].lookup('i')[1][0].lineno, 2) + self.assertEqual(len(xnames[1].lookup('i')[1]), 1) + self.assertEqual(xnames[1].lookup('i')[1][0].lineno, 3) + + def test_set_comps(self): + astroid = builder.parse(""" + print ({ i for i in range(10) }) + print ({ i for i in range(10) }) + """, __name__) + xnames = [n for n in astroid.nodes_of_class(nodes.Name) if n.name == 'i'] + self.assertEqual(len(xnames[0].lookup('i')[1]), 1) + self.assertEqual(xnames[0].lookup('i')[1][0].lineno, 2) + self.assertEqual(len(xnames[1].lookup('i')[1]), 1) + self.assertEqual(xnames[1].lookup('i')[1][0].lineno, 3) + + def test_set_comp_closure(self): + astroid = builder.parse(""" + ten = { var for var in range(10) } + var + """) + var = astroid.body[1].value + self.assertRaises(exceptions.UnresolvableName, var.inferred) + + def test_generator_attributes(self): + tree = builder.parse(""" + def count(): + "test" + yield 0 + + iterer = count() + num = iterer.next() + """) + next_node = tree.body[2].value.func + gener = next_node.expr.inferred()[0] + if sys.version_info < (3, 0): + self.assertIsInstance(gener.getattr('next')[0], nodes.FunctionDef) + else: + self.assertIsInstance(gener.getattr('__next__')[0], nodes.FunctionDef) + self.assertIsInstance(gener.getattr('send')[0], nodes.FunctionDef) + self.assertIsInstance(gener.getattr('throw')[0], nodes.FunctionDef) + self.assertIsInstance(gener.getattr('close')[0], nodes.FunctionDef) + + def test_explicit___name__(self): + code = ''' + class Pouet: + __name__ = "pouet" + p1 = Pouet() + + class PouetPouet(Pouet): pass + p2 = Pouet() + + class NoName: pass + p3 = NoName() + ''' + astroid = builder.parse(code, __name__) + p1 = next(astroid['p1'].infer()) + self.assertTrue(p1.getattr('__name__')) + p2 = next(astroid['p2'].infer()) + self.assertTrue(p2.getattr('__name__')) + self.assertTrue(astroid['NoName'].getattr('__name__')) + p3 = next(astroid['p3'].infer()) + self.assertRaises(exceptions.NotFoundError, p3.getattr, '__name__') + + def test_function_module_special(self): + astroid = builder.parse(''' + def initialize(linter): + """initialize linter with checkers in this package """ + package_load(linter, __path__[0]) + ''', 'data.__init__') + path = [n for n in astroid.nodes_of_class(nodes.Name) if n.name == '__path__'][0] + self.assertEqual(len(path.lookup('__path__')[1]), 1) + + def test_builtin_lookup(self): + self.assertEqual(scoped_nodes.builtin_lookup('__dict__')[1], ()) + intstmts = scoped_nodes.builtin_lookup('int')[1] + self.assertEqual(len(intstmts), 1) + self.assertIsInstance(intstmts[0], nodes.ClassDef) + self.assertEqual(intstmts[0].name, 'int') + self.assertIs(intstmts[0], nodes.const_factory(1)._proxied) + + def test_decorator_arguments_lookup(self): + code = ''' + def decorator(value): + def wrapper(function): + return function + return wrapper + + class foo: + member = 10 #@ + + @decorator(member) #This will cause pylint to complain + def test(self): + pass + ''' + member = test_utils.extract_node(code, __name__).targets[0] + it = member.infer() + obj = next(it) + self.assertIsInstance(obj, nodes.Const) + self.assertEqual(obj.value, 10) + self.assertRaises(StopIteration, functools.partial(next, it)) + + def test_inner_decorator_member_lookup(self): + code = ''' + class FileA: + def decorator(bla): + return bla + + @__(decorator) + def funcA(): + return 4 + ''' + decname = test_utils.extract_node(code, __name__) + it = decname.infer() + obj = next(it) + self.assertIsInstance(obj, nodes.FunctionDef) + self.assertRaises(StopIteration, functools.partial(next, it)) + + def test_static_method_lookup(self): + code = ''' + class FileA: + @staticmethod + def funcA(): + return 4 + + + class Test: + FileA = [1,2,3] + + def __init__(self): + print (FileA.funcA()) + ''' + astroid = builder.parse(code, __name__) + it = astroid['Test']['__init__'].ilookup('FileA') + obj = next(it) + self.assertIsInstance(obj, nodes.ClassDef) + self.assertRaises(StopIteration, functools.partial(next, it)) + + def test_global_delete(self): + code = ''' + def run2(): + f = Frobble() + + class Frobble: + pass + Frobble.mumble = True + + del Frobble + + def run1(): + f = Frobble() + ''' + astroid = builder.parse(code, __name__) + stmts = astroid['run2'].lookup('Frobbel')[1] + self.assertEqual(len(stmts), 0) + stmts = astroid['run1'].lookup('Frobbel')[1] + self.assertEqual(len(stmts), 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_manager.py b/pymode/libs/astroid/tests/unittest_manager.py new file mode 100644 index 00000000..452b759e --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_manager.py @@ -0,0 +1,216 @@ +# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +import os +import platform +import sys +import unittest + +import six + +from astroid import exceptions +from astroid import manager +from astroid.tests import resources + + +BUILTINS = six.moves.builtins.__name__ + + +def _get_file_from_object(obj): + if platform.python_implementation() == 'Jython': + return obj.__file__.split("$py.class")[0] + ".py" + if sys.version_info > (3, 0): + return obj.__file__ + if not obj.__file__.endswith(".py"): + return obj.__file__[:-1] + return obj.__file__ + + +class AstroidManagerTest(resources.SysPathSetup, + resources.AstroidCacheSetupMixin, + unittest.TestCase): + + def setUp(self): + super(AstroidManagerTest, self).setUp() + self.manager = manager.AstroidManager() + self.manager.clear_cache(self._builtins) # take care of borg + + def test_ast_from_file(self): + filepath = unittest.__file__ + astroid = self.manager.ast_from_file(filepath) + self.assertEqual(astroid.name, 'unittest') + self.assertIn('unittest', self.manager.astroid_cache) + + def test_ast_from_file_cache(self): + filepath = unittest.__file__ + self.manager.ast_from_file(filepath) + astroid = self.manager.ast_from_file('unhandledName', 'unittest') + self.assertEqual(astroid.name, 'unittest') + self.assertIn('unittest', self.manager.astroid_cache) + + def test_ast_from_file_astro_builder(self): + filepath = unittest.__file__ + astroid = self.manager.ast_from_file(filepath, None, True, True) + self.assertEqual(astroid.name, 'unittest') + self.assertIn('unittest', self.manager.astroid_cache) + + def test_ast_from_file_name_astro_builder_exception(self): + self.assertRaises(exceptions.AstroidBuildingException, + self.manager.ast_from_file, 'unhandledName') + + def test_do_not_expose_main(self): + obj = self.manager.ast_from_module_name('__main__') + self.assertEqual(obj.name, '__main__') + self.assertEqual(obj.items(), []) + + def test_ast_from_module_name(self): + astroid = self.manager.ast_from_module_name('unittest') + self.assertEqual(astroid.name, 'unittest') + self.assertIn('unittest', self.manager.astroid_cache) + + def test_ast_from_module_name_not_python_source(self): + astroid = self.manager.ast_from_module_name('time') + self.assertEqual(astroid.name, 'time') + self.assertIn('time', self.manager.astroid_cache) + self.assertEqual(astroid.pure_python, False) + + def test_ast_from_module_name_astro_builder_exception(self): + self.assertRaises(exceptions.AstroidBuildingException, + self.manager.ast_from_module_name, + 'unhandledModule') + + def _test_ast_from_zip(self, archive): + origpath = sys.path[:] + sys.modules.pop('mypypa', None) + archive_path = resources.find(archive) + sys.path.insert(0, archive_path) + try: + module = self.manager.ast_from_module_name('mypypa') + self.assertEqual(module.name, 'mypypa') + end = os.path.join(archive, 'mypypa') + self.assertTrue(module.source_file.endswith(end), + "%s doesn't endswith %s" % (module.source_file, end)) + finally: + # remove the module, else after importing egg, we don't get the zip + if 'mypypa' in self.manager.astroid_cache: + del self.manager.astroid_cache['mypypa'] + del self.manager._mod_file_cache[('mypypa', None)] + if archive_path in sys.path_importer_cache: + del sys.path_importer_cache[archive_path] + sys.path = origpath + + def test_ast_from_module_name_egg(self): + self._test_ast_from_zip( + os.path.sep.join(['data', os.path.normcase('MyPyPa-0.1.0-py2.5.egg')]) + ) + + def test_ast_from_module_name_zip(self): + self._test_ast_from_zip( + os.path.sep.join(['data', os.path.normcase('MyPyPa-0.1.0-py2.5.zip')]) + ) + + def test_zip_import_data(self): + """check if zip_import_data works""" + filepath = resources.find('data/MyPyPa-0.1.0-py2.5.zip/mypypa') + astroid = self.manager.zip_import_data(filepath) + self.assertEqual(astroid.name, 'mypypa') + + def test_zip_import_data_without_zipimport(self): + """check if zip_import_data return None without zipimport""" + self.assertEqual(self.manager.zip_import_data('path'), None) + + def test_file_from_module(self): + """check if the unittest filepath is equals to the result of the method""" + self.assertEqual( + _get_file_from_object(unittest), + self.manager.file_from_module_name('unittest', None)[0]) + + def test_file_from_module_name_astro_building_exception(self): + """check if the method launch a exception with a wrong module name""" + self.assertRaises(exceptions.AstroidBuildingException, + self.manager.file_from_module_name, 'unhandledModule', None) + + def test_ast_from_module(self): + astroid = self.manager.ast_from_module(unittest) + self.assertEqual(astroid.pure_python, True) + import time + astroid = self.manager.ast_from_module(time) + self.assertEqual(astroid.pure_python, False) + + def test_ast_from_module_cache(self): + """check if the module is in the cache manager""" + astroid = self.manager.ast_from_module(unittest) + self.assertEqual(astroid.name, 'unittest') + self.assertIn('unittest', self.manager.astroid_cache) + + def test_ast_from_class(self): + astroid = self.manager.ast_from_class(int) + self.assertEqual(astroid.name, 'int') + self.assertEqual(astroid.parent.frame().name, BUILTINS) + + astroid = self.manager.ast_from_class(object) + self.assertEqual(astroid.name, 'object') + self.assertEqual(astroid.parent.frame().name, BUILTINS) + self.assertIn('__setattr__', astroid) + + def test_ast_from_class_with_module(self): + """check if the method works with the module name""" + astroid = self.manager.ast_from_class(int, int.__module__) + self.assertEqual(astroid.name, 'int') + self.assertEqual(astroid.parent.frame().name, BUILTINS) + + astroid = self.manager.ast_from_class(object, object.__module__) + self.assertEqual(astroid.name, 'object') + self.assertEqual(astroid.parent.frame().name, BUILTINS) + self.assertIn('__setattr__', astroid) + + def test_ast_from_class_attr_error(self): + """give a wrong class at the ast_from_class method""" + self.assertRaises(exceptions.AstroidBuildingException, + self.manager.ast_from_class, None) + + def testFailedImportHooks(self): + def hook(modname): + if modname == 'foo.bar': + return unittest + else: + raise exceptions.AstroidBuildingException() + + with self.assertRaises(exceptions.AstroidBuildingException): + self.manager.ast_from_module_name('foo.bar') + self.manager.register_failed_import_hook(hook) + self.assertEqual(unittest, self.manager.ast_from_module_name('foo.bar')) + with self.assertRaises(exceptions.AstroidBuildingException): + self.manager.ast_from_module_name('foo.bar.baz') + del self.manager._failed_import_hooks[0] + + +class BorgAstroidManagerTC(unittest.TestCase): + + def test_borg(self): + """test that the AstroidManager is really a borg, i.e. that two different + instances has same cache""" + first_manager = manager.AstroidManager() + built = first_manager.ast_from_module_name(BUILTINS) + + second_manager = manager.AstroidManager() + second_built = second_manager.ast_from_module_name(BUILTINS) + self.assertIs(built, second_built) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_modutils.py b/pymode/libs/astroid/tests/unittest_modutils.py new file mode 100644 index 00000000..dffc3b8d --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_modutils.py @@ -0,0 +1,269 @@ +# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) any +# later version. +# +# astroid 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 Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +""" +unit tests for module modutils (module manipulation utilities) +""" +import os +import sys +import unittest + +from astroid import modutils +from astroid.tests import resources + + +def _get_file_from_object(obj): + return modutils._path_from_filename(obj.__file__) + + +class ModuleFileTest(unittest.TestCase): + package = "mypypa" + + def tearDown(self): + for k in list(sys.path_importer_cache.keys()): + if 'MyPyPa' in k: + del sys.path_importer_cache[k] + + def test_find_zipped_module(self): + mtype, mfile = modutils._module_file( + [self.package], [resources.find('data/MyPyPa-0.1.0-py2.5.zip')]) + self.assertEqual(mtype, modutils.PY_ZIPMODULE) + self.assertEqual(mfile.split(os.sep)[-3:], ["data", "MyPyPa-0.1.0-py2.5.zip", self.package]) + + def test_find_egg_module(self): + mtype, mfile = modutils._module_file( + [self.package], [resources.find('data/MyPyPa-0.1.0-py2.5.egg')]) + self.assertEqual(mtype, modutils.PY_ZIPMODULE) + self.assertEqual(mfile.split(os.sep)[-3:], ["data", "MyPyPa-0.1.0-py2.5.egg", self.package]) + + +class LoadModuleFromNameTest(unittest.TestCase): + """ load a python module from it's name """ + + def test_knownValues_load_module_from_name_1(self): + self.assertEqual(modutils.load_module_from_name('sys'), sys) + + def test_knownValues_load_module_from_name_2(self): + self.assertEqual(modutils.load_module_from_name('os.path'), os.path) + + def test_raise_load_module_from_name_1(self): + self.assertRaises(ImportError, + modutils.load_module_from_name, 'os.path', use_sys=0) + + +class GetModulePartTest(unittest.TestCase): + """given a dotted name return the module part of the name""" + + def test_knownValues_get_module_part_1(self): + self.assertEqual(modutils.get_module_part('astroid.modutils'), + 'astroid.modutils') + + def test_knownValues_get_module_part_2(self): + self.assertEqual(modutils.get_module_part('astroid.modutils.get_module_part'), + 'astroid.modutils') + + def test_knownValues_get_module_part_3(self): + """relative import from given file""" + self.assertEqual(modutils.get_module_part('node_classes.AssName', + modutils.__file__), 'node_classes') + + def test_knownValues_get_compiled_module_part(self): + self.assertEqual(modutils.get_module_part('math.log10'), 'math') + self.assertEqual(modutils.get_module_part('math.log10', __file__), 'math') + + def test_knownValues_get_builtin_module_part(self): + self.assertEqual(modutils.get_module_part('sys.path'), 'sys') + self.assertEqual(modutils.get_module_part('sys.path', '__file__'), 'sys') + + def test_get_module_part_exception(self): + self.assertRaises(ImportError, modutils.get_module_part, 'unknown.module', + modutils.__file__) + + +class ModPathFromFileTest(unittest.TestCase): + """ given an absolute file path return the python module's path as a list """ + + def test_knownValues_modpath_from_file_1(self): + from xml.etree import ElementTree + self.assertEqual(modutils.modpath_from_file(ElementTree.__file__), + ['xml', 'etree', 'ElementTree']) + + def test_knownValues_modpath_from_file_2(self): + self.assertEqual(modutils.modpath_from_file('unittest_modutils.py', + {os.getcwd(): 'arbitrary.pkg'}), + ['arbitrary', 'pkg', 'unittest_modutils']) + + def test_raise_modpath_from_file_Exception(self): + self.assertRaises(Exception, modutils.modpath_from_file, '/turlututu') + + +class LoadModuleFromPathTest(resources.SysPathSetup, unittest.TestCase): + + def test_do_not_load_twice(self): + modutils.load_module_from_modpath(['data', 'lmfp', 'foo']) + modutils.load_module_from_modpath(['data', 'lmfp']) + self.assertEqual(len(sys.just_once), 1) + del sys.just_once + + +class FileFromModPathTest(resources.SysPathSetup, unittest.TestCase): + """given a mod path (i.e. splited module / package name), return the + corresponding file, giving priority to source file over precompiled file + if it exists""" + + def test_site_packages(self): + filename = _get_file_from_object(modutils) + result = modutils.file_from_modpath(['astroid', 'modutils']) + self.assertEqual(os.path.realpath(result), os.path.realpath(filename)) + + def test_std_lib(self): + from os import path + self.assertEqual(os.path.realpath(modutils.file_from_modpath(['os', 'path']).replace('.pyc', '.py')), + os.path.realpath(path.__file__.replace('.pyc', '.py'))) + + def test_xmlplus(self): + try: + # don't fail if pyxml isn't installed + from xml.dom import ext + except ImportError: + pass + else: + self.assertEqual(os.path.realpath(modutils.file_from_modpath(['xml', 'dom', 'ext']).replace('.pyc', '.py')), + os.path.realpath(ext.__file__.replace('.pyc', '.py'))) + + def test_builtin(self): + self.assertEqual(modutils.file_from_modpath(['sys']), + None) + + + def test_unexisting(self): + self.assertRaises(ImportError, modutils.file_from_modpath, ['turlututu']) + + def test_unicode_in_package_init(self): + # file_from_modpath should not crash when reading an __init__ + # file with unicode characters. + modutils.file_from_modpath(["data", "unicode_package", "core"]) + + +class GetSourceFileTest(unittest.TestCase): + + def test(self): + filename = _get_file_from_object(os.path) + self.assertEqual(modutils.get_source_file(os.path.__file__), + os.path.normpath(filename)) + + def test_raise(self): + self.assertRaises(modutils.NoSourceFile, modutils.get_source_file, 'whatever') + + +class StandardLibModuleTest(resources.SysPathSetup, unittest.TestCase): + """ + return true if the module may be considered as a module from the standard + library + """ + + def test_datetime(self): + # This is an interesting example, since datetime, on pypy, + # is under lib_pypy, rather than the usual Lib directory. + self.assertTrue(modutils.is_standard_module('datetime')) + + def test_builtins(self): + if sys.version_info < (3, 0): + self.assertEqual(modutils.is_standard_module('__builtin__'), True) + self.assertEqual(modutils.is_standard_module('builtins'), False) + else: + self.assertEqual(modutils.is_standard_module('__builtin__'), False) + self.assertEqual(modutils.is_standard_module('builtins'), True) + + def test_builtin(self): + self.assertEqual(modutils.is_standard_module('sys'), True) + self.assertEqual(modutils.is_standard_module('marshal'), True) + + def test_nonstandard(self): + self.assertEqual(modutils.is_standard_module('astroid'), False) + + def test_unknown(self): + self.assertEqual(modutils.is_standard_module('unknown'), False) + + def test_4(self): + self.assertEqual(modutils.is_standard_module('hashlib'), True) + self.assertEqual(modutils.is_standard_module('pickle'), True) + self.assertEqual(modutils.is_standard_module('email'), True) + self.assertEqual(modutils.is_standard_module('io'), sys.version_info >= (2, 6)) + self.assertEqual(modutils.is_standard_module('StringIO'), sys.version_info < (3, 0)) + self.assertEqual(modutils.is_standard_module('unicodedata'), True) + + def test_custom_path(self): + datadir = resources.find('') + if datadir.startswith(modutils.EXT_LIB_DIR): + self.skipTest('known breakage of is_standard_module on installed package') + self.assertEqual(modutils.is_standard_module('data.module', (datadir,)), True) + self.assertEqual(modutils.is_standard_module('data.module', (os.path.abspath(datadir),)), True) + + def test_failing_edge_cases(self): + from xml import etree + # using a subpackage/submodule path as std_path argument + self.assertEqual(modutils.is_standard_module('xml.etree', etree.__path__), False) + # using a module + object name as modname argument + self.assertEqual(modutils.is_standard_module('sys.path'), True) + # this is because only the first package/module is considered + self.assertEqual(modutils.is_standard_module('sys.whatever'), True) + self.assertEqual(modutils.is_standard_module('xml.whatever', etree.__path__), False) + + +class IsRelativeTest(unittest.TestCase): + + + def test_knownValues_is_relative_1(self): + import email + self.assertEqual(modutils.is_relative('utils', email.__path__[0]), + True) + + def test_knownValues_is_relative_2(self): + from xml.etree import ElementTree + self.assertEqual(modutils.is_relative('ElementPath', ElementTree.__file__), + True) + + def test_knownValues_is_relative_3(self): + import astroid + self.assertEqual(modutils.is_relative('astroid', astroid.__path__[0]), + False) + + +class GetModuleFilesTest(unittest.TestCase): + + def test_get_module_files_1(self): + package = resources.find('data/find_test') + modules = set(modutils.get_module_files(package, [])) + expected = ['__init__.py', 'module.py', 'module2.py', + 'noendingnewline.py', 'nonregr.py'] + self.assertEqual(modules, + {os.path.join(package, x) for x in expected}) + + def test_load_module_set_attribute(self): + import xml.etree.ElementTree + import xml + del xml.etree.ElementTree + del sys.modules['xml.etree.ElementTree'] + m = modutils.load_module_from_modpath(['xml', 'etree', 'ElementTree']) + self.assertTrue(hasattr(xml, 'etree')) + self.assertTrue(hasattr(xml.etree, 'ElementTree')) + self.assertTrue(m is xml.etree.ElementTree) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_nodes.py b/pymode/libs/astroid/tests/unittest_nodes.py new file mode 100644 index 00000000..6fa4b6f3 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_nodes.py @@ -0,0 +1,764 @@ +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +"""tests for specific behaviour of astroid nodes +""" +import os +import sys +import textwrap +import unittest +import warnings + +import six + +from astroid import bases +from astroid import builder +from astroid import context as contextmod +from astroid import exceptions +from astroid import node_classes +from astroid import nodes +from astroid import parse +from astroid import util +from astroid import test_utils +from astroid import transforms +from astroid.tests import resources + + +abuilder = builder.AstroidBuilder() +BUILTINS = six.moves.builtins.__name__ + + +class AsStringTest(resources.SysPathSetup, unittest.TestCase): + + def test_tuple_as_string(self): + def build(string): + return abuilder.string_build(string).body[0].value + + self.assertEqual(build('1,').as_string(), '(1, )') + self.assertEqual(build('1, 2, 3').as_string(), '(1, 2, 3)') + self.assertEqual(build('(1, )').as_string(), '(1, )') + self.assertEqual(build('1, 2, 3').as_string(), '(1, 2, 3)') + + def test_as_string_for_list_containing_uninferable(self): + node = test_utils.extract_node(''' + def foo(arg): + bar = [arg] * 1 + ''') + binop = node.body[0].value + inferred = next(binop.infer()) + self.assertEqual(inferred.as_string(), '[Uninferable]') + self.assertEqual(binop.as_string(), '([arg]) * (1)') + + def test_frozenset_as_string(self): + nodes = test_utils.extract_node(''' + frozenset((1, 2, 3)) #@ + frozenset({1, 2, 3}) #@ + frozenset([1, 2, 3,]) #@ + + frozenset(None) #@ + frozenset(1) #@ + ''') + nodes = [next(node.infer()) for node in nodes] + + self.assertEqual(nodes[0].as_string(), 'frozenset((1, 2, 3))') + self.assertEqual(nodes[1].as_string(), 'frozenset({1, 2, 3})') + self.assertEqual(nodes[2].as_string(), 'frozenset([1, 2, 3])') + + self.assertNotEqual(nodes[3].as_string(), 'frozenset(None)') + self.assertNotEqual(nodes[4].as_string(), 'frozenset(1)') + + @test_utils.require_version(minver='3.0') + def test_func_signature_issue_185(self): + code = textwrap.dedent(''' + def test(a, b, c=42, *, x=42, **kwargs): + print(a, b, c, args) + ''') + node = parse(code) + self.assertEqual(node.as_string().strip(), code.strip()) + def test_varargs_kwargs_as_string(self): + ast = abuilder.string_build('raise_string(*args, **kwargs)').body[0] + self.assertEqual(ast.as_string(), 'raise_string(*args, **kwargs)') + + def test_module_as_string(self): + """check as_string on a whole module prepared to be returned identically + """ + module = resources.build_file('data/module.py', 'data.module') + with open(resources.find('data/module.py'), 'r') as fobj: + self.assertMultiLineEqual(module.as_string(), fobj.read()) + + def test_module2_as_string(self): + """check as_string on a whole module prepared to be returned identically + """ + module2 = resources.build_file('data/module2.py', 'data.module2') + with open(resources.find('data/module2.py'), 'r') as fobj: + self.assertMultiLineEqual(module2.as_string(), fobj.read()) + + def test_as_string(self): + """check as_string for python syntax >= 2.7""" + code = '''one_two = {1, 2} +b = {v: k for (k, v) in enumerate('string')} +cdd = {k for k in b}\n\n''' + ast = abuilder.string_build(code) + self.assertMultiLineEqual(ast.as_string(), code) + + @test_utils.require_version('3.0') + def test_3k_as_string(self): + """check as_string for python 3k syntax""" + code = '''print() + +def function(var): + nonlocal counter + try: + hello + except NameError as nexc: + (*hell, o) = b'hello' + raise AttributeError from nexc +\n''' + ast = abuilder.string_build(code) + self.assertEqual(ast.as_string(), code) + + @test_utils.require_version('3.0') + @unittest.expectedFailure + def test_3k_annotations_and_metaclass(self): + code_annotations = textwrap.dedent(''' + def function(var:int): + nonlocal counter + + class Language(metaclass=Natural): + """natural language""" + ''') + + ast = abuilder.string_build(code_annotations) + self.assertEqual(ast.as_string(), code_annotations) + + def test_ellipsis(self): + ast = abuilder.string_build('a[...]').body[0] + self.assertEqual(ast.as_string(), 'a[...]') + + def test_slices(self): + for code in ('a[0]', 'a[1:3]', 'a[:-1:step]', 'a[:,newaxis]', + 'a[newaxis,:]', 'del L[::2]', 'del A[1]', 'del Br[:]'): + ast = abuilder.string_build(code).body[0] + self.assertEqual(ast.as_string(), code) + + def test_slice_and_subscripts(self): + code = """a[:1] = bord[2:] +a[:1] = bord[2:] +del bree[3:d] +bord[2:] +del av[d::f], a[df:] +a[:1] = bord[2:] +del SRC[::1,newaxis,1:] +tous[vals] = 1010 +del thousand[key] +del a[::2], a[:-1:step] +del Fee.form[left:] +aout.vals = miles.of_stuff +del (ccok, (name.thing, foo.attrib.value)), Fee.form[left:] +if all[1] == bord[0:]: + pass\n\n""" + ast = abuilder.string_build(code) + self.assertEqual(ast.as_string(), code) + + +class _NodeTest(unittest.TestCase): + """test transformation of If Node""" + CODE = None + + @property + def astroid(self): + try: + return self.__class__.__dict__['CODE_Astroid'] + except KeyError: + astroid = builder.parse(self.CODE) + self.__class__.CODE_Astroid = astroid + return astroid + + +class IfNodeTest(_NodeTest): + """test transformation of If Node""" + CODE = """ + if 0: + print() + + if True: + print() + else: + pass + + if "": + print() + elif []: + raise + + if 1: + print() + elif True: + print() + elif func(): + pass + else: + raise + """ + + def test_if_elif_else_node(self): + """test transformation for If node""" + self.assertEqual(len(self.astroid.body), 4) + for stmt in self.astroid.body: + self.assertIsInstance(stmt, nodes.If) + self.assertFalse(self.astroid.body[0].orelse) # simple If + self.assertIsInstance(self.astroid.body[1].orelse[0], nodes.Pass) # If / else + self.assertIsInstance(self.astroid.body[2].orelse[0], nodes.If) # If / elif + self.assertIsInstance(self.astroid.body[3].orelse[0].orelse[0], nodes.If) + + def test_block_range(self): + # XXX ensure expected values + self.assertEqual(self.astroid.block_range(1), (0, 22)) + self.assertEqual(self.astroid.block_range(10), (0, 22)) # XXX (10, 22) ? + self.assertEqual(self.astroid.body[1].block_range(5), (5, 6)) + self.assertEqual(self.astroid.body[1].block_range(6), (6, 6)) + self.assertEqual(self.astroid.body[1].orelse[0].block_range(7), (7, 8)) + self.assertEqual(self.astroid.body[1].orelse[0].block_range(8), (8, 8)) + + +class TryExceptNodeTest(_NodeTest): + CODE = """ + try: + print ('pouet') + except IOError: + pass + except UnicodeError: + print() + else: + print() + """ + + def test_block_range(self): + # XXX ensure expected values + self.assertEqual(self.astroid.body[0].block_range(1), (1, 8)) + self.assertEqual(self.astroid.body[0].block_range(2), (2, 2)) + self.assertEqual(self.astroid.body[0].block_range(3), (3, 8)) + self.assertEqual(self.astroid.body[0].block_range(4), (4, 4)) + self.assertEqual(self.astroid.body[0].block_range(5), (5, 5)) + self.assertEqual(self.astroid.body[0].block_range(6), (6, 6)) + self.assertEqual(self.astroid.body[0].block_range(7), (7, 7)) + self.assertEqual(self.astroid.body[0].block_range(8), (8, 8)) + + +class TryFinallyNodeTest(_NodeTest): + CODE = """ + try: + print ('pouet') + finally: + print ('pouet') + """ + + def test_block_range(self): + # XXX ensure expected values + self.assertEqual(self.astroid.body[0].block_range(1), (1, 4)) + self.assertEqual(self.astroid.body[0].block_range(2), (2, 2)) + self.assertEqual(self.astroid.body[0].block_range(3), (3, 4)) + self.assertEqual(self.astroid.body[0].block_range(4), (4, 4)) + + +class TryExceptFinallyNodeTest(_NodeTest): + CODE = """ + try: + print('pouet') + except Exception: + print ('oops') + finally: + print ('pouet') + """ + + def test_block_range(self): + # XXX ensure expected values + self.assertEqual(self.astroid.body[0].block_range(1), (1, 6)) + self.assertEqual(self.astroid.body[0].block_range(2), (2, 2)) + self.assertEqual(self.astroid.body[0].block_range(3), (3, 4)) + self.assertEqual(self.astroid.body[0].block_range(4), (4, 4)) + self.assertEqual(self.astroid.body[0].block_range(5), (5, 5)) + self.assertEqual(self.astroid.body[0].block_range(6), (6, 6)) + + +@unittest.skipIf(six.PY3, "Python 2 specific test.") +class TryExcept2xNodeTest(_NodeTest): + CODE = """ + try: + hello + except AttributeError, (retval, desc): + pass + """ + + + def test_tuple_attribute(self): + handler = self.astroid.body[0].handlers[0] + self.assertIsInstance(handler.name, nodes.Tuple) + + +class ImportNodeTest(resources.SysPathSetup, unittest.TestCase): + def setUp(self): + super(ImportNodeTest, self).setUp() + self.module = resources.build_file('data/module.py', 'data.module') + self.module2 = resources.build_file('data/module2.py', 'data.module2') + + def test_import_self_resolve(self): + myos = next(self.module2.igetattr('myos')) + self.assertTrue(isinstance(myos, nodes.Module), myos) + self.assertEqual(myos.name, 'os') + self.assertEqual(myos.qname(), 'os') + self.assertEqual(myos.pytype(), '%s.module' % BUILTINS) + + def test_from_self_resolve(self): + namenode = next(self.module.igetattr('NameNode')) + self.assertTrue(isinstance(namenode, nodes.ClassDef), namenode) + self.assertEqual(namenode.root().name, 'astroid.node_classes') + self.assertEqual(namenode.qname(), 'astroid.node_classes.Name') + self.assertEqual(namenode.pytype(), '%s.type' % BUILTINS) + abspath = next(self.module2.igetattr('abspath')) + self.assertTrue(isinstance(abspath, nodes.FunctionDef), abspath) + self.assertEqual(abspath.root().name, 'os.path') + self.assertEqual(abspath.qname(), 'os.path.abspath') + self.assertEqual(abspath.pytype(), '%s.function' % BUILTINS) + + def test_real_name(self): + from_ = self.module['NameNode'] + self.assertEqual(from_.real_name('NameNode'), 'Name') + imp_ = self.module['os'] + self.assertEqual(imp_.real_name('os'), 'os') + self.assertRaises(exceptions.NotFoundError, imp_.real_name, 'os.path') + imp_ = self.module['NameNode'] + self.assertEqual(imp_.real_name('NameNode'), 'Name') + self.assertRaises(exceptions.NotFoundError, imp_.real_name, 'Name') + imp_ = self.module2['YO'] + self.assertEqual(imp_.real_name('YO'), 'YO') + self.assertRaises(exceptions.NotFoundError, imp_.real_name, 'data') + + def test_as_string(self): + ast = self.module['modutils'] + self.assertEqual(ast.as_string(), "from astroid import modutils") + ast = self.module['NameNode'] + self.assertEqual(ast.as_string(), "from astroid.node_classes import Name as NameNode") + ast = self.module['os'] + self.assertEqual(ast.as_string(), "import os.path") + code = """from . import here +from .. import door +from .store import bread +from ..cave import wine\n\n""" + ast = abuilder.string_build(code) + self.assertMultiLineEqual(ast.as_string(), code) + + def test_bad_import_inference(self): + # Explication of bug + '''When we import PickleError from nonexistent, a call to the infer + method of this From node will be made by unpack_infer. + inference.infer_from will try to import this module, which will fail and + raise a InferenceException (by mixins.do_import_module). The infer_name + will catch this exception and yield and YES instead. + ''' + + code = ''' + try: + from pickle import PickleError + except ImportError: + from nonexistent import PickleError + + try: + pass + except PickleError: + pass + ''' + astroid = builder.parse(code) + handler_type = astroid.body[1].handlers[0].type + + excs = list(node_classes.unpack_infer(handler_type)) + # The number of returned object can differ on Python 2 + # and Python 3. In one version, an additional item will + # be returned, from the _pickle module, which is not + # present in the other version. + self.assertIsInstance(excs[0], nodes.ClassDef) + self.assertEqual(excs[0].name, 'PickleError') + self.assertIs(excs[-1], util.YES) + + def test_absolute_import(self): + astroid = resources.build_file('data/absimport.py') + ctx = contextmod.InferenceContext() + # will fail if absolute import failed + ctx.lookupname = 'message' + next(astroid['message'].infer(ctx)) + ctx.lookupname = 'email' + m = next(astroid['email'].infer(ctx)) + self.assertFalse(m.source_file.startswith(os.path.join('data', 'email.py'))) + + def test_more_absolute_import(self): + astroid = resources.build_file('data/module1abs/__init__.py', 'data.module1abs') + self.assertIn('sys', astroid._locals) + + +class CmpNodeTest(unittest.TestCase): + def test_as_string(self): + ast = abuilder.string_build("a == 2").body[0] + self.assertEqual(ast.as_string(), "a == 2") + + +class ConstNodeTest(unittest.TestCase): + + def _test(self, value): + node = nodes.const_factory(value) + self.assertIsInstance(node._proxied, nodes.ClassDef) + self.assertEqual(node._proxied.name, value.__class__.__name__) + self.assertIs(node.value, value) + self.assertTrue(node._proxied.parent) + self.assertEqual(node._proxied.root().name, value.__class__.__module__) + + def test_none(self): + self._test(None) + + def test_bool(self): + self._test(True) + + def test_int(self): + self._test(1) + + def test_float(self): + self._test(1.0) + + def test_complex(self): + self._test(1.0j) + + def test_str(self): + self._test('a') + + def test_unicode(self): + self._test(u'a') + + +class NameNodeTest(unittest.TestCase): + def test_assign_to_True(self): + """test that True and False assignements don't crash""" + code = """ + True = False + def hello(False): + pass + del True + """ + if sys.version_info >= (3, 0): + with self.assertRaises(exceptions.AstroidBuildingException): + builder.parse(code) + else: + ast = builder.parse(code) + assign_true = ast['True'] + self.assertIsInstance(assign_true, nodes.AssignName) + self.assertEqual(assign_true.name, "True") + del_true = ast.body[2].targets[0] + self.assertIsInstance(del_true, nodes.DelName) + self.assertEqual(del_true.name, "True") + + +class ArgumentsNodeTC(unittest.TestCase): + def test_linenumbering(self): + ast = builder.parse(''' + def func(a, + b): pass + x = lambda x: None + ''') + self.assertEqual(ast['func'].args.fromlineno, 2) + self.assertFalse(ast['func'].args.is_statement) + xlambda = next(ast['x'].infer()) + self.assertEqual(xlambda.args.fromlineno, 4) + self.assertEqual(xlambda.args.tolineno, 4) + self.assertFalse(xlambda.args.is_statement) + if sys.version_info < (3, 0): + self.assertEqual(ast['func'].args.tolineno, 3) + else: + self.skipTest('FIXME http://bugs.python.org/issue10445 ' + '(no line number on function args)') + + def test_builtin_fromlineno_missing(self): + cls = test_utils.extract_node(''' + class Foo(Exception): #@ + pass + ''') + new = cls.getattr('__new__')[-1] + self.assertEqual(new.args.fromlineno, 0) + + +class UnboundMethodNodeTest(unittest.TestCase): + + def test_no_super_getattr(self): + # This is a test for issue + # https://bitbucket.org/logilab/astroid/issue/91, which tests + # that UnboundMethod doesn't call super when doing .getattr. + + ast = builder.parse(''' + class A(object): + def test(self): + pass + meth = A.test + ''') + node = next(ast['meth'].infer()) + with self.assertRaises(exceptions.NotFoundError): + node.getattr('__missssing__') + name = node.getattr('__name__')[0] + self.assertIsInstance(name, nodes.Const) + self.assertEqual(name.value, 'test') + + +class BoundMethodNodeTest(unittest.TestCase): + + def test_is_property(self): + ast = builder.parse(''' + import abc + + def cached_property(): + # Not a real decorator, but we don't care + pass + def reify(): + # Same as cached_property + pass + def lazy_property(): + pass + def lazyproperty(): + pass + def lazy(): pass + class A(object): + @property + def builtin_property(self): + return 42 + @abc.abstractproperty + def abc_property(self): + return 42 + @cached_property + def cached_property(self): return 42 + @reify + def reified(self): return 42 + @lazy_property + def lazy_prop(self): return 42 + @lazyproperty + def lazyprop(self): return 42 + def not_prop(self): pass + @lazy + def decorated_with_lazy(self): return 42 + + cls = A() + builtin_property = cls.builtin_property + abc_property = cls.abc_property + cached_p = cls.cached_property + reified = cls.reified + not_prop = cls.not_prop + lazy_prop = cls.lazy_prop + lazyprop = cls.lazyprop + decorated_with_lazy = cls.decorated_with_lazy + ''') + for prop in ('builtin_property', 'abc_property', 'cached_p', 'reified', + 'lazy_prop', 'lazyprop', 'decorated_with_lazy'): + inferred = next(ast[prop].infer()) + self.assertIsInstance(inferred, nodes.Const, prop) + self.assertEqual(inferred.value, 42, prop) + + inferred = next(ast['not_prop'].infer()) + self.assertIsInstance(inferred, bases.BoundMethod) + + +class AliasesTest(unittest.TestCase): + + def setUp(self): + self.transformer = transforms.TransformVisitor() + + def parse_transform(self, code): + module = parse(code, apply_transforms=False) + return self.transformer.visit(module) + + def test_aliases(self): + def test_from(node): + node.names = node.names + [('absolute_import', None)] + return node + + def test_class(node): + node.name = 'Bar' + return node + + def test_function(node): + node.name = 'another_test' + return node + + def test_callfunc(node): + if node.func.name == 'Foo': + node.func.name = 'Bar' + return node + + def test_assname(node): + if node.name == 'foo': + n = nodes.AssignName() + n.name = 'bar' + return n + def test_assattr(node): + if node.attrname == 'a': + node.attrname = 'b' + return node + + def test_getattr(node): + if node.attrname == 'a': + node.attrname = 'b' + return node + + def test_genexpr(node): + if node.elt.value == 1: + node.elt = nodes.Const(2) + return node + + self.transformer.register_transform(nodes.From, test_from) + self.transformer.register_transform(nodes.Class, test_class) + self.transformer.register_transform(nodes.Function, test_function) + self.transformer.register_transform(nodes.CallFunc, test_callfunc) + self.transformer.register_transform(nodes.AssName, test_assname) + self.transformer.register_transform(nodes.AssAttr, test_assattr) + self.transformer.register_transform(nodes.Getattr, test_getattr) + self.transformer.register_transform(nodes.GenExpr, test_genexpr) + + string = ''' + from __future__ import print_function + + class Foo: pass + + def test(a): return a + + foo = Foo() + foo.a = test(42) + foo.a + (1 for _ in range(0, 42)) + ''' + + module = self.parse_transform(string) + + self.assertEqual(len(module.body[0].names), 2) + self.assertIsInstance(module.body[0], nodes.ImportFrom) + self.assertEqual(module.body[1].name, 'Bar') + self.assertIsInstance(module.body[1], nodes.ClassDef) + self.assertEqual(module.body[2].name, 'another_test') + self.assertIsInstance(module.body[2], nodes.FunctionDef) + self.assertEqual(module.body[3].targets[0].name, 'bar') + self.assertIsInstance(module.body[3].targets[0], nodes.AssignName) + self.assertEqual(module.body[3].value.func.name, 'Bar') + self.assertIsInstance(module.body[3].value, nodes.Call) + self.assertEqual(module.body[4].targets[0].attrname, 'b') + self.assertIsInstance(module.body[4].targets[0], nodes.AssignAttr) + self.assertIsInstance(module.body[5], nodes.Expr) + self.assertEqual(module.body[5].value.attrname, 'b') + self.assertIsInstance(module.body[5].value, nodes.Attribute) + self.assertEqual(module.body[6].value.elt.value, 2) + self.assertIsInstance(module.body[6].value, nodes.GeneratorExp) + + @unittest.skipIf(six.PY3, "Python 3 doesn't have Repr nodes.") + def test_repr(self): + def test_backquote(node): + node.value.name = 'bar' + return node + + self.transformer.register_transform(nodes.Backquote, test_backquote) + + module = self.parse_transform('`foo`') + + self.assertEqual(module.body[0].value.value.name, 'bar') + self.assertIsInstance(module.body[0].value, nodes.Repr) + + +class DeprecationWarningsTest(unittest.TestCase): + def test_asstype_warnings(self): + string = ''' + class C: pass + c = C() + with warnings.catch_warnings(record=True) as w: + pass + ''' + module = parse(string) + filter_stmts_mixin = module.body[0] + assign_type_mixin = module.body[1].targets[0] + parent_assign_type_mixin = module.body[2] + + warnings.simplefilter('always') + + with warnings.catch_warnings(record=True) as w: + filter_stmts_mixin.ass_type() + self.assertIsInstance(w[0].message, PendingDeprecationWarning) + with warnings.catch_warnings(record=True) as w: + assign_type_mixin.ass_type() + self.assertIsInstance(w[0].message, PendingDeprecationWarning) + with warnings.catch_warnings(record=True) as w: + parent_assign_type_mixin.ass_type() + self.assertIsInstance(w[0].message, PendingDeprecationWarning) + + def test_isinstance_warnings(self): + msg_format = ("%r is deprecated and slated for removal in astroid " + "2.0, use %r instead") + for cls in (nodes.Discard, nodes.Backquote, nodes.AssName, + nodes.AssAttr, nodes.Getattr, nodes.CallFunc, nodes.From): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + isinstance(42, cls) + self.assertIsInstance(w[0].message, PendingDeprecationWarning) + actual_msg = msg_format % (cls.__class__.__name__, cls.__wrapped__.__name__) + self.assertEqual(str(w[0].message), actual_msg) + + +@test_utils.require_version('3.5') +class Python35AsyncTest(unittest.TestCase): + + def test_async_await_keywords(self): + async_def, async_for, async_with, await_node = test_utils.extract_node(''' + async def func(): #@ + async for i in range(10): #@ + f = __(await i) + async with test(): #@ + pass + ''') + self.assertIsInstance(async_def, nodes.AsyncFunctionDef) + self.assertIsInstance(async_for, nodes.AsyncFor) + self.assertIsInstance(async_with, nodes.AsyncWith) + self.assertIsInstance(await_node, nodes.Await) + self.assertIsInstance(await_node.value, nodes.Name) + + def _test_await_async_as_string(self, code): + ast_node = parse(code) + self.assertEqual(ast_node.as_string().strip(), code.strip()) + + def test_await_as_string(self): + code = textwrap.dedent(''' + async def function(): + await 42 + ''') + self._test_await_async_as_string(code) + + def test_asyncwith_as_string(self): + code = textwrap.dedent(''' + async def function(): + async with (42): + pass + ''') + self._test_await_async_as_string(code) + + def test_asyncfor_as_string(self): + code = textwrap.dedent(''' + async def function(): + async for i in range(10): + await 42 + ''') + self._test_await_async_as_string(code) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_objects.py b/pymode/libs/astroid/tests/unittest_objects.py new file mode 100644 index 00000000..da9f0c87 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_objects.py @@ -0,0 +1,530 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . + +import unittest + +from astroid import bases +from astroid import exceptions +from astroid import nodes +from astroid import objects +from astroid import test_utils + + +class ObjectsTest(unittest.TestCase): + + def test_frozenset(self): + node = test_utils.extract_node(""" + frozenset({1: 2, 2: 3}) #@ + """) + infered = next(node.infer()) + self.assertIsInstance(infered, objects.FrozenSet) + + self.assertEqual(infered.pytype(), "%s.frozenset" % bases.BUILTINS) + + itered = infered.itered() + self.assertEqual(len(itered), 2) + self.assertIsInstance(itered[0], nodes.Const) + self.assertEqual([const.value for const in itered], [1, 2]) + + proxied = infered._proxied + self.assertEqual(infered.qname(), "%s.frozenset" % bases.BUILTINS) + self.assertIsInstance(proxied, nodes.ClassDef) + + +class SuperTests(unittest.TestCase): + + def test_inferring_super_outside_methods(self): + ast_nodes = test_utils.extract_node(''' + class Module(object): + pass + class StaticMethod(object): + @staticmethod + def static(): + # valid, but we don't bother with it. + return super(StaticMethod, StaticMethod) #@ + # super outside methods aren't inferred + super(Module, Module) #@ + # no argument super is not recognised outside methods as well. + super() #@ + ''') + in_static = next(ast_nodes[0].value.infer()) + self.assertIsInstance(in_static, bases.Instance) + self.assertEqual(in_static.qname(), "%s.super" % bases.BUILTINS) + + module_level = next(ast_nodes[1].infer()) + self.assertIsInstance(module_level, bases.Instance) + self.assertEqual(in_static.qname(), "%s.super" % bases.BUILTINS) + + no_arguments = next(ast_nodes[2].infer()) + self.assertIsInstance(no_arguments, bases.Instance) + self.assertEqual(no_arguments.qname(), "%s.super" % bases.BUILTINS) + + def test_inferring_unbound_super_doesnt_work(self): + node = test_utils.extract_node(''' + class Test(object): + def __init__(self): + super(Test) #@ + ''') + unbounded = next(node.infer()) + self.assertIsInstance(unbounded, bases.Instance) + self.assertEqual(unbounded.qname(), "%s.super" % bases.BUILTINS) + + def test_use_default_inference_on_not_inferring_args(self): + ast_nodes = test_utils.extract_node(''' + class Test(object): + def __init__(self): + super(Lala, self) #@ + super(Test, lala) #@ + ''') + first = next(ast_nodes[0].infer()) + self.assertIsInstance(first, bases.Instance) + self.assertEqual(first.qname(), "%s.super" % bases.BUILTINS) + + second = next(ast_nodes[1].infer()) + self.assertIsInstance(second, bases.Instance) + self.assertEqual(second.qname(), "%s.super" % bases.BUILTINS) + + @test_utils.require_version(maxver='3.0') + def test_super_on_old_style_class(self): + # super doesn't work on old style class, but leave + # that as an error for pylint. We'll infer Super objects, + # but every call will result in a failure at some point. + node = test_utils.extract_node(''' + class OldStyle: + def __init__(self): + super(OldStyle, self) #@ + ''') + old = next(node.infer()) + self.assertIsInstance(old, objects.Super) + self.assertIsInstance(old.mro_pointer, nodes.ClassDef) + self.assertEqual(old.mro_pointer.name, 'OldStyle') + with self.assertRaises(exceptions.SuperError) as cm: + old.super_mro() + self.assertEqual(str(cm.exception), + "Unable to call super on old-style classes.") + + @test_utils.require_version(minver='3.0') + def test_no_arguments_super(self): + ast_nodes = test_utils.extract_node(''' + class First(object): pass + class Second(First): + def test(self): + super() #@ + @classmethod + def test_classmethod(cls): + super() #@ + ''') + first = next(ast_nodes[0].infer()) + self.assertIsInstance(first, objects.Super) + self.assertIsInstance(first.type, bases.Instance) + self.assertEqual(first.type.name, 'Second') + self.assertIsInstance(first.mro_pointer, nodes.ClassDef) + self.assertEqual(first.mro_pointer.name, 'Second') + + second = next(ast_nodes[1].infer()) + self.assertIsInstance(second, objects.Super) + self.assertIsInstance(second.type, nodes.ClassDef) + self.assertEqual(second.type.name, 'Second') + self.assertIsInstance(second.mro_pointer, nodes.ClassDef) + self.assertEqual(second.mro_pointer.name, 'Second') + + def test_super_simple_cases(self): + ast_nodes = test_utils.extract_node(''' + class First(object): pass + class Second(First): pass + class Third(First): + def test(self): + super(Third, self) #@ + super(Second, self) #@ + + # mro position and the type + super(Third, Third) #@ + super(Third, Second) #@ + super(Fourth, Fourth) #@ + + class Fourth(Third): + pass + ''') + + # .type is the object which provides the mro. + # .mro_pointer is the position in the mro from where + # the lookup should be done. + + # super(Third, self) + first = next(ast_nodes[0].infer()) + self.assertIsInstance(first, objects.Super) + self.assertIsInstance(first.type, bases.Instance) + self.assertEqual(first.type.name, 'Third') + self.assertIsInstance(first.mro_pointer, nodes.ClassDef) + self.assertEqual(first.mro_pointer.name, 'Third') + + # super(Second, self) + second = next(ast_nodes[1].infer()) + self.assertIsInstance(second, objects.Super) + self.assertIsInstance(second.type, bases.Instance) + self.assertEqual(second.type.name, 'Third') + self.assertIsInstance(first.mro_pointer, nodes.ClassDef) + self.assertEqual(second.mro_pointer.name, 'Second') + + # super(Third, Third) + third = next(ast_nodes[2].infer()) + self.assertIsInstance(third, objects.Super) + self.assertIsInstance(third.type, nodes.ClassDef) + self.assertEqual(third.type.name, 'Third') + self.assertIsInstance(third.mro_pointer, nodes.ClassDef) + self.assertEqual(third.mro_pointer.name, 'Third') + + # super(Third, second) + fourth = next(ast_nodes[3].infer()) + self.assertIsInstance(fourth, objects.Super) + self.assertIsInstance(fourth.type, nodes.ClassDef) + self.assertEqual(fourth.type.name, 'Second') + self.assertIsInstance(fourth.mro_pointer, nodes.ClassDef) + self.assertEqual(fourth.mro_pointer.name, 'Third') + + # Super(Fourth, Fourth) + fifth = next(ast_nodes[4].infer()) + self.assertIsInstance(fifth, objects.Super) + self.assertIsInstance(fifth.type, nodes.ClassDef) + self.assertEqual(fifth.type.name, 'Fourth') + self.assertIsInstance(fifth.mro_pointer, nodes.ClassDef) + self.assertEqual(fifth.mro_pointer.name, 'Fourth') + + def test_super_infer(self): + node = test_utils.extract_node(''' + class Super(object): + def __init__(self): + super(Super, self) #@ + ''') + inferred = next(node.infer()) + self.assertIsInstance(inferred, objects.Super) + reinferred = next(inferred.infer()) + self.assertIsInstance(reinferred, objects.Super) + self.assertIs(inferred, reinferred) + + def test_inferring_invalid_supers(self): + ast_nodes = test_utils.extract_node(''' + class Super(object): + def __init__(self): + # MRO pointer is not a type + super(1, self) #@ + # MRO type is not a subtype + super(Super, 1) #@ + # self is not a subtype of Bupper + super(Bupper, self) #@ + class Bupper(Super): + pass + ''') + first = next(ast_nodes[0].infer()) + self.assertIsInstance(first, objects.Super) + with self.assertRaises(exceptions.SuperError) as cm: + first.super_mro() + self.assertEqual(str(cm.exception), "The first super argument must be type.") + + for node in ast_nodes[1:]: + inferred = next(node.infer()) + self.assertIsInstance(inferred, objects.Super, node) + with self.assertRaises(exceptions.SuperArgumentTypeError) as cm: + inferred.super_mro() + self.assertEqual(str(cm.exception), + "super(type, obj): obj must be an instance " + "or subtype of type", node) + + def test_proxied(self): + node = test_utils.extract_node(''' + class Super(object): + def __init__(self): + super(Super, self) #@ + ''') + infered = next(node.infer()) + proxied = infered._proxied + self.assertEqual(proxied.qname(), "%s.super" % bases.BUILTINS) + self.assertIsInstance(proxied, nodes.ClassDef) + + def test_super_bound_model(self): + ast_nodes = test_utils.extract_node(''' + class First(object): + def method(self): + pass + @classmethod + def class_method(cls): + pass + class Super_Type_Type(First): + def method(self): + super(Super_Type_Type, Super_Type_Type).method #@ + super(Super_Type_Type, Super_Type_Type).class_method #@ + @classmethod + def class_method(cls): + super(Super_Type_Type, Super_Type_Type).method #@ + super(Super_Type_Type, Super_Type_Type).class_method #@ + + class Super_Type_Object(First): + def method(self): + super(Super_Type_Object, self).method #@ + super(Super_Type_Object, self).class_method #@ + ''') + # Super(type, type) is the same for both functions and classmethods. + first = next(ast_nodes[0].infer()) + self.assertIsInstance(first, nodes.FunctionDef) + self.assertEqual(first.name, 'method') + + second = next(ast_nodes[1].infer()) + self.assertIsInstance(second, bases.BoundMethod) + self.assertEqual(second.bound.name, 'First') + self.assertEqual(second.type, 'classmethod') + + third = next(ast_nodes[2].infer()) + self.assertIsInstance(third, nodes.FunctionDef) + self.assertEqual(third.name, 'method') + + fourth = next(ast_nodes[3].infer()) + self.assertIsInstance(fourth, bases.BoundMethod) + self.assertEqual(fourth.bound.name, 'First') + self.assertEqual(fourth.type, 'classmethod') + + # Super(type, obj) can lead to different attribute bindings + # depending on the type of the place where super was called. + fifth = next(ast_nodes[4].infer()) + self.assertIsInstance(fifth, bases.BoundMethod) + self.assertEqual(fifth.bound.name, 'First') + self.assertEqual(fifth.type, 'method') + + sixth = next(ast_nodes[5].infer()) + self.assertIsInstance(sixth, bases.BoundMethod) + self.assertEqual(sixth.bound.name, 'First') + self.assertEqual(sixth.type, 'classmethod') + + def test_super_getattr_single_inheritance(self): + ast_nodes = test_utils.extract_node(''' + class First(object): + def test(self): pass + class Second(First): + def test2(self): pass + class Third(Second): + test3 = 42 + def __init__(self): + super(Third, self).test2 #@ + super(Third, self).test #@ + # test3 is local, no MRO lookup is done. + super(Third, self).test3 #@ + super(Third, self) #@ + + # Unbounds. + super(Third, Third).test2 #@ + super(Third, Third).test #@ + + ''') + first = next(ast_nodes[0].infer()) + self.assertIsInstance(first, bases.BoundMethod) + self.assertEqual(first.bound.name, 'Second') + + second = next(ast_nodes[1].infer()) + self.assertIsInstance(second, bases.BoundMethod) + self.assertEqual(second.bound.name, 'First') + + with self.assertRaises(exceptions.InferenceError): + next(ast_nodes[2].infer()) + fourth = next(ast_nodes[3].infer()) + with self.assertRaises(exceptions.NotFoundError): + fourth.getattr('test3') + with self.assertRaises(exceptions.NotFoundError): + next(fourth.igetattr('test3')) + + first_unbound = next(ast_nodes[4].infer()) + self.assertIsInstance(first_unbound, nodes.FunctionDef) + self.assertEqual(first_unbound.name, 'test2') + self.assertEqual(first_unbound.parent.name, 'Second') + + second_unbound = next(ast_nodes[5].infer()) + self.assertIsInstance(second_unbound, nodes.FunctionDef) + self.assertEqual(second_unbound.name, 'test') + self.assertEqual(second_unbound.parent.name, 'First') + + def test_super_invalid_mro(self): + node = test_utils.extract_node(''' + class A(object): + test = 42 + class Super(A, A): + def __init__(self): + super(Super, self) #@ + ''') + inferred = next(node.infer()) + with self.assertRaises(exceptions.NotFoundError): + next(inferred.getattr('test')) + + def test_super_complex_mro(self): + ast_nodes = test_utils.extract_node(''' + class A(object): + def spam(self): return "A" + def foo(self): return "A" + @staticmethod + def static(self): pass + class B(A): + def boo(self): return "B" + def spam(self): return "B" + class C(A): + def boo(self): return "C" + class E(C, B): + def __init__(self): + super(E, self).boo #@ + super(C, self).boo #@ + super(E, self).spam #@ + super(E, self).foo #@ + super(E, self).static #@ + ''') + first = next(ast_nodes[0].infer()) + self.assertIsInstance(first, bases.BoundMethod) + self.assertEqual(first.bound.name, 'C') + second = next(ast_nodes[1].infer()) + self.assertIsInstance(second, bases.BoundMethod) + self.assertEqual(second.bound.name, 'B') + third = next(ast_nodes[2].infer()) + self.assertIsInstance(third, bases.BoundMethod) + self.assertEqual(third.bound.name, 'B') + fourth = next(ast_nodes[3].infer()) + self.assertEqual(fourth.bound.name, 'A') + static = next(ast_nodes[4].infer()) + self.assertIsInstance(static, nodes.FunctionDef) + self.assertEqual(static.parent.scope().name, 'A') + + def test_super_data_model(self): + ast_nodes = test_utils.extract_node(''' + class X(object): pass + class A(X): + def __init__(self): + super(A, self) #@ + super(A, A) #@ + super(X, A) #@ + ''') + first = next(ast_nodes[0].infer()) + thisclass = first.getattr('__thisclass__')[0] + self.assertIsInstance(thisclass, nodes.ClassDef) + self.assertEqual(thisclass.name, 'A') + selfclass = first.getattr('__self_class__')[0] + self.assertIsInstance(selfclass, nodes.ClassDef) + self.assertEqual(selfclass.name, 'A') + self_ = first.getattr('__self__')[0] + self.assertIsInstance(self_, bases.Instance) + self.assertEqual(self_.name, 'A') + cls = first.getattr('__class__')[0] + self.assertEqual(cls, first._proxied) + + second = next(ast_nodes[1].infer()) + thisclass = second.getattr('__thisclass__')[0] + self.assertEqual(thisclass.name, 'A') + self_ = second.getattr('__self__')[0] + self.assertIsInstance(self_, nodes.ClassDef) + self.assertEqual(self_.name, 'A') + + third = next(ast_nodes[2].infer()) + thisclass = third.getattr('__thisclass__')[0] + self.assertEqual(thisclass.name, 'X') + selfclass = third.getattr('__self_class__')[0] + self.assertEqual(selfclass.name, 'A') + + def assertEqualMro(self, klass, expected_mro): + self.assertEqual( + [member.name for member in klass.super_mro()], + expected_mro) + + def test_super_mro(self): + ast_nodes = test_utils.extract_node(''' + class A(object): pass + class B(A): pass + class C(A): pass + class E(C, B): + def __init__(self): + super(E, self) #@ + super(C, self) #@ + super(B, self) #@ + + super(B, 1) #@ + super(1, B) #@ + ''') + first = next(ast_nodes[0].infer()) + self.assertEqualMro(first, ['C', 'B', 'A', 'object']) + second = next(ast_nodes[1].infer()) + self.assertEqualMro(second, ['B', 'A', 'object']) + third = next(ast_nodes[2].infer()) + self.assertEqualMro(third, ['A', 'object']) + + fourth = next(ast_nodes[3].infer()) + with self.assertRaises(exceptions.SuperError): + fourth.super_mro() + fifth = next(ast_nodes[4].infer()) + with self.assertRaises(exceptions.SuperError): + fifth.super_mro() + + def test_super_yes_objects(self): + ast_nodes = test_utils.extract_node(''' + from collections import Missing + class A(object): + def __init__(self): + super(Missing, self) #@ + super(A, Missing) #@ + ''') + first = next(ast_nodes[0].infer()) + self.assertIsInstance(first, bases.Instance) + second = next(ast_nodes[1].infer()) + self.assertIsInstance(second, bases.Instance) + + def test_super_invalid_types(self): + node = test_utils.extract_node(''' + import collections + class A(object): + def __init__(self): + super(A, collections) #@ + ''') + inferred = next(node.infer()) + with self.assertRaises(exceptions.SuperError): + inferred.super_mro() + with self.assertRaises(exceptions.SuperArgumentTypeError): + inferred.super_mro() + + def test_super_pytype_display_type_name(self): + node = test_utils.extract_node(''' + class A(object): + def __init__(self): + super(A, self) #@ + ''') + inferred = next(node.infer()) + self.assertEqual(inferred.pytype(), "%s.super" % bases.BUILTINS) + self.assertEqual(inferred.display_type(), 'Super of') + self.assertEqual(inferred.name, 'A') + + def test_super_properties(self): + node = test_utils.extract_node(''' + class Foo(object): + @property + def dict(self): + return 42 + + class Bar(Foo): + @property + def dict(self): + return super(Bar, self).dict + + Bar().dict + ''') + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, 42) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_peephole.py b/pymode/libs/astroid/tests/unittest_peephole.py new file mode 100644 index 00000000..78349898 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_peephole.py @@ -0,0 +1,121 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . + +"""Tests for the astroid AST peephole optimizer.""" + +import ast +import textwrap +import unittest + +import astroid +from astroid import astpeephole +from astroid import builder +from astroid import manager +from astroid import test_utils +from astroid.tests import resources + + +MANAGER = manager.AstroidManager() + + +class PeepholeOptimizer(unittest.TestCase): + @classmethod + def setUpClass(cls): + MANAGER.optimize_ast = True + + @classmethod + def tearDownClass(cls): + MANAGER.optimize_ast = False + + def setUp(self): + self._optimizer = astpeephole.ASTPeepholeOptimizer() + + @staticmethod + def _get_binops(code): + module = ast.parse(textwrap.dedent(code)) + return [node.value for node in module.body + if isinstance(node, ast.Expr)] + + @test_utils.require_version(maxver='3.0') + def test_optimize_binop_unicode(self): + nodes = self._get_binops(""" + u"a" + u"b" + u"c" + + u"a" + "c" + "b" + u"a" + b"c" + """) + + result = self._optimizer.optimize_binop(nodes[0]) + self.assertIsInstance(result, astroid.Const) + self.assertEqual(result.value, u"abc") + + self.assertIsNone(self._optimizer.optimize_binop(nodes[1])) + self.assertIsNone(self._optimizer.optimize_binop(nodes[2])) + + def test_optimize_binop(self): + nodes = self._get_binops(""" + "a" + "b" + "c" + "d" + b"a" + b"b" + b"c" + b"d" + "a" + "b" + + "a" + "b" + 1 + object + var = 4 + "a" + "b" + var + "c" + "a" + "b" + "c" - "4" + "a" + "b" + "c" + "d".format() + "a" - "b" + "a" + 1 + 4 + 5 + 6 + """) + + result = self._optimizer.optimize_binop(nodes[0]) + self.assertIsInstance(result, astroid.Const) + self.assertEqual(result.value, "abcd") + + result = self._optimizer.optimize_binop(nodes[1]) + self.assertIsInstance(result, astroid.Const) + self.assertEqual(result.value, b"abcd") + + for node in nodes[2:]: + self.assertIsNone(self._optimizer.optimize_binop(node)) + + def test_big_binop_crash(self): + # Test that we don't fail on a lot of joined strings + # through the addition operator. + module = resources.build_file('data/joined_strings.py') + element = next(module['x'].infer()) + self.assertIsInstance(element, astroid.Const) + self.assertEqual(len(element.value), 61660) + + def test_optimisation_disabled(self): + try: + MANAGER.optimize_ast = False + module = builder.parse(""" + '1' + '2' + '3' + """) + self.assertIsInstance(module.body[0], astroid.Expr) + self.assertIsInstance(module.body[0].value, astroid.BinOp) + self.assertIsInstance(module.body[0].value.left, astroid.BinOp) + self.assertIsInstance(module.body[0].value.left.left, + astroid.Const) + finally: + MANAGER.optimize_ast = True + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_protocols.py b/pymode/libs/astroid/tests/unittest_protocols.py new file mode 100644 index 00000000..16745129 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_protocols.py @@ -0,0 +1,176 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . + +import contextlib +import unittest + +import astroid +from astroid.test_utils import extract_node, require_version +from astroid import InferenceError +from astroid import nodes +from astroid import util +from astroid.node_classes import AssignName, Const, Name, Starred + + +@contextlib.contextmanager +def _add_transform(manager, node, transform, predicate=None): + manager.register_transform(node, transform, predicate) + try: + yield + finally: + manager.unregister_transform(node, transform, predicate) + + +class ProtocolTests(unittest.TestCase): + + def assertConstNodesEqual(self, nodes_list_expected, nodes_list_got): + self.assertEqual(len(nodes_list_expected), len(nodes_list_got)) + for node in nodes_list_got: + self.assertIsInstance(node, Const) + for node, expected_value in zip(nodes_list_got, nodes_list_expected): + self.assertEqual(expected_value, node.value) + + def assertNameNodesEqual(self, nodes_list_expected, nodes_list_got): + self.assertEqual(len(nodes_list_expected), len(nodes_list_got)) + for node in nodes_list_got: + self.assertIsInstance(node, Name) + for node, expected_name in zip(nodes_list_got, nodes_list_expected): + self.assertEqual(expected_name, node.name) + + def test_assigned_stmts_simple_for(self): + assign_stmts = extract_node(""" + for a in (1, 2, 3): #@ + pass + + for b in range(3): #@ + pass + """) + + for1_assnode = next(assign_stmts[0].nodes_of_class(AssignName)) + assigned = list(for1_assnode.assigned_stmts()) + self.assertConstNodesEqual([1, 2, 3], assigned) + + for2_assnode = next(assign_stmts[1].nodes_of_class(AssignName)) + self.assertRaises(InferenceError, + list, for2_assnode.assigned_stmts()) + + @require_version(minver='3.0') + def test_assigned_stmts_starred_for(self): + assign_stmts = extract_node(""" + for *a, b in ((1, 2, 3), (4, 5, 6, 7)): #@ + pass + """) + + for1_starred = next(assign_stmts.nodes_of_class(Starred)) + assigned = next(for1_starred.assigned_stmts()) + self.assertEqual(assigned, util.YES) + + def _get_starred_stmts(self, code): + assign_stmt = extract_node("{} #@".format(code)) + starred = next(assign_stmt.nodes_of_class(Starred)) + return next(starred.assigned_stmts()) + + def _helper_starred_expected_const(self, code, expected): + stmts = self._get_starred_stmts(code) + self.assertIsInstance(stmts, nodes.List) + stmts = stmts.elts + self.assertConstNodesEqual(expected, stmts) + + def _helper_starred_expected(self, code, expected): + stmts = self._get_starred_stmts(code) + self.assertEqual(expected, stmts) + + def _helper_starred_inference_error(self, code): + assign_stmt = extract_node("{} #@".format(code)) + starred = next(assign_stmt.nodes_of_class(Starred)) + self.assertRaises(InferenceError, list, starred.assigned_stmts()) + + @require_version(minver='3.0') + def test_assigned_stmts_starred_assnames(self): + self._helper_starred_expected_const( + "a, *b = (1, 2, 3, 4) #@", [2, 3, 4]) + self._helper_starred_expected_const( + "*a, b = (1, 2, 3) #@", [1, 2]) + self._helper_starred_expected_const( + "a, *b, c = (1, 2, 3, 4, 5) #@", + [2, 3, 4]) + self._helper_starred_expected_const( + "a, *b = (1, 2) #@", [2]) + self._helper_starred_expected_const( + "*b, a = (1, 2) #@", [1]) + self._helper_starred_expected_const( + "[*b] = (1, 2) #@", [1, 2]) + + @require_version(minver='3.0') + def test_assigned_stmts_starred_yes(self): + # Not something iterable and known + self._helper_starred_expected("a, *b = range(3) #@", util.YES) + # Not something inferrable + self._helper_starred_expected("a, *b = balou() #@", util.YES) + # In function, unknown. + self._helper_starred_expected(""" + def test(arg): + head, *tail = arg #@""", util.YES) + # These cases aren't worth supporting. + self._helper_starred_expected( + "a, (*b, c), d = (1, (2, 3, 4), 5) #@", util.YES) + + @require_version(minver='3.0') + def test_assign_stmts_starred_fails(self): + # Too many starred + self._helper_starred_inference_error("a, *b, *c = (1, 2, 3) #@") + # Too many lhs values + self._helper_starred_inference_error("a, *b, c = (1, 2) #@") + # This could be solved properly, but it complicates needlessly the + # code for assigned_stmts, without oferring real benefit. + self._helper_starred_inference_error( + "(*a, b), (c, *d) = (1, 2, 3), (4, 5, 6) #@") + + def test_assigned_stmts_assignments(self): + assign_stmts = extract_node(""" + c = a #@ + + d, e = b, c #@ + """) + + simple_assnode = next(assign_stmts[0].nodes_of_class(AssignName)) + assigned = list(simple_assnode.assigned_stmts()) + self.assertNameNodesEqual(['a'], assigned) + + assnames = assign_stmts[1].nodes_of_class(AssignName) + simple_mul_assnode_1 = next(assnames) + assigned = list(simple_mul_assnode_1.assigned_stmts()) + self.assertNameNodesEqual(['b'], assigned) + simple_mul_assnode_2 = next(assnames) + assigned = list(simple_mul_assnode_2.assigned_stmts()) + self.assertNameNodesEqual(['c'], assigned) + + def test_sequence_assigned_stmts_not_accepting_empty_node(self): + def transform(node): + node.root().locals['__all__'] = [node.value] + + manager = astroid.MANAGER + with _add_transform(manager, astroid.Assign, transform): + module = astroid.parse(''' + __all__ = ['a'] + ''') + module.wildcard_import_names() + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_python3.py b/pymode/libs/astroid/tests/unittest_python3.py new file mode 100644 index 00000000..87010571 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_python3.py @@ -0,0 +1,254 @@ +# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +from textwrap import dedent +import unittest + +from astroid import nodes +from astroid.node_classes import Assign, Expr, YieldFrom, Name, Const +from astroid.builder import AstroidBuilder +from astroid.scoped_nodes import ClassDef, FunctionDef +from astroid.test_utils import require_version, extract_node + + +class Python3TC(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.builder = AstroidBuilder() + + @require_version('3.0') + def test_starred_notation(self): + astroid = self.builder.string_build("*a, b = [1, 2, 3]", 'test', 'test') + + # Get the star node + node = next(next(next(astroid.get_children()).get_children()).get_children()) + + self.assertTrue(isinstance(node.assign_type(), Assign)) + + @require_version('3.3') + def test_yield_from(self): + body = dedent(""" + def func(): + yield from iter([1, 2]) + """) + astroid = self.builder.string_build(body) + func = astroid.body[0] + self.assertIsInstance(func, FunctionDef) + yieldfrom_stmt = func.body[0] + + self.assertIsInstance(yieldfrom_stmt, Expr) + self.assertIsInstance(yieldfrom_stmt.value, YieldFrom) + self.assertEqual(yieldfrom_stmt.as_string(), + 'yield from iter([1, 2])') + + @require_version('3.3') + def test_yield_from_is_generator(self): + body = dedent(""" + def func(): + yield from iter([1, 2]) + """) + astroid = self.builder.string_build(body) + func = astroid.body[0] + self.assertIsInstance(func, FunctionDef) + self.assertTrue(func.is_generator()) + + @require_version('3.3') + def test_yield_from_as_string(self): + body = dedent(""" + def func(): + yield from iter([1, 2]) + value = yield from other() + """) + astroid = self.builder.string_build(body) + func = astroid.body[0] + self.assertEqual(func.as_string().strip(), body.strip()) + + # metaclass tests + + @require_version('3.0') + def test_simple_metaclass(self): + astroid = self.builder.string_build("class Test(metaclass=type): pass") + klass = astroid.body[0] + + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, ClassDef) + self.assertEqual(metaclass.name, 'type') + + @require_version('3.0') + def test_metaclass_error(self): + astroid = self.builder.string_build("class Test(metaclass=typ): pass") + klass = astroid.body[0] + self.assertFalse(klass.metaclass()) + + @require_version('3.0') + def test_metaclass_imported(self): + astroid = self.builder.string_build(dedent(""" + from abc import ABCMeta + class Test(metaclass=ABCMeta): pass""")) + klass = astroid.body[1] + + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, ClassDef) + self.assertEqual(metaclass.name, 'ABCMeta') + + @require_version('3.0') + def test_as_string(self): + body = dedent(""" + from abc import ABCMeta + class Test(metaclass=ABCMeta): pass""") + astroid = self.builder.string_build(body) + klass = astroid.body[1] + + self.assertEqual(klass.as_string(), + '\n\nclass Test(metaclass=ABCMeta):\n pass\n') + + @require_version('3.0') + def test_old_syntax_works(self): + astroid = self.builder.string_build(dedent(""" + class Test: + __metaclass__ = type + class SubTest(Test): pass + """)) + klass = astroid['SubTest'] + metaclass = klass.metaclass() + self.assertIsNone(metaclass) + + @require_version('3.0') + def test_metaclass_yes_leak(self): + astroid = self.builder.string_build(dedent(""" + # notice `ab` instead of `abc` + from ab import ABCMeta + + class Meta(metaclass=ABCMeta): pass + """)) + klass = astroid['Meta'] + self.assertIsNone(klass.metaclass()) + + @require_version('3.0') + def test_parent_metaclass(self): + astroid = self.builder.string_build(dedent(""" + from abc import ABCMeta + class Test(metaclass=ABCMeta): pass + class SubTest(Test): pass + """)) + klass = astroid['SubTest'] + self.assertTrue(klass.newstyle) + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, ClassDef) + self.assertEqual(metaclass.name, 'ABCMeta') + + @require_version('3.0') + def test_metaclass_ancestors(self): + astroid = self.builder.string_build(dedent(""" + from abc import ABCMeta + + class FirstMeta(metaclass=ABCMeta): pass + class SecondMeta(metaclass=type): + pass + + class Simple: + pass + + class FirstImpl(FirstMeta): pass + class SecondImpl(FirstImpl): pass + class ThirdImpl(Simple, SecondMeta): + pass + """)) + classes = { + 'ABCMeta': ('FirstImpl', 'SecondImpl'), + 'type': ('ThirdImpl', ) + } + for metaclass, names in classes.items(): + for name in names: + impl = astroid[name] + meta = impl.metaclass() + self.assertIsInstance(meta, ClassDef) + self.assertEqual(meta.name, metaclass) + + @require_version('3.0') + def test_annotation_support(self): + astroid = self.builder.string_build(dedent(""" + def test(a: int, b: str, c: None, d, e, + *args: float, **kwargs: int)->int: + pass + """)) + func = astroid['test'] + self.assertIsInstance(func.args.varargannotation, Name) + self.assertEqual(func.args.varargannotation.name, 'float') + self.assertIsInstance(func.args.kwargannotation, Name) + self.assertEqual(func.args.kwargannotation.name, 'int') + self.assertIsInstance(func.returns, Name) + self.assertEqual(func.returns.name, 'int') + arguments = func.args + self.assertIsInstance(arguments.annotations[0], Name) + self.assertEqual(arguments.annotations[0].name, 'int') + self.assertIsInstance(arguments.annotations[1], Name) + self.assertEqual(arguments.annotations[1].name, 'str') + self.assertIsInstance(arguments.annotations[2], Const) + self.assertIsNone(arguments.annotations[2].value) + self.assertIsNone(arguments.annotations[3]) + self.assertIsNone(arguments.annotations[4]) + + astroid = self.builder.string_build(dedent(""" + def test(a: int=1, b: str=2): + pass + """)) + func = astroid['test'] + self.assertIsInstance(func.args.annotations[0], Name) + self.assertEqual(func.args.annotations[0].name, 'int') + self.assertIsInstance(func.args.annotations[1], Name) + self.assertEqual(func.args.annotations[1].name, 'str') + self.assertIsNone(func.returns) + + @require_version('3.0') + def test_annotation_as_string(self): + code1 = dedent(''' + def test(a, b:int=4, c=2, f:'lala'=4)->2: + pass''') + code2 = dedent(''' + def test(a:typing.Generic[T], c:typing.Any=24)->typing.Iterable: + pass''') + for code in (code1, code2): + func = extract_node(code) + self.assertEqual(func.as_string(), code) + + @require_version('3.5') + def test_unpacking_in_dicts(self): + code = "{'x': 1, **{'y': 2}}" + node = extract_node(code) + self.assertEqual(node.as_string(), code) + keys = [key for (key, _) in node.items] + self.assertIsInstance(keys[0], nodes.Const) + self.assertIsInstance(keys[1], nodes.DictUnpack) + + @require_version('3.5') + def test_nested_unpacking_in_dicts(self): + code = "{'x': 1, **{'y': 2, **{'z': 3}}}" + node = extract_node(code) + self.assertEqual(node.as_string(), code) + + @require_version('3.5') + def test_unpacking_in_dict_getitem(self): + node = extract_node('{1:2, **{2:3, 3:4}, **{5: 6}}') + for key, expected in ((1, 2), (2, 3), (3, 4), (5, 6)): + value = node.getitem(key) + self.assertIsInstance(value, nodes.Const) + self.assertEqual(value.value, expected) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_raw_building.py b/pymode/libs/astroid/tests/unittest_raw_building.py new file mode 100644 index 00000000..2bdaac17 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_raw_building.py @@ -0,0 +1,85 @@ +import inspect +import os +import unittest + +from six.moves import builtins # pylint: disable=import-error + +from astroid.builder import AstroidBuilder +from astroid.raw_building import ( + attach_dummy_node, build_module, + build_class, build_function, build_from_import +) +from astroid import test_utils +from astroid import nodes +from astroid.bases import BUILTINS + + +class RawBuildingTC(unittest.TestCase): + + def test_attach_dummy_node(self): + node = build_module('MyModule') + attach_dummy_node(node, 'DummyNode') + self.assertEqual(1, len(list(node.get_children()))) + + def test_build_module(self): + node = build_module('MyModule') + self.assertEqual(node.name, 'MyModule') + self.assertEqual(node.pure_python, False) + self.assertEqual(node.package, False) + self.assertEqual(node.parent, None) + + def test_build_class(self): + node = build_class('MyClass') + self.assertEqual(node.name, 'MyClass') + self.assertEqual(node.doc, None) + + def test_build_function(self): + node = build_function('MyFunction') + self.assertEqual(node.name, 'MyFunction') + self.assertEqual(node.doc, None) + + def test_build_function_args(self): + args = ['myArgs1', 'myArgs2'] + node = build_function('MyFunction', args) + self.assertEqual('myArgs1', node.args.args[0].name) + self.assertEqual('myArgs2', node.args.args[1].name) + self.assertEqual(2, len(node.args.args)) + + def test_build_function_defaults(self): + defaults = ['defaults1', 'defaults2'] + node = build_function('MyFunction', None, defaults) + self.assertEqual(2, len(node.args.defaults)) + + def test_build_from_import(self): + names = ['exceptions, inference, inspector'] + node = build_from_import('astroid', names) + self.assertEqual(len(names), len(node.names)) + + @test_utils.require_version(minver='3.0') + def test_io_is__io(self): + # _io module calls itself io. This leads + # to cyclic dependencies when astroid tries to resolve + # what io.BufferedReader is. The code that handles this + # is in astroid.raw_building.imported_member, which verifies + # the true name of the module. + import _io + + builder = AstroidBuilder() + module = builder.inspect_build(_io) + buffered_reader = module.getattr('BufferedReader')[0] + self.assertEqual(buffered_reader.root().name, 'io') + + @unittest.skipUnless(os.name == 'java', 'Requires Jython') + def test_open_is_inferred_correctly(self): + # Lot of Jython builtins don't have a __module__ attribute. + for name, _ in inspect.getmembers(builtins, predicate=inspect.isbuiltin): + if name == 'print': + continue + node = test_utils.extract_node('{0} #@'.format(name)) + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.FunctionDef, name) + self.assertEqual(inferred.root().name, BUILTINS, name) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_regrtest.py b/pymode/libs/astroid/tests/unittest_regrtest.py new file mode 100644 index 00000000..608426b6 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_regrtest.py @@ -0,0 +1,336 @@ +# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +import sys +import unittest +import textwrap + +import six + +from astroid import MANAGER, Instance, nodes +from astroid.bases import BUILTINS +from astroid.builder import AstroidBuilder +from astroid import exceptions +from astroid.raw_building import build_module +from astroid.manager import AstroidManager +from astroid.test_utils import require_version, extract_node +from astroid.tests import resources +from astroid import transforms + + +class NonRegressionTests(resources.AstroidCacheSetupMixin, + unittest.TestCase): + + def setUp(self): + sys.path.insert(0, resources.find('data')) + MANAGER.always_load_extensions = True + MANAGER.astroid_cache[BUILTINS] = self._builtins + + def tearDown(self): + # Since we may have created a brainless manager, leading + # to a new cache builtin module and proxy classes in the constants, + # clear out the global manager cache. + MANAGER.clear_cache(self._builtins) + MANAGER.always_load_extensions = False + sys.path.pop(0) + sys.path_importer_cache.pop(resources.find('data'), None) + + def brainless_manager(self): + manager = AstroidManager() + # avoid caching into the AstroidManager borg since we get problems + # with other tests : + manager.__dict__ = {} + manager._failed_import_hooks = [] + manager.astroid_cache = {} + manager._mod_file_cache = {} + manager._transform = transforms.TransformVisitor() + manager.clear_cache() # trigger proper bootstraping + return manager + + def test_module_path(self): + man = self.brainless_manager() + mod = man.ast_from_module_name('package.import_package_subpackage_module') + package = next(mod.igetattr('package')) + self.assertEqual(package.name, 'package') + subpackage = next(package.igetattr('subpackage')) + self.assertIsInstance(subpackage, nodes.Module) + self.assertTrue(subpackage.package) + self.assertEqual(subpackage.name, 'package.subpackage') + module = next(subpackage.igetattr('module')) + self.assertEqual(module.name, 'package.subpackage.module') + + + def test_package_sidepackage(self): + manager = self.brainless_manager() + assert 'package.sidepackage' not in MANAGER.astroid_cache + package = manager.ast_from_module_name('absimp') + self.assertIsInstance(package, nodes.Module) + self.assertTrue(package.package) + subpackage = next(package.getattr('sidepackage')[0].infer()) + self.assertIsInstance(subpackage, nodes.Module) + self.assertTrue(subpackage.package) + self.assertEqual(subpackage.name, 'absimp.sidepackage') + + + def test_living_property(self): + builder = AstroidBuilder() + builder._done = {} + builder._module = sys.modules[__name__] + builder.object_build(build_module('module_name', ''), Whatever) + + + def test_new_style_class_detection(self): + try: + import pygtk # pylint: disable=unused-variable + except ImportError: + self.skipTest('test skipped: pygtk is not available') + # XXX may fail on some pygtk version, because objects in + # gobject._gobject have __module__ set to gobject :( + builder = AstroidBuilder() + data = """ +import pygtk +pygtk.require("2.6") +import gobject + +class A(gobject.GObject): + pass +""" + astroid = builder.string_build(data, __name__, __file__) + a = astroid['A'] + self.assertTrue(a.newstyle) + + + def test_pylint_config_attr(self): + try: + from pylint import lint # pylint: disable=unused-variable + except ImportError: + self.skipTest('pylint not available') + mod = MANAGER.ast_from_module_name('pylint.lint') + pylinter = mod['PyLinter'] + expect = ['OptionsManagerMixIn', 'object', 'MessagesHandlerMixIn', + 'ReportsHandlerMixIn', 'BaseTokenChecker', 'BaseChecker', + 'OptionsProviderMixIn'] + self.assertListEqual([c.name for c in pylinter.ancestors()], + expect) + self.assertTrue(list(Instance(pylinter).getattr('config'))) + inferred = list(Instance(pylinter).igetattr('config')) + self.assertEqual(len(inferred), 1) + self.assertEqual(inferred[0].root().name, 'optparse') + self.assertEqual(inferred[0].name, 'Values') + + def test_numpy_crash(self): + """test don't crash on numpy""" + #a crash occured somewhere in the past, and an + # InferenceError instead of a crash was better, but now we even infer! + try: + import numpy # pylint: disable=unused-variable + except ImportError: + self.skipTest('test skipped: numpy is not available') + builder = AstroidBuilder() + data = """ +from numpy import multiply + +multiply(1, 2, 3) +""" + astroid = builder.string_build(data, __name__, __file__) + callfunc = astroid.body[1].value.func + inferred = callfunc.inferred() + self.assertEqual(len(inferred), 2) + + @require_version('3.0') + def test_nameconstant(self): + # used to fail for Python 3.4 + builder = AstroidBuilder() + astroid = builder.string_build("def test(x=True): pass") + default = astroid.body[0].args.args[0] + self.assertEqual(default.name, 'x') + self.assertEqual(next(default.infer()).value, True) + + @require_version('2.7') + def test_with_infer_assignnames(self): + builder = AstroidBuilder() + data = """ +with open('a.txt') as stream, open('b.txt'): + stream.read() +""" + astroid = builder.string_build(data, __name__, __file__) + # Used to crash due to the fact that the second + # context manager didn't use an assignment name. + list(astroid.nodes_of_class(nodes.Call))[-1].inferred() + + def test_recursion_regression_issue25(self): + builder = AstroidBuilder() + data = """ +import recursion as base + +_real_Base = base.Base + +class Derived(_real_Base): + pass + +def run(): + base.Base = Derived +""" + astroid = builder.string_build(data, __name__, __file__) + # Used to crash in _is_metaclass, due to wrong + # ancestors chain + classes = astroid.nodes_of_class(nodes.ClassDef) + for klass in classes: + # triggers the _is_metaclass call + klass.type # pylint: disable=pointless-statement + + def test_decorator_callchain_issue42(self): + builder = AstroidBuilder() + data = """ + +def test(): + def factory(func): + def newfunc(): + func() + return newfunc + return factory + +@test() +def crash(): + pass +""" + astroid = builder.string_build(data, __name__, __file__) + self.assertEqual(astroid['crash'].type, 'function') + + def test_filter_stmts_scoping(self): + builder = AstroidBuilder() + data = """ +def test(): + compiler = int() + class B(compiler.__class__): + pass + compiler = B() + return compiler +""" + astroid = builder.string_build(data, __name__, __file__) + test = astroid['test'] + result = next(test.infer_call_result(astroid)) + self.assertIsInstance(result, Instance) + base = next(result._proxied.bases[0].infer()) + self.assertEqual(base.name, 'int') + + def test_ancestors_patching_class_recursion(self): + node = AstroidBuilder().string_build(textwrap.dedent(""" + import string + Template = string.Template + + class A(Template): + pass + + class B(A): + pass + + def test(x=False): + if x: + string.Template = A + else: + string.Template = B + """)) + klass = node['A'] + ancestors = list(klass.ancestors()) + self.assertEqual(ancestors[0].qname(), 'string.Template') + + def test_ancestors_yes_in_bases(self): + # Test for issue https://bitbucket.org/logilab/astroid/issue/84 + # This used to crash astroid with a TypeError, because an YES + # node was present in the bases + node = extract_node(""" + def with_metaclass(meta, *bases): + class metaclass(meta): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + import lala + + class A(with_metaclass(object, lala.lala)): #@ + pass + """) + ancestors = list(node.ancestors()) + if six.PY3: + self.assertEqual(len(ancestors), 1) + self.assertEqual(ancestors[0].qname(), + "{}.object".format(BUILTINS)) + else: + self.assertEqual(len(ancestors), 0) + + def test_ancestors_missing_from_function(self): + # Test for https://www.logilab.org/ticket/122793 + node = extract_node(''' + def gen(): yield + GEN = gen() + next(GEN) + ''') + self.assertRaises(exceptions.InferenceError, next, node.infer()) + + def test_unicode_in_docstring(self): + # Crashed for astroid==1.4.1 + # Test for https://bitbucket.org/logilab/astroid/issues/273/ + + # In a regular file, "coding: utf-8" would have been used. + node = extract_node(u''' + from __future__ import unicode_literals + + class MyClass(object): + def method(self): + "With unicode : %s " + + instance = MyClass() + ''' % u"\u2019") + + next(node.value.infer()).as_string() + + def test_binop_generates_nodes_with_parents(self): + node = extract_node(''' + def no_op(*args): + pass + def foo(*args): + def inner(*more_args): + args + more_args #@ + return inner + ''') + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Tuple) + self.assertIsNotNone(inferred.parent) + self.assertIsInstance(inferred.parent, nodes.BinOp) + + def test_decorator_names_inference_error_leaking(self): + node = extract_node(''' + class Parent(object): + @property + def foo(self): + pass + + class Child(Parent): + @Parent.foo.getter + def foo(self): #@ + return super(Child, self).foo + ['oink'] + ''') + inferred = next(node.infer()) + self.assertEqual(inferred.decoratornames(), set()) + + +class Whatever(object): + a = property(lambda x: x, lambda x: x) + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_scoped_nodes.py b/pymode/libs/astroid/tests/unittest_scoped_nodes.py new file mode 100644 index 00000000..0019ee08 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_scoped_nodes.py @@ -0,0 +1,1571 @@ +# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +"""tests for specific behaviour of astroid scoped nodes (i.e. module, class and +function) +""" +import os +import sys +from functools import partial +import unittest +import warnings + +from astroid import builder +from astroid import nodes +from astroid import scoped_nodes +from astroid import util +from astroid.exceptions import ( + InferenceError, NotFoundError, + NoDefault, ResolveError, MroError, + InconsistentMroError, DuplicateBasesError, +) +from astroid.bases import ( + BUILTINS, Instance, + BoundMethod, UnboundMethod, Generator +) +from astroid import __pkginfo__ +from astroid import test_utils +from astroid.tests import resources + + +def _test_dict_interface(self, node, test_attr): + self.assertIs(node[test_attr], node[test_attr]) + self.assertIn(test_attr, node) + node.keys() + node.values() + node.items() + iter(node) + + +class ModuleLoader(resources.SysPathSetup): + def setUp(self): + super(ModuleLoader, self).setUp() + self.module = resources.build_file('data/module.py', 'data.module') + self.module2 = resources.build_file('data/module2.py', 'data.module2') + self.nonregr = resources.build_file('data/nonregr.py', 'data.nonregr') + self.pack = resources.build_file('data/__init__.py', 'data') + + +class ModuleNodeTest(ModuleLoader, unittest.TestCase): + + def test_special_attributes(self): + self.assertEqual(len(self.module.getattr('__name__')), 1) + self.assertIsInstance(self.module.getattr('__name__')[0], nodes.Const) + self.assertEqual(self.module.getattr('__name__')[0].value, 'data.module') + self.assertEqual(len(self.module.getattr('__doc__')), 1) + self.assertIsInstance(self.module.getattr('__doc__')[0], nodes.Const) + self.assertEqual(self.module.getattr('__doc__')[0].value, 'test module for astroid\n') + self.assertEqual(len(self.module.getattr('__file__')), 1) + self.assertIsInstance(self.module.getattr('__file__')[0], nodes.Const) + self.assertEqual(self.module.getattr('__file__')[0].value, + os.path.abspath(resources.find('data/module.py'))) + self.assertEqual(len(self.module.getattr('__dict__')), 1) + self.assertIsInstance(self.module.getattr('__dict__')[0], nodes.Dict) + self.assertRaises(NotFoundError, self.module.getattr, '__path__') + self.assertEqual(len(self.pack.getattr('__path__')), 1) + self.assertIsInstance(self.pack.getattr('__path__')[0], nodes.List) + + def test_dict_interface(self): + _test_dict_interface(self, self.module, 'YO') + + def test_getattr(self): + yo = self.module.getattr('YO')[0] + self.assertIsInstance(yo, nodes.ClassDef) + self.assertEqual(yo.name, 'YO') + red = next(self.module.igetattr('redirect')) + self.assertIsInstance(red, nodes.FunctionDef) + self.assertEqual(red.name, 'four_args') + namenode = next(self.module.igetattr('NameNode')) + self.assertIsInstance(namenode, nodes.ClassDef) + self.assertEqual(namenode.name, 'Name') + # resolve packageredirection + mod = resources.build_file('data/appl/myConnection.py', + 'data.appl.myConnection') + ssl = next(mod.igetattr('SSL1')) + cnx = next(ssl.igetattr('Connection')) + self.assertEqual(cnx.__class__, nodes.ClassDef) + self.assertEqual(cnx.name, 'Connection') + self.assertEqual(cnx.root().name, 'data.SSL1.Connection1') + self.assertEqual(len(self.nonregr.getattr('enumerate')), 2) + # raise ResolveError + self.assertRaises(InferenceError, self.nonregr.igetattr, 'YOAA') + + def test_wildcard_import_names(self): + m = resources.build_file('data/all.py', 'all') + self.assertEqual(m.wildcard_import_names(), ['Aaa', '_bla', 'name']) + m = resources.build_file('data/notall.py', 'notall') + res = sorted(m.wildcard_import_names()) + self.assertEqual(res, ['Aaa', 'func', 'name', 'other']) + + def test_public_names(self): + m = builder.parse(''' + name = 'a' + _bla = 2 + other = 'o' + class Aaa: pass + def func(): print('yo') + __all__ = 'Aaa', '_bla', 'name' + ''') + values = sorted(['Aaa', 'name', 'other', 'func']) + self.assertEqual(sorted(m._public_names()), values) + m = builder.parse(''' + name = 'a' + _bla = 2 + other = 'o' + class Aaa: pass + + def func(): return 'yo' + ''') + res = sorted(m._public_names()) + self.assertEqual(res, values) + + m = builder.parse(''' + from missing import tzop + trop = "test" + __all__ = (trop, "test1", tzop, 42) + ''') + res = sorted(m._public_names()) + self.assertEqual(res, ["trop", "tzop"]) + + m = builder.parse(''' + test = tzop = 42 + __all__ = ('test', ) + ('tzop', ) + ''') + res = sorted(m._public_names()) + self.assertEqual(res, ['test', 'tzop']) + + def test_module_getattr(self): + data = ''' + appli = application + appli += 2 + del appli + ''' + astroid = builder.parse(data, __name__) + # test del statement not returned by getattr + self.assertEqual(len(astroid.getattr('appli')), 2, + astroid.getattr('appli')) + + def test_relative_to_absolute_name(self): + # package + mod = nodes.Module('very.multi.package', 'doc') + mod.package = True + modname = mod.relative_to_absolute_name('utils', 1) + self.assertEqual(modname, 'very.multi.package.utils') + modname = mod.relative_to_absolute_name('utils', 2) + self.assertEqual(modname, 'very.multi.utils') + modname = mod.relative_to_absolute_name('utils', 0) + self.assertEqual(modname, 'very.multi.package.utils') + modname = mod.relative_to_absolute_name('', 1) + self.assertEqual(modname, 'very.multi.package') + # non package + mod = nodes.Module('very.multi.module', 'doc') + mod.package = False + modname = mod.relative_to_absolute_name('utils', 0) + self.assertEqual(modname, 'very.multi.utils') + modname = mod.relative_to_absolute_name('utils', 1) + self.assertEqual(modname, 'very.multi.utils') + modname = mod.relative_to_absolute_name('utils', 2) + self.assertEqual(modname, 'very.utils') + modname = mod.relative_to_absolute_name('', 1) + self.assertEqual(modname, 'very.multi') + + def test_import_1(self): + data = '''from . import subpackage''' + sys.path.insert(0, resources.find('data')) + astroid = builder.parse(data, 'package', 'data/package/__init__.py') + try: + m = astroid.import_module('', level=1) + self.assertEqual(m.name, 'package') + inferred = list(astroid.igetattr('subpackage')) + self.assertEqual(len(inferred), 1) + self.assertEqual(inferred[0].name, 'package.subpackage') + finally: + del sys.path[0] + + + def test_import_2(self): + data = '''from . import subpackage as pouet''' + astroid = builder.parse(data, 'package', 'data/package/__init__.py') + sys.path.insert(0, resources.find('data')) + try: + m = astroid.import_module('', level=1) + self.assertEqual(m.name, 'package') + inferred = list(astroid.igetattr('pouet')) + self.assertEqual(len(inferred), 1) + self.assertEqual(inferred[0].name, 'package.subpackage') + finally: + del sys.path[0] + + + def test_file_stream_in_memory(self): + data = '''irrelevant_variable is irrelevant''' + astroid = builder.parse(data, 'in_memory') + with warnings.catch_warnings(record=True): + self.assertEqual(astroid.file_stream.read().decode(), data) + + def test_file_stream_physical(self): + path = resources.find('data/all.py') + astroid = builder.AstroidBuilder().file_build(path, 'all') + with open(path, 'rb') as file_io: + with warnings.catch_warnings(record=True): + self.assertEqual(astroid.file_stream.read(), file_io.read()) + + def test_file_stream_api(self): + path = resources.find('data/all.py') + astroid = builder.AstroidBuilder().file_build(path, 'all') + if __pkginfo__.numversion >= (1, 6): + # file_stream is slated for removal in astroid 1.6. + with self.assertRaises(AttributeError): + # pylint: disable=pointless-statement + astroid.file_stream + else: + # Until astroid 1.6, Module.file_stream will emit + # PendingDeprecationWarning in 1.4, DeprecationWarning + # in 1.5 and finally it will be removed in 1.6, leaving + # only Module.stream as the recommended way to retrieve + # its file stream. + with warnings.catch_warnings(record=True) as cm: + warnings.simplefilter("always") + self.assertIsNot(astroid.file_stream, astroid.file_stream) + self.assertGreater(len(cm), 1) + self.assertEqual(cm[0].category, PendingDeprecationWarning) + + def test_stream_api(self): + path = resources.find('data/all.py') + astroid = builder.AstroidBuilder().file_build(path, 'all') + stream = astroid.stream() + self.assertTrue(hasattr(stream, 'close')) + with stream: + with open(path, 'rb') as file_io: + self.assertEqual(stream.read(), file_io.read()) + + +class FunctionNodeTest(ModuleLoader, unittest.TestCase): + + def test_special_attributes(self): + func = self.module2['make_class'] + self.assertEqual(len(func.getattr('__name__')), 1) + self.assertIsInstance(func.getattr('__name__')[0], nodes.Const) + self.assertEqual(func.getattr('__name__')[0].value, 'make_class') + self.assertEqual(len(func.getattr('__doc__')), 1) + self.assertIsInstance(func.getattr('__doc__')[0], nodes.Const) + self.assertEqual(func.getattr('__doc__')[0].value, 'check base is correctly resolved to Concrete0') + self.assertEqual(len(self.module.getattr('__dict__')), 1) + self.assertIsInstance(self.module.getattr('__dict__')[0], nodes.Dict) + + def test_dict_interface(self): + _test_dict_interface(self, self.module['global_access'], 'local') + + def test_default_value(self): + func = self.module2['make_class'] + self.assertIsInstance(func.args.default_value('base'), nodes.Attribute) + self.assertRaises(NoDefault, func.args.default_value, 'args') + self.assertRaises(NoDefault, func.args.default_value, 'kwargs') + self.assertRaises(NoDefault, func.args.default_value, 'any') + #self.assertIsInstance(func.mularg_class('args'), nodes.Tuple) + #self.assertIsInstance(func.mularg_class('kwargs'), nodes.Dict) + #self.assertIsNone(func.mularg_class('base')) + + def test_navigation(self): + function = self.module['global_access'] + self.assertEqual(function.statement(), function) + l_sibling = function.previous_sibling() + # check taking parent if child is not a stmt + self.assertIsInstance(l_sibling, nodes.Assign) + child = function.args.args[0] + self.assertIs(l_sibling, child.previous_sibling()) + r_sibling = function.next_sibling() + self.assertIsInstance(r_sibling, nodes.ClassDef) + self.assertEqual(r_sibling.name, 'YO') + self.assertIs(r_sibling, child.next_sibling()) + last = r_sibling.next_sibling().next_sibling().next_sibling() + self.assertIsInstance(last, nodes.Assign) + self.assertIsNone(last.next_sibling()) + first = l_sibling.root().body[0] + self.assertIsNone(first.previous_sibling()) + + def test_nested_args(self): + if sys.version_info >= (3, 0): + self.skipTest("nested args has been removed in py3.x") + code = ''' + def nested_args(a, (b, c, d)): + "nested arguments test" + ''' + tree = builder.parse(code) + func = tree['nested_args'] + self.assertEqual(sorted(func._locals), ['a', 'b', 'c', 'd']) + self.assertEqual(func.args.format_args(), 'a, (b, c, d)') + + def test_four_args(self): + func = self.module['four_args'] + #self.assertEqual(func.args.args, ['a', ('b', 'c', 'd')]) + local = sorted(func.keys()) + self.assertEqual(local, ['a', 'b', 'c', 'd']) + self.assertEqual(func.type, 'function') + + def test_format_args(self): + func = self.module2['make_class'] + self.assertEqual(func.args.format_args(), + 'any, base=data.module.YO, *args, **kwargs') + func = self.module['four_args'] + self.assertEqual(func.args.format_args(), 'a, b, c, d') + + def test_is_generator(self): + self.assertTrue(self.module2['generator'].is_generator()) + self.assertFalse(self.module2['not_a_generator'].is_generator()) + self.assertFalse(self.module2['make_class'].is_generator()) + + def test_is_abstract(self): + method = self.module2['AbstractClass']['to_override'] + self.assertTrue(method.is_abstract(pass_is_abstract=False)) + self.assertEqual(method.qname(), 'data.module2.AbstractClass.to_override') + self.assertEqual(method.pytype(), '%s.instancemethod' % BUILTINS) + method = self.module2['AbstractClass']['return_something'] + self.assertFalse(method.is_abstract(pass_is_abstract=False)) + # non regression : test raise "string" doesn't cause an exception in is_abstract + func = self.module2['raise_string'] + self.assertFalse(func.is_abstract(pass_is_abstract=False)) + + def test_is_abstract_decorated(self): + methods = test_utils.extract_node(""" + import abc + + class Klass(object): + @abc.abstractproperty + def prop(self): #@ + pass + + @abc.abstractmethod + def method1(self): #@ + pass + + some_other_decorator = lambda x: x + @some_other_decorator + def method2(self): #@ + pass + """) + self.assertTrue(methods[0].is_abstract(pass_is_abstract=False)) + self.assertTrue(methods[1].is_abstract(pass_is_abstract=False)) + self.assertFalse(methods[2].is_abstract(pass_is_abstract=False)) + +## def test_raises(self): +## method = self.module2['AbstractClass']['to_override'] +## self.assertEqual([str(term) for term in method.raises()], +## ["Call(Name('NotImplementedError'), [], None, None)"] ) + +## def test_returns(self): +## method = self.module2['AbstractClass']['return_something'] +## # use string comp since Node doesn't handle __cmp__ +## self.assertEqual([str(term) for term in method.returns()], +## ["Const('toto')", "Const(None)"]) + + def test_lambda_pytype(self): + data = ''' + def f(): + g = lambda: None + ''' + astroid = builder.parse(data) + g = list(astroid['f'].ilookup('g'))[0] + self.assertEqual(g.pytype(), '%s.function' % BUILTINS) + + def test_lambda_qname(self): + astroid = builder.parse('lmbd = lambda: None', __name__) + self.assertEqual('%s.' % __name__, astroid['lmbd'].parent.value.qname()) + + def test_is_method(self): + data = ''' + class A: + def meth1(self): + return 1 + @classmethod + def meth2(cls): + return 2 + @staticmethod + def meth3(): + return 3 + + def function(): + return 0 + + @staticmethod + def sfunction(): + return -1 + ''' + astroid = builder.parse(data) + self.assertTrue(astroid['A']['meth1'].is_method()) + self.assertTrue(astroid['A']['meth2'].is_method()) + self.assertTrue(astroid['A']['meth3'].is_method()) + self.assertFalse(astroid['function'].is_method()) + self.assertFalse(astroid['sfunction'].is_method()) + + def test_argnames(self): + if sys.version_info < (3, 0): + code = 'def f(a, (b, c), *args, **kwargs): pass' + else: + code = 'def f(a, b, c, *args, **kwargs): pass' + astroid = builder.parse(code, __name__) + self.assertEqual(astroid['f'].argnames(), ['a', 'b', 'c', 'args', 'kwargs']) + + def test_return_nothing(self): + """test inferred value on a function with empty return""" + data = ''' + def func(): + return + + a = func() + ''' + astroid = builder.parse(data) + call = astroid.body[1].value + func_vals = call.inferred() + self.assertEqual(len(func_vals), 1) + self.assertIsInstance(func_vals[0], nodes.Const) + self.assertIsNone(func_vals[0].value) + + def test_func_instance_attr(self): + """test instance attributes for functions""" + data = """ + def test(): + print(test.bar) + + test.bar = 1 + test() + """ + astroid = builder.parse(data, 'mod') + func = astroid.body[2].value.func.inferred()[0] + self.assertIsInstance(func, nodes.FunctionDef) + self.assertEqual(func.name, 'test') + one = func.getattr('bar')[0].inferred()[0] + self.assertIsInstance(one, nodes.Const) + self.assertEqual(one.value, 1) + + def test_type_builtin_descriptor_subclasses(self): + astroid = builder.parse(""" + class classonlymethod(classmethod): + pass + class staticonlymethod(staticmethod): + pass + + class Node: + @classonlymethod + def clsmethod_subclass(cls): + pass + @classmethod + def clsmethod(cls): + pass + @staticonlymethod + def staticmethod_subclass(cls): + pass + @staticmethod + def stcmethod(cls): + pass + """) + node = astroid._locals['Node'][0] + self.assertEqual(node._locals['clsmethod_subclass'][0].type, + 'classmethod') + self.assertEqual(node._locals['clsmethod'][0].type, + 'classmethod') + self.assertEqual(node._locals['staticmethod_subclass'][0].type, + 'staticmethod') + self.assertEqual(node._locals['stcmethod'][0].type, + 'staticmethod') + + def test_decorator_builtin_descriptors(self): + astroid = builder.parse(""" + def static_decorator(platform=None, order=50): + def wrapper(f): + f.cgm_module = True + f.cgm_module_order = order + f.cgm_module_platform = platform + return staticmethod(f) + return wrapper + + def long_classmethod_decorator(platform=None, order=50): + def wrapper(f): + def wrapper2(f): + def wrapper3(f): + f.cgm_module = True + f.cgm_module_order = order + f.cgm_module_platform = platform + return classmethod(f) + return wrapper3(f) + return wrapper2(f) + return wrapper + + def classmethod_decorator(platform=None): + def wrapper(f): + f.platform = platform + return classmethod(f) + return wrapper + + def classmethod_wrapper(fn): + def wrapper(cls, *args, **kwargs): + result = fn(cls, *args, **kwargs) + return result + + return classmethod(wrapper) + + def staticmethod_wrapper(fn): + def wrapper(*args, **kwargs): + return fn(*args, **kwargs) + return staticmethod(wrapper) + + class SomeClass(object): + @static_decorator() + def static(node, cfg): + pass + @classmethod_decorator() + def classmethod(cls): + pass + @static_decorator + def not_so_static(node): + pass + @classmethod_decorator + def not_so_classmethod(node): + pass + @classmethod_wrapper + def classmethod_wrapped(cls): + pass + @staticmethod_wrapper + def staticmethod_wrapped(): + pass + @long_classmethod_decorator() + def long_classmethod(cls): + pass + """) + node = astroid._locals['SomeClass'][0] + self.assertEqual(node._locals['static'][0].type, + 'staticmethod') + self.assertEqual(node._locals['classmethod'][0].type, + 'classmethod') + self.assertEqual(node._locals['not_so_static'][0].type, + 'method') + self.assertEqual(node._locals['not_so_classmethod'][0].type, + 'method') + self.assertEqual(node._locals['classmethod_wrapped'][0].type, + 'classmethod') + self.assertEqual(node._locals['staticmethod_wrapped'][0].type, + 'staticmethod') + self.assertEqual(node._locals['long_classmethod'][0].type, + 'classmethod') + + def test_igetattr(self): + func = test_utils.extract_node(''' + def test(): + pass + ''') + func._instance_attrs['value'] = [nodes.Const(42)] + value = func.getattr('value') + self.assertEqual(len(value), 1) + self.assertIsInstance(value[0], nodes.Const) + self.assertEqual(value[0].value, 42) + inferred = next(func.igetattr('value')) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, 42) + + +class ClassNodeTest(ModuleLoader, unittest.TestCase): + + def test_dict_interface(self): + _test_dict_interface(self, self.module['YOUPI'], 'method') + + def test_cls_special_attributes_1(self): + cls = self.module['YO'] + self.assertEqual(len(cls.getattr('__bases__')), 1) + self.assertEqual(len(cls.getattr('__name__')), 1) + self.assertIsInstance(cls.getattr('__name__')[0], nodes.Const) + self.assertEqual(cls.getattr('__name__')[0].value, 'YO') + self.assertEqual(len(cls.getattr('__doc__')), 1) + self.assertIsInstance(cls.getattr('__doc__')[0], nodes.Const) + self.assertEqual(cls.getattr('__doc__')[0].value, 'hehe') + self.assertEqual(len(cls.getattr('__module__')), 1) + self.assertIsInstance(cls.getattr('__module__')[0], nodes.Const) + self.assertEqual(cls.getattr('__module__')[0].value, 'data.module') + self.assertEqual(len(cls.getattr('__dict__')), 1) + if not cls.newstyle: + self.assertRaises(NotFoundError, cls.getattr, '__mro__') + for cls in (nodes.List._proxied, nodes.Const(1)._proxied): + self.assertEqual(len(cls.getattr('__bases__')), 1) + self.assertEqual(len(cls.getattr('__name__')), 1) + self.assertEqual(len(cls.getattr('__doc__')), 1, (cls, cls.getattr('__doc__'))) + self.assertEqual(cls.getattr('__doc__')[0].value, cls.doc) + self.assertEqual(len(cls.getattr('__module__')), 1) + self.assertEqual(len(cls.getattr('__dict__')), 1) + self.assertEqual(len(cls.getattr('__mro__')), 1) + + def test__mro__attribute(self): + node = test_utils.extract_node(''' + class A(object): pass + class B(object): pass + class C(A, B): pass + ''') + mro = node.getattr('__mro__')[0] + self.assertIsInstance(mro, nodes.Tuple) + self.assertEqual(mro.elts, node.mro()) + + def test__bases__attribute(self): + node = test_utils.extract_node(''' + class A(object): pass + class B(object): pass + class C(A, B): pass + class D(C): pass + ''') + bases = node.getattr('__bases__')[0] + self.assertIsInstance(bases, nodes.Tuple) + self.assertEqual(len(bases.elts), 1) + self.assertIsInstance(bases.elts[0], nodes.ClassDef) + self.assertEqual(bases.elts[0].name, 'C') + + def test_cls_special_attributes_2(self): + astroid = builder.parse(''' + class A: pass + class B: pass + + A.__bases__ += (B,) + ''', __name__) + self.assertEqual(len(astroid['A'].getattr('__bases__')), 2) + self.assertIsInstance(astroid['A'].getattr('__bases__')[0], nodes.Tuple) + self.assertIsInstance(astroid['A'].getattr('__bases__')[1], nodes.AssignAttr) + + def test_instance_special_attributes(self): + for inst in (Instance(self.module['YO']), nodes.List(), nodes.Const(1)): + self.assertRaises(NotFoundError, inst.getattr, '__mro__') + self.assertRaises(NotFoundError, inst.getattr, '__bases__') + self.assertRaises(NotFoundError, inst.getattr, '__name__') + self.assertEqual(len(inst.getattr('__dict__')), 1) + self.assertEqual(len(inst.getattr('__doc__')), 1) + + def test_navigation(self): + klass = self.module['YO'] + self.assertEqual(klass.statement(), klass) + l_sibling = klass.previous_sibling() + self.assertTrue(isinstance(l_sibling, nodes.FunctionDef), l_sibling) + self.assertEqual(l_sibling.name, 'global_access') + r_sibling = klass.next_sibling() + self.assertIsInstance(r_sibling, nodes.ClassDef) + self.assertEqual(r_sibling.name, 'YOUPI') + + def test_local_attr_ancestors(self): + module = builder.parse(''' + class A(): + def __init__(self): pass + class B(A): pass + class C(B): pass + class D(object): pass + class F(): pass + class E(F, D): pass + ''') + # Test old-style (Python 2) / new-style (Python 3+) ancestors lookups + klass2 = module['C'] + it = klass2.local_attr_ancestors('__init__') + anc_klass = next(it) + self.assertIsInstance(anc_klass, nodes.ClassDef) + self.assertEqual(anc_klass.name, 'A') + if sys.version_info[0] == 2: + self.assertRaises(StopIteration, partial(next, it)) + else: + anc_klass = next(it) + self.assertIsInstance(anc_klass, nodes.ClassDef) + self.assertEqual(anc_klass.name, 'object') + self.assertRaises(StopIteration, partial(next, it)) + + it = klass2.local_attr_ancestors('method') + self.assertRaises(StopIteration, partial(next, it)) + + # Test mixed-style ancestor lookups + klass2 = module['E'] + it = klass2.local_attr_ancestors('__init__') + anc_klass = next(it) + self.assertIsInstance(anc_klass, nodes.ClassDef) + self.assertEqual(anc_klass.name, 'object') + self.assertRaises(StopIteration, partial(next, it)) + + def test_local_attr_mro(self): + module = builder.parse(''' + class A(object): + def __init__(self): pass + class B(A): + def __init__(self, arg, arg2): pass + class C(A): pass + class D(C, B): pass + ''') + dclass = module['D'] + init = dclass.local_attr('__init__')[0] + self.assertIsInstance(init, nodes.FunctionDef) + self.assertEqual(init.parent.name, 'B') + + cclass = module['C'] + init = cclass.local_attr('__init__')[0] + self.assertIsInstance(init, nodes.FunctionDef) + self.assertEqual(init.parent.name, 'A') + + ancestors = list(dclass.local_attr_ancestors('__init__')) + self.assertEqual([node.name for node in ancestors], ['B', 'A', 'object']) + + def test_instance_attr_ancestors(self): + klass2 = self.module['YOUPI'] + it = klass2.instance_attr_ancestors('yo') + anc_klass = next(it) + self.assertIsInstance(anc_klass, nodes.ClassDef) + self.assertEqual(anc_klass.name, 'YO') + self.assertRaises(StopIteration, partial(next, it)) + klass2 = self.module['YOUPI'] + it = klass2.instance_attr_ancestors('member') + self.assertRaises(StopIteration, partial(next, it)) + + def test_methods(self): + expected_methods = {'__init__', 'class_method', 'method', 'static_method'} + klass2 = self.module['YOUPI'] + methods = {m.name for m in klass2.methods()} + self.assertTrue( + methods.issuperset(expected_methods)) + methods = {m.name for m in klass2.mymethods()} + self.assertSetEqual(expected_methods, methods) + klass2 = self.module2['Specialization'] + methods = {m.name for m in klass2.mymethods()} + self.assertSetEqual(set([]), methods) + method_locals = klass2.local_attr('method') + self.assertEqual(len(method_locals), 1) + self.assertEqual(method_locals[0].name, 'method') + self.assertRaises(NotFoundError, klass2.local_attr, 'nonexistant') + methods = {m.name for m in klass2.methods()} + self.assertTrue(methods.issuperset(expected_methods)) + + #def test_rhs(self): + # my_dict = self.module['MY_DICT'] + # self.assertIsInstance(my_dict.rhs(), nodes.Dict) + # a = self.module['YO']['a'] + # value = a.rhs() + # self.assertIsInstance(value, nodes.Const) + # self.assertEqual(value.value, 1) + + @unittest.skipIf(sys.version_info[0] >= 3, "Python 2 class semantics required.") + def test_ancestors(self): + klass = self.module['YOUPI'] + self.assertEqual(['YO'], [a.name for a in klass.ancestors()]) + klass = self.module2['Specialization'] + self.assertEqual(['YOUPI', 'YO'], [a.name for a in klass.ancestors()]) + + @unittest.skipIf(sys.version_info[0] < 3, "Python 3 class semantics required.") + def test_ancestors_py3(self): + klass = self.module['YOUPI'] + self.assertEqual(['YO', 'object'], [a.name for a in klass.ancestors()]) + klass = self.module2['Specialization'] + self.assertEqual(['YOUPI', 'YO', 'object'], [a.name for a in klass.ancestors()]) + + def test_type(self): + klass = self.module['YOUPI'] + self.assertEqual(klass.type, 'class') + klass = self.module2['Metaclass'] + self.assertEqual(klass.type, 'metaclass') + klass = self.module2['MyException'] + self.assertEqual(klass.type, 'exception') + klass = self.module2['MyError'] + self.assertEqual(klass.type, 'exception') + # the following class used to be detected as a metaclass + # after the fix which used instance._proxied in .ancestors(), + # when in fact it is a normal class + klass = self.module2['NotMetaclass'] + self.assertEqual(klass.type, 'class') + + def test_inner_classes(self): + eee = self.nonregr['Ccc']['Eee'] + self.assertEqual([n.name for n in eee.ancestors()], ['Ddd', 'Aaa', 'object']) + + + def test_classmethod_attributes(self): + data = ''' + class WebAppObject(object): + def registered(cls, application): + cls.appli = application + cls.schema = application.schema + cls.config = application.config + return cls + registered = classmethod(registered) + ''' + astroid = builder.parse(data, __name__) + cls = astroid['WebAppObject'] + self.assertEqual(sorted(cls._locals.keys()), + ['appli', 'config', 'registered', 'schema']) + + def test_class_getattr(self): + data = ''' + class WebAppObject(object): + appli = application + appli += 2 + del self.appli + ''' + astroid = builder.parse(data, __name__) + cls = astroid['WebAppObject'] + # test del statement not returned by getattr + self.assertEqual(len(cls.getattr('appli')), 2) + + + def test_instance_getattr(self): + data = ''' + class WebAppObject(object): + def __init__(self, application): + self.appli = application + self.appli += 2 + del self.appli + ''' + astroid = builder.parse(data) + inst = Instance(astroid['WebAppObject']) + # test del statement not returned by getattr + self.assertEqual(len(inst.getattr('appli')), 2) + + + def test_instance_getattr_with_class_attr(self): + data = ''' + class Parent: + aa = 1 + cc = 1 + + class Klass(Parent): + aa = 0 + bb = 0 + + def incr(self, val): + self.cc = self.aa + if val > self.aa: + val = self.aa + if val < self.bb: + val = self.bb + self.aa += val + ''' + astroid = builder.parse(data) + inst = Instance(astroid['Klass']) + self.assertEqual(len(inst.getattr('aa')), 3, inst.getattr('aa')) + self.assertEqual(len(inst.getattr('bb')), 1, inst.getattr('bb')) + self.assertEqual(len(inst.getattr('cc')), 2, inst.getattr('cc')) + + + def test_getattr_method_transform(self): + data = ''' + class Clazz(object): + + def m1(self, value): + self.value = value + m2 = m1 + + def func(arg1, arg2): + "function that will be used as a method" + return arg1.value + arg2 + + Clazz.m3 = func + inst = Clazz() + inst.m4 = func + ''' + astroid = builder.parse(data) + cls = astroid['Clazz'] + # test del statement not returned by getattr + for method in ('m1', 'm2', 'm3'): + inferred = list(cls.igetattr(method)) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], UnboundMethod) + inferred = list(Instance(cls).igetattr(method)) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], BoundMethod) + inferred = list(Instance(cls).igetattr('m4')) + self.assertEqual(len(inferred), 1) + self.assertIsInstance(inferred[0], nodes.FunctionDef) + + def test_getattr_from_grandpa(self): + data = ''' + class Future: + attr = 1 + + class Present(Future): + pass + + class Past(Present): + pass + ''' + astroid = builder.parse(data) + past = astroid['Past'] + attr = past.getattr('attr') + self.assertEqual(len(attr), 1) + attr1 = attr[0] + self.assertIsInstance(attr1, nodes.AssignName) + self.assertEqual(attr1.name, 'attr') + + def test_function_with_decorator_lineno(self): + data = ''' + @f(a=2, + b=3) + def g1(x): + print(x) + + @f(a=2, + b=3) + def g2(): + pass + ''' + astroid = builder.parse(data) + self.assertEqual(astroid['g1'].fromlineno, 4) + self.assertEqual(astroid['g1'].tolineno, 5) + self.assertEqual(astroid['g2'].fromlineno, 9) + self.assertEqual(astroid['g2'].tolineno, 10) + + @test_utils.require_version(maxver='3.0') + def test_simple_metaclass(self): + astroid = builder.parse(""" + class Test(object): + __metaclass__ = type + """) + klass = astroid['Test'] + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, scoped_nodes.ClassDef) + self.assertEqual(metaclass.name, 'type') + + def test_metaclass_error(self): + astroid = builder.parse(""" + class Test(object): + __metaclass__ = typ + """) + klass = astroid['Test'] + self.assertFalse(klass.metaclass()) + + @test_utils.require_version(maxver='3.0') + def test_metaclass_imported(self): + astroid = builder.parse(""" + from abc import ABCMeta + class Test(object): + __metaclass__ = ABCMeta + """) + klass = astroid['Test'] + + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, scoped_nodes.ClassDef) + self.assertEqual(metaclass.name, 'ABCMeta') + + def test_metaclass_yes_leak(self): + astroid = builder.parse(""" + # notice `ab` instead of `abc` + from ab import ABCMeta + + class Meta(object): + __metaclass__ = ABCMeta + """) + klass = astroid['Meta'] + self.assertIsNone(klass.metaclass()) + + @test_utils.require_version(maxver='3.0') + def test_newstyle_and_metaclass_good(self): + astroid = builder.parse(""" + from abc import ABCMeta + class Test: + __metaclass__ = ABCMeta + """) + klass = astroid['Test'] + self.assertTrue(klass.newstyle) + self.assertEqual(klass.metaclass().name, 'ABCMeta') + astroid = builder.parse(""" + from abc import ABCMeta + __metaclass__ = ABCMeta + class Test: + pass + """) + klass = astroid['Test'] + self.assertTrue(klass.newstyle) + self.assertEqual(klass.metaclass().name, 'ABCMeta') + + @test_utils.require_version(maxver='3.0') + def test_nested_metaclass(self): + astroid = builder.parse(""" + from abc import ABCMeta + class A(object): + __metaclass__ = ABCMeta + class B: pass + + __metaclass__ = ABCMeta + class C: + __metaclass__ = type + class D: pass + """) + a = astroid['A'] + b = a._locals['B'][0] + c = astroid['C'] + d = c._locals['D'][0] + self.assertEqual(a.metaclass().name, 'ABCMeta') + self.assertFalse(b.newstyle) + self.assertIsNone(b.metaclass()) + self.assertEqual(c.metaclass().name, 'type') + self.assertEqual(d.metaclass().name, 'ABCMeta') + + @test_utils.require_version(maxver='3.0') + def test_parent_metaclass(self): + astroid = builder.parse(""" + from abc import ABCMeta + class Test: + __metaclass__ = ABCMeta + class SubTest(Test): pass + """) + klass = astroid['SubTest'] + self.assertTrue(klass.newstyle) + metaclass = klass.metaclass() + self.assertIsInstance(metaclass, scoped_nodes.ClassDef) + self.assertEqual(metaclass.name, 'ABCMeta') + + @test_utils.require_version(maxver='3.0') + def test_metaclass_ancestors(self): + astroid = builder.parse(""" + from abc import ABCMeta + + class FirstMeta(object): + __metaclass__ = ABCMeta + + class SecondMeta(object): + __metaclass__ = type + + class Simple(object): + pass + + class FirstImpl(FirstMeta): pass + class SecondImpl(FirstImpl): pass + class ThirdImpl(Simple, SecondMeta): + pass + """) + classes = { + 'ABCMeta': ('FirstImpl', 'SecondImpl'), + 'type': ('ThirdImpl', ) + } + for metaclass, names in classes.items(): + for name in names: + impl = astroid[name] + meta = impl.metaclass() + self.assertIsInstance(meta, nodes.ClassDef) + self.assertEqual(meta.name, metaclass) + + def test_metaclass_type(self): + klass = test_utils.extract_node(""" + def with_metaclass(meta, base=object): + return meta("NewBase", (base, ), {}) + + class ClassWithMeta(with_metaclass(type)): #@ + pass + """) + self.assertEqual( + ['NewBase', 'object'], + [base.name for base in klass.ancestors()]) + + def test_no_infinite_metaclass_loop(self): + klass = test_utils.extract_node(""" + class SSS(object): + + class JJJ(object): + pass + + @classmethod + def Init(cls): + cls.JJJ = type('JJJ', (cls.JJJ,), {}) + + class AAA(SSS): + pass + + class BBB(AAA.JJJ): + pass + """) + self.assertFalse(scoped_nodes._is_metaclass(klass)) + ancestors = [base.name for base in klass.ancestors()] + self.assertIn('object', ancestors) + self.assertIn('JJJ', ancestors) + + def test_no_infinite_metaclass_loop_with_redefine(self): + nodes = test_utils.extract_node(""" + import datetime + + class A(datetime.date): #@ + @classmethod + def now(cls): + return cls() + + class B(datetime.date): #@ + pass + + datetime.date = A + datetime.date = B + """) + for klass in nodes: + self.assertEqual(None, klass.metaclass()) + + def test_metaclass_generator_hack(self): + klass = test_utils.extract_node(""" + import six + + class WithMeta(six.with_metaclass(type, object)): #@ + pass + """) + self.assertEqual( + ['object'], + [base.name for base in klass.ancestors()]) + self.assertEqual( + 'type', klass.metaclass().name) + + def test_using_six_add_metaclass(self): + klass = test_utils.extract_node(''' + import six + import abc + + @six.add_metaclass(abc.ABCMeta) + class WithMeta(object): + pass + ''') + inferred = next(klass.infer()) + metaclass = inferred.metaclass() + self.assertIsInstance(metaclass, scoped_nodes.ClassDef) + self.assertEqual(metaclass.qname(), 'abc.ABCMeta') + + def test_using_invalid_six_add_metaclass_call(self): + klass = test_utils.extract_node(''' + import six + @six.add_metaclass() + class Invalid(object): + pass + ''') + inferred = next(klass.infer()) + self.assertIsNone(inferred.metaclass()) + + def test_nonregr_infer_callresult(self): + astroid = builder.parse(""" + class Delegate(object): + def __get__(self, obj, cls): + return getattr(obj._subject, self.attribute) + + class CompositeBuilder(object): + __call__ = Delegate() + + builder = CompositeBuilder(result, composite) + tgts = builder() + """) + instance = astroid['tgts'] + # used to raise "'_Yes' object is not iterable", see + # https://bitbucket.org/logilab/astroid/issue/17 + self.assertEqual(list(instance.infer()), [util.YES]) + + def test_slots(self): + astroid = builder.parse(""" + from collections import deque + from textwrap import dedent + + class First(object): #@ + __slots__ = ("a", "b", 1) + class Second(object): #@ + __slots__ = "a" + class Third(object): #@ + __slots__ = deque(["a", "b", "c"]) + class Fourth(object): #@ + __slots__ = {"a": "a", "b": "b"} + class Fifth(object): #@ + __slots__ = list + class Sixth(object): #@ + __slots__ = "" + class Seventh(object): #@ + __slots__ = dedent.__name__ + class Eight(object): #@ + __slots__ = ("parens") + class Ninth(object): #@ + pass + class Ten(object): #@ + __slots__ = dict({"a": "b", "c": "d"}) + """) + expected = [ + ('First', ('a', 'b')), + ('Second', ('a', )), + ('Third', None), + ('Fourth', ('a', 'b')), + ('Fifth', None), + ('Sixth', None), + ('Seventh', ('dedent', )), + ('Eight', ('parens', )), + ('Ninth', None), + ('Ten', ('a', 'c')), + ] + for cls, expected_value in expected: + slots = astroid[cls].slots() + if expected_value is None: + self.assertIsNone(slots) + else: + self.assertEqual(list(expected_value), + [node.value for node in slots]) + + @test_utils.require_version(maxver='3.0') + def test_slots_py2(self): + module = builder.parse(""" + class UnicodeSlots(object): + __slots__ = (u"a", u"b", "c") + """) + slots = module['UnicodeSlots'].slots() + self.assertEqual(len(slots), 3) + self.assertEqual(slots[0].value, "a") + self.assertEqual(slots[1].value, "b") + self.assertEqual(slots[2].value, "c") + + @test_utils.require_version(maxver='3.0') + def test_slots_py2_not_implemented(self): + module = builder.parse(""" + class OldStyle: + __slots__ = ("a", "b") + """) + msg = "The concept of slots is undefined for old-style classes." + with self.assertRaises(NotImplementedError) as cm: + module['OldStyle'].slots() + self.assertEqual(str(cm.exception), msg) + + def test_slots_empty_list_of_slots(self): + module = builder.parse(""" + class Klass(object): + __slots__ = () + """) + cls = module['Klass'] + self.assertEqual(cls.slots(), []) + + def test_slots_taken_from_parents(self): + module = builder.parse(''' + class FirstParent(object): + __slots__ = ('a', 'b', 'c') + class SecondParent(FirstParent): + __slots__ = ('d', 'e') + class Third(SecondParent): + __slots__ = ('d', ) + ''') + cls = module['Third'] + slots = cls.slots() + self.assertEqual(sorted(set(slot.value for slot in slots)), + ['a', 'b', 'c', 'd', 'e']) + + def test_all_ancestors_need_slots(self): + module = builder.parse(''' + class A(object): + __slots__ = ('a', ) + class B(A): pass + class C(B): + __slots__ = ('a', ) + ''') + cls = module['C'] + self.assertIsNone(cls.slots()) + cls = module['B'] + self.assertIsNone(cls.slots()) + + def assertEqualMro(self, klass, expected_mro): + self.assertEqual( + [member.name for member in klass.mro()], + expected_mro) + + @test_utils.require_version(maxver='3.0') + def test_no_mro_for_old_style(self): + node = test_utils.extract_node(""" + class Old: pass""") + with self.assertRaises(NotImplementedError) as cm: + node.mro() + self.assertEqual(str(cm.exception), "Could not obtain mro for " + "old-style classes.") + + @test_utils.require_version(maxver='3.0') + def test_combined_newstyle_oldstyle_in_mro(self): + node = test_utils.extract_node(''' + class Old: + pass + class New(object): + pass + class New1(object): + pass + class New2(New, New1): + pass + class NewOld(New2, Old): #@ + pass + ''') + self.assertEqualMro(node, ['NewOld', 'New2', 'New', 'New1', 'object', 'Old']) + self.assertTrue(node.newstyle) + + def test_with_metaclass_mro(self): + astroid = builder.parse(""" + import six + + class C(object): + pass + class B(C): + pass + class A(six.with_metaclass(type, B)): + pass + """) + self.assertEqualMro(astroid['A'], ['A', 'B', 'C', 'object']) + + def test_mro(self): + astroid = builder.parse(""" + class C(object): pass + class D(dict, C): pass + + class A1(object): pass + class B1(A1): pass + class C1(A1): pass + class D1(B1, C1): pass + class E1(C1, B1): pass + class F1(D1, E1): pass + class G1(E1, D1): pass + + class Boat(object): pass + class DayBoat(Boat): pass + class WheelBoat(Boat): pass + class EngineLess(DayBoat): pass + class SmallMultihull(DayBoat): pass + class PedalWheelBoat(EngineLess, WheelBoat): pass + class SmallCatamaran(SmallMultihull): pass + class Pedalo(PedalWheelBoat, SmallCatamaran): pass + + class OuterA(object): + class Inner(object): + pass + class OuterB(OuterA): + class Inner(OuterA.Inner): + pass + class OuterC(OuterA): + class Inner(OuterA.Inner): + pass + class OuterD(OuterC): + class Inner(OuterC.Inner, OuterB.Inner): + pass + class Duplicates(str, str): pass + + """) + self.assertEqualMro(astroid['D'], ['D', 'dict', 'C', 'object']) + self.assertEqualMro(astroid['D1'], ['D1', 'B1', 'C1', 'A1', 'object']) + self.assertEqualMro(astroid['E1'], ['E1', 'C1', 'B1', 'A1', 'object']) + with self.assertRaises(InconsistentMroError) as cm: + astroid['F1'].mro() + self.assertEqual(str(cm.exception), + "Cannot create a consistent method resolution order " + "for bases (B1, C1, A1, object), " + "(C1, B1, A1, object)") + + with self.assertRaises(InconsistentMroError) as cm: + astroid['G1'].mro() + self.assertEqual(str(cm.exception), + "Cannot create a consistent method resolution order " + "for bases (C1, B1, A1, object), " + "(B1, C1, A1, object)") + + self.assertEqualMro( + astroid['PedalWheelBoat'], + ["PedalWheelBoat", "EngineLess", + "DayBoat", "WheelBoat", "Boat", "object"]) + + self.assertEqualMro( + astroid["SmallCatamaran"], + ["SmallCatamaran", "SmallMultihull", "DayBoat", "Boat", "object"]) + + self.assertEqualMro( + astroid["Pedalo"], + ["Pedalo", "PedalWheelBoat", "EngineLess", "SmallCatamaran", + "SmallMultihull", "DayBoat", "WheelBoat", "Boat", "object"]) + + self.assertEqualMro( + astroid['OuterD']['Inner'], + ['Inner', 'Inner', 'Inner', 'Inner', 'object']) + + with self.assertRaises(DuplicateBasesError) as cm: + astroid['Duplicates'].mro() + self.assertEqual(str(cm.exception), "Duplicates found in the mro.") + self.assertTrue(issubclass(cm.exception.__class__, MroError)) + self.assertTrue(issubclass(cm.exception.__class__, ResolveError)) + + def test_generator_from_infer_call_result_parent(self): + func = test_utils.extract_node(""" + import contextlib + + @contextlib.contextmanager + def test(): #@ + yield + """) + result = next(func.infer_call_result(func)) + self.assertIsInstance(result, Generator) + self.assertEqual(result.parent, func) + + def test_type_three_arguments(self): + classes = test_utils.extract_node(""" + type('A', (object, ), {"a": 1, "b": 2, missing: 3}) #@ + """) + first = next(classes.infer()) + self.assertIsInstance(first, nodes.ClassDef) + self.assertEqual(first.name, "A") + self.assertEqual(first.basenames, ["object"]) + self.assertIsInstance(first["a"], nodes.Const) + self.assertEqual(first["a"].value, 1) + self.assertIsInstance(first["b"], nodes.Const) + self.assertEqual(first["b"].value, 2) + with self.assertRaises(NotFoundError): + first.getattr("missing") + + def test_implicit_metaclass(self): + cls = test_utils.extract_node(""" + class A(object): + pass + """) + type_cls = scoped_nodes.builtin_lookup("type")[1][0] + self.assertEqual(cls.implicit_metaclass(), type_cls) + + @test_utils.require_version(maxver='3.0') + def test_implicit_metaclass_is_none(self): + cls = test_utils.extract_node(""" + class A: pass + """) + self.assertIsNone(cls.implicit_metaclass()) + + def test_local_attr_invalid_mro(self): + cls = test_utils.extract_node(""" + # A has an invalid MRO, local_attr should fallback + # to using .ancestors. + class A(object, object): + test = 42 + class B(A): #@ + pass + """) + local = cls.local_attr('test')[0] + inferred = next(local.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, 42) + + def test_has_dynamic_getattr(self): + module = builder.parse(""" + class Getattr(object): + def __getattr__(self, attrname): + pass + + class Getattribute(object): + def __getattribute__(self, attrname): + pass + + class ParentGetattr(Getattr): + pass + """) + self.assertTrue(module['Getattr'].has_dynamic_getattr()) + self.assertTrue(module['Getattribute'].has_dynamic_getattr()) + self.assertTrue(module['ParentGetattr'].has_dynamic_getattr()) + + # Test that objects analyzed through the live introspection + # aren't considered to have dynamic getattr implemented. + import datetime + astroid_builder = builder.AstroidBuilder() + module = astroid_builder.module_build(datetime) + self.assertFalse(module['timedelta'].has_dynamic_getattr()) + + def test_duplicate_bases_namedtuple(self): + module = builder.parse(""" + import collections + _A = collections.namedtuple('A', 'a') + + class A(_A): pass + + class B(A): pass + """) + self.assertRaises(DuplicateBasesError, module['B'].mro) + + def test_instance_bound_method_lambdas(self): + ast_nodes = test_utils.extract_node(''' + class Test(object): #@ + lam = lambda self: self + not_method = lambda xargs: xargs + Test() #@ + ''') + cls = next(ast_nodes[0].infer()) + self.assertIsInstance(next(cls.igetattr('lam')), scoped_nodes.Lambda) + self.assertIsInstance(next(cls.igetattr('not_method')), scoped_nodes.Lambda) + + instance = next(ast_nodes[1].infer()) + lam = next(instance.igetattr('lam')) + self.assertIsInstance(lam, BoundMethod) + not_method = next(instance.igetattr('not_method')) + self.assertIsInstance(not_method, scoped_nodes.Lambda) + + def test_class_extra_decorators_frame_is_not_class(self): + ast_node = test_utils.extract_node(''' + def ala(): + def bala(): #@ + func = 42 + ''') + self.assertEqual(ast_node.extra_decorators, []) + + def test_class_extra_decorators_only_callfunc_are_considered(self): + ast_node = test_utils.extract_node(''' + class Ala(object): + def func(self): #@ + pass + func = 42 + ''') + self.assertEqual(ast_node.extra_decorators, []) + + def test_class_extra_decorators_only_assignment_names_are_considered(self): + ast_node = test_utils.extract_node(''' + class Ala(object): + def func(self): #@ + pass + def __init__(self): + self.func = staticmethod(func) + + ''') + self.assertEqual(ast_node.extra_decorators, []) + + def test_class_extra_decorators_only_same_name_considered(self): + ast_node = test_utils.extract_node(''' + class Ala(object): + def func(self): #@ + pass + bala = staticmethod(func) + ''') + self.assertEqual(ast_node.extra_decorators, []) + self.assertEqual(ast_node.type, 'method') + + def test_class_extra_decorators(self): + static_method, clsmethod = test_utils.extract_node(''' + class Ala(object): + def static(self): #@ + pass + def class_method(self): #@ + pass + class_method = classmethod(class_method) + static = staticmethod(static) + ''') + self.assertEqual(len(clsmethod.extra_decorators), 1) + self.assertEqual(clsmethod.type, 'classmethod') + self.assertEqual(len(static_method.extra_decorators), 1) + self.assertEqual(static_method.type, 'staticmethod') + + def test_extra_decorators_only_class_level_assignments(self): + node = test_utils.extract_node(''' + def _bind(arg): + return arg.bind + + class A(object): + @property + def bind(self): + return 42 + def irelevant(self): + # This is important, because it used to trigger + # a maximum recursion error. + bind = _bind(self) + return bind + A() #@ + ''') + inferred = next(node.infer()) + bind = next(inferred.igetattr('bind')) + self.assertIsInstance(bind, nodes.Const) + self.assertEqual(bind.value, 42) + parent = bind.scope() + self.assertEqual(len(parent.extra_decorators), 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_transforms.py b/pymode/libs/astroid/tests/unittest_transforms.py new file mode 100644 index 00000000..29a8b8db --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_transforms.py @@ -0,0 +1,245 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . + +from __future__ import print_function + +import contextlib +import time +import unittest + +from astroid import builder +from astroid import nodes +from astroid import parse +from astroid import transforms + + +@contextlib.contextmanager +def add_transform(manager, node, transform, predicate=None): + manager.register_transform(node, transform, predicate) + try: + yield + finally: + manager.unregister_transform(node, transform, predicate) + + +class TestTransforms(unittest.TestCase): + + def setUp(self): + self.transformer = transforms.TransformVisitor() + + def parse_transform(self, code): + module = parse(code, apply_transforms=False) + return self.transformer.visit(module) + + def test_function_inlining_transform(self): + def transform_call(node): + # Let's do some function inlining + inferred = next(node.infer()) + return inferred + + self.transformer.register_transform(nodes.Call, + transform_call) + + module = self.parse_transform(''' + def test(): return 42 + test() #@ + ''') + + self.assertIsInstance(module.body[1], nodes.Expr) + self.assertIsInstance(module.body[1].value, nodes.Const) + self.assertEqual(module.body[1].value.value, 42) + + def test_recursive_transforms_into_astroid_fields(self): + # Test that the transformer walks properly the tree + # by going recursively into the _astroid_fields per each node. + def transform_compare(node): + # Let's check the values of the ops + _, right = node.ops[0] + # Assume they are Consts and they were transformed before + # us. + return nodes.const_factory(node.left.value < right.value) + + def transform_name(node): + # Should be Consts + return next(node.infer()) + + self.transformer.register_transform(nodes.Compare, transform_compare) + self.transformer.register_transform(nodes.Name, transform_name) + + module = self.parse_transform(''' + a = 42 + b = 24 + a < b + ''') + + self.assertIsInstance(module.body[2], nodes.Expr) + self.assertIsInstance(module.body[2].value, nodes.Const) + self.assertFalse(module.body[2].value.value) + + def test_transform_patches_locals(self): + def transform_function(node): + assign = nodes.Assign() + name = nodes.AssignName() + name.name = 'value' + assign.targets = [name] + assign.value = nodes.const_factory(42) + node.body.append(assign) + + self.transformer.register_transform(nodes.FunctionDef, + transform_function) + + module = self.parse_transform(''' + def test(): + pass + ''') + + func = module.body[0] + self.assertEqual(len(func.body), 2) + self.assertIsInstance(func.body[1], nodes.Assign) + self.assertEqual(func.body[1].as_string(), 'value = 42') + + def test_predicates(self): + def transform_call(node): + inferred = next(node.infer()) + return inferred + + def should_inline(node): + return node.func.name.startswith('inlineme') + + self.transformer.register_transform(nodes.Call, + transform_call, + should_inline) + + module = self.parse_transform(''' + def inlineme_1(): + return 24 + def dont_inline_me(): + return 42 + def inlineme_2(): + return 2 + inlineme_1() + dont_inline_me() + inlineme_2() + ''') + values = module.body[-3:] + self.assertIsInstance(values[0], nodes.Expr) + self.assertIsInstance(values[0].value, nodes.Const) + self.assertEqual(values[0].value.value, 24) + self.assertIsInstance(values[1], nodes.Expr) + self.assertIsInstance(values[1].value, nodes.Call) + self.assertIsInstance(values[2], nodes.Expr) + self.assertIsInstance(values[2].value, nodes.Const) + self.assertEqual(values[2].value.value, 2) + + def test_transforms_are_separated(self): + # Test that the transforming is done at a separate + # step, which means that we are not doing inference + # on a partially constructred tree anymore, which was the + # source of crashes in the past when certain inference rules + # were used in a transform. + def transform_function(node): + if node.decorators: + for decorator in node.decorators.nodes: + inferred = next(decorator.infer()) + if inferred.qname() == 'abc.abstractmethod': + return next(node.infer_call_result(node)) + + manager = builder.MANAGER + with add_transform(manager, nodes.FunctionDef, transform_function): + module = builder.parse(''' + import abc + from abc import abstractmethod + + class A(object): + @abc.abstractmethod + def ala(self): + return 24 + + @abstractmethod + def bala(self): + return 42 + ''') + + cls = module['A'] + ala = cls.body[0] + bala = cls.body[1] + self.assertIsInstance(ala, nodes.Const) + self.assertEqual(ala.value, 24) + self.assertIsInstance(bala, nodes.Const) + self.assertEqual(bala.value, 42) + + def test_transforms_are_called_for_builtin_modules(self): + # Test that transforms are called for builtin modules. + def transform_function(node): + name = nodes.AssignName() + name.name = 'value' + node.args.args = [name] + return node + + manager = builder.MANAGER + predicate = lambda node: node.root().name == 'time' + with add_transform(manager, nodes.FunctionDef, + transform_function, predicate): + builder_instance = builder.AstroidBuilder() + module = builder_instance.module_build(time) + + asctime = module['asctime'] + self.assertEqual(len(asctime.args.args), 1) + self.assertIsInstance(asctime.args.args[0], nodes.AssignName) + self.assertEqual(asctime.args.args[0].name, 'value') + + def test_builder_apply_transforms(self): + def transform_function(node): + return nodes.const_factory(42) + + manager = builder.MANAGER + with add_transform(manager, nodes.FunctionDef, transform_function): + astroid_builder = builder.AstroidBuilder(apply_transforms=False) + module = astroid_builder.string_build('''def test(): pass''') + + # The transform wasn't applied. + self.assertIsInstance(module.body[0], nodes.FunctionDef) + + def test_transform_crashes_on_is_subtype_of(self): + # Test that we don't crash when having is_subtype_of + # in a transform, as per issue #188. This happened + # before, when the transforms weren't in their own step. + def transform_class(cls): + if cls.is_subtype_of('django.db.models.base.Model'): + return cls + return cls + + self.transformer.register_transform(nodes.ClassDef, + transform_class) + + self.parse_transform(''' + # Change environ to automatically call putenv() if it exists + import os + putenv = os.putenv + try: + # This will fail if there's no putenv + putenv + except NameError: + pass + else: + import UserDict + ''') + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/tests/unittest_utils.py b/pymode/libs/astroid/tests/unittest_utils.py new file mode 100644 index 00000000..ef832252 --- /dev/null +++ b/pymode/libs/astroid/tests/unittest_utils.py @@ -0,0 +1,124 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +import unittest + +from astroid import builder +from astroid import InferenceError +from astroid import nodes +from astroid import node_classes +from astroid import test_utils +from astroid import util as astroid_util + + +class InferenceUtil(unittest.TestCase): + + def test_not_exclusive(self): + module = builder.parse(""" + x = 10 + for x in range(5): + print (x) + + if x > 0: + print ('#' * x) + """, __name__, __file__) + xass1 = module.locals['x'][0] + assert xass1.lineno == 2 + xnames = [n for n in module.nodes_of_class(nodes.Name) if n.name == 'x'] + assert len(xnames) == 3 + assert xnames[1].lineno == 6 + self.assertEqual(node_classes.are_exclusive(xass1, xnames[1]), False) + self.assertEqual(node_classes.are_exclusive(xass1, xnames[2]), False) + + def test_if(self): + module = builder.parse(''' + if 1: + a = 1 + a = 2 + elif 2: + a = 12 + a = 13 + else: + a = 3 + a = 4 + ''') + a1 = module.locals['a'][0] + a2 = module.locals['a'][1] + a3 = module.locals['a'][2] + a4 = module.locals['a'][3] + a5 = module.locals['a'][4] + a6 = module.locals['a'][5] + self.assertEqual(node_classes.are_exclusive(a1, a2), False) + self.assertEqual(node_classes.are_exclusive(a1, a3), True) + self.assertEqual(node_classes.are_exclusive(a1, a5), True) + self.assertEqual(node_classes.are_exclusive(a3, a5), True) + self.assertEqual(node_classes.are_exclusive(a3, a4), False) + self.assertEqual(node_classes.are_exclusive(a5, a6), False) + + def test_try_except(self): + module = builder.parse(''' + try: + def exclusive_func2(): + "docstring" + except TypeError: + def exclusive_func2(): + "docstring" + except: + def exclusive_func2(): + "docstring" + else: + def exclusive_func2(): + "this one redefine the one defined line 42" + ''') + f1 = module.locals['exclusive_func2'][0] + f2 = module.locals['exclusive_func2'][1] + f3 = module.locals['exclusive_func2'][2] + f4 = module.locals['exclusive_func2'][3] + self.assertEqual(node_classes.are_exclusive(f1, f2), True) + self.assertEqual(node_classes.are_exclusive(f1, f3), True) + self.assertEqual(node_classes.are_exclusive(f1, f4), False) + self.assertEqual(node_classes.are_exclusive(f2, f4), True) + self.assertEqual(node_classes.are_exclusive(f3, f4), True) + self.assertEqual(node_classes.are_exclusive(f3, f2), True) + + self.assertEqual(node_classes.are_exclusive(f2, f1), True) + self.assertEqual(node_classes.are_exclusive(f4, f1), False) + self.assertEqual(node_classes.are_exclusive(f4, f2), True) + + def test_unpack_infer_uninferable_nodes(self): + node = test_utils.extract_node(''' + x = [A] * 1 + f = [x, [A] * 2] + f + ''') + inferred = next(node.infer()) + unpacked = list(node_classes.unpack_infer(inferred)) + self.assertEqual(len(unpacked), 3) + self.assertTrue(all(elt is astroid_util.YES + for elt in unpacked)) + + def test_unpack_infer_empty_tuple(self): + node = test_utils.extract_node(''' + () + ''') + inferred = next(node.infer()) + with self.assertRaises(InferenceError): + list(node_classes.unpack_infer(inferred)) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/astroid/transforms.py b/pymode/libs/astroid/transforms.py new file mode 100644 index 00000000..5d8fc91b --- /dev/null +++ b/pymode/libs/astroid/transforms.py @@ -0,0 +1,96 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . + +import collections +import warnings + + +class TransformVisitor(object): + """A visitor for handling transforms. + + The standard approach of using it is to call + :meth:`~visit` with an *astroid* module and the class + will take care of the rest, walking the tree and running the + transforms for each encountered node. + """ + + def __init__(self): + self.transforms = collections.defaultdict(list) + + def _transform(self, node): + """Call matching transforms for the given node if any and return the + transformed node. + """ + cls = node.__class__ + if cls not in self.transforms: + # no transform registered for this class of node + return node + + transforms = self.transforms[cls] + orig_node = node # copy the reference + for transform_func, predicate in transforms: + if predicate is None or predicate(node): + ret = transform_func(node) + # if the transformation function returns something, it's + # expected to be a replacement for the node + if ret is not None: + if node is not orig_node: + # node has already be modified by some previous + # transformation, warn about it + warnings.warn('node %s substituted multiple times' % node) + node = ret + return node + + def _visit(self, node): + if hasattr(node, '_astroid_fields'): + for field in node._astroid_fields: + value = getattr(node, field) + visited = self._visit_generic(value) + setattr(node, field, visited) + return self._transform(node) + + def _visit_generic(self, node): + if isinstance(node, list): + return [self._visit_generic(child) for child in node] + elif isinstance(node, tuple): + return tuple(self._visit_generic(child) for child in node) + else: + return self._visit(node) + + def register_transform(self, node_class, transform, predicate=None): + """Register `transform(node)` function to be applied on the given + astroid's `node_class` if `predicate` is None or returns true + when called with the node as argument. + + The transform function may return a value which is then used to + substitute the original node in the tree. + """ + self.transforms[node_class].append((transform, predicate)) + + def unregister_transform(self, node_class, transform, predicate=None): + """Unregister the given transform.""" + self.transforms[node_class].remove((transform, predicate)) + + def visit(self, module): + """Walk the given astroid *tree* and transform each encountered node + + Only the nodes which have transforms registered will actually + be replaced or changed. + """ + module.body = [self._visit(child) for child in module.body] + return self._transform(module) diff --git a/pymode/libs/astroid/util.py b/pymode/libs/astroid/util.py new file mode 100644 index 00000000..44e2039d --- /dev/null +++ b/pymode/libs/astroid/util.py @@ -0,0 +1,89 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +# +# The code in this file was originally part of logilab-common, licensed under +# the same license. +import warnings + +from astroid import exceptions + + +def generate_warning(message, warning): + return lambda *args: warnings.warn(message % args, warning, stacklevel=3) + +rename_warning = generate_warning( + "%r is deprecated and will be removed in astroid %.1f, use %r instead", + PendingDeprecationWarning) + +attribute_to_method_warning = generate_warning( + "%s is deprecated and will be removed in astroid %.1f, use the " + "method '%s()' instead.", PendingDeprecationWarning) + +attribute_to_function_warning = generate_warning( + "%s is deprecated and will be removed in astroid %.1f, use the " + "function '%s()' instead.", PendingDeprecationWarning) + +method_to_function_warning = generate_warning( + "%s() is deprecated and will be removed in astroid %.1f, use the " + "function '%s()' instead.", PendingDeprecationWarning) + + +class _Yes(object): + """Special inference object, which is returned when inference fails.""" + def __repr__(self): + return 'YES' + + __str__ = __repr__ + + def __getattribute__(self, name): + if name == 'next': + raise AttributeError('next method should not be called') + if name.startswith('__') and name.endswith('__'): + return super(_Yes, self).__getattribute__(name) + if name == 'accept': + return super(_Yes, self).__getattribute__(name) + return self + + def __call__(self, *args, **kwargs): + return self + + def accept(self, visitor): + func = getattr(visitor, "visit_yes") + return func(self) + + +YES = _Yes() + +def safe_infer(node, context=None): + """Return the inferred value for the given node. + + Return None if inference failed or if there is some ambiguity (more than + one node has been inferred). + """ + try: + inferit = node.infer(context=context) + value = next(inferit) + except exceptions.InferenceError: + return + try: + next(inferit) + return # None if there is ambiguity on the inferred node + except exceptions.InferenceError: + return # there is some kind of ambiguity + except StopIteration: + return value diff --git a/pymode/libs/astroid/utils.py b/pymode/libs/astroid/utils.py deleted file mode 100644 index ae72a92c..00000000 --- a/pymode/libs/astroid/utils.py +++ /dev/null @@ -1,239 +0,0 @@ -# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of astroid. -# -# astroid is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by the -# Free Software Foundation, either version 2.1 of the License, or (at your -# option) any later version. -# -# astroid 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 Lesser General Public License -# for more details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with astroid. If not, see . -"""this module contains some utilities to navigate in the tree or to -extract information from it -""" -from __future__ import print_function - -__docformat__ = "restructuredtext en" - -from astroid.exceptions import AstroidBuildingException -from astroid.builder import parse - - -class ASTWalker(object): - """a walker visiting a tree in preorder, calling on the handler: - - * visit_ on entering a node, where class name is the class of - the node in lower case - - * leave_ on leaving a node, where class name is the class of - the node in lower case - """ - - def __init__(self, handler): - self.handler = handler - self._cache = {} - - def walk(self, node, _done=None): - """walk on the tree from , getting callbacks from handler""" - if _done is None: - _done = set() - if node in _done: - raise AssertionError((id(node), node, node.parent)) - _done.add(node) - self.visit(node) - for child_node in node.get_children(): - self.handler.set_context(node, child_node) - assert child_node is not node - self.walk(child_node, _done) - self.leave(node) - assert node.parent is not node - - def get_callbacks(self, node): - """get callbacks from handler for the visited node""" - klass = node.__class__ - methods = self._cache.get(klass) - if methods is None: - handler = self.handler - kid = klass.__name__.lower() - e_method = getattr(handler, 'visit_%s' % kid, - getattr(handler, 'visit_default', None)) - l_method = getattr(handler, 'leave_%s' % kid, - getattr(handler, 'leave_default', None)) - self._cache[klass] = (e_method, l_method) - else: - e_method, l_method = methods - return e_method, l_method - - def visit(self, node): - """walk on the tree from , getting callbacks from handler""" - method = self.get_callbacks(node)[0] - if method is not None: - method(node) - - def leave(self, node): - """walk on the tree from , getting callbacks from handler""" - method = self.get_callbacks(node)[1] - if method is not None: - method(node) - - -class LocalsVisitor(ASTWalker): - """visit a project by traversing the locals dictionary""" - def __init__(self): - ASTWalker.__init__(self, self) - self._visited = {} - - def visit(self, node): - """launch the visit starting from the given node""" - if node in self._visited: - return - self._visited[node] = 1 # FIXME: use set ? - methods = self.get_callbacks(node) - if methods[0] is not None: - methods[0](node) - if 'locals' in node.__dict__: # skip Instance and other proxy - for local_node in node.values(): - self.visit(local_node) - if methods[1] is not None: - return methods[1](node) - - -def _check_children(node): - """a helper function to check children - parent relations""" - for child in node.get_children(): - ok = False - if child is None: - print("Hm, child of %s is None" % node) - continue - if not hasattr(child, 'parent'): - print(" ERROR: %s has child %s %x with no parent" % ( - node, child, id(child))) - elif not child.parent: - print(" ERROR: %s has child %s %x with parent %r" % ( - node, child, id(child), child.parent)) - elif child.parent is not node: - print(" ERROR: %s %x has child %s %x with wrong parent %s" % ( - node, id(node), child, id(child), child.parent)) - else: - ok = True - if not ok: - print("lines;", node.lineno, child.lineno) - print("of module", node.root(), node.root().name) - raise AstroidBuildingException - _check_children(child) - - -class TreeTester(object): - '''A helper class to see _ast tree and compare with astroid tree - - indent: string for tree indent representation - lineno: bool to tell if we should print the line numbers - - >>> tester = TreeTester('print') - >>> print tester.native_tree_repr() - - - . body = [ - . - . . nl = True - . ] - >>> print tester.astroid_tree_repr() - Module() - body = [ - Print() - dest = - values = [ - ] - ] - ''' - - indent = '. ' - lineno = False - - def __init__(self, sourcecode): - self._string = '' - self.sourcecode = sourcecode - self._ast_node = None - self.build_ast() - - def build_ast(self): - """build the _ast tree from the source code""" - self._ast_node = parse(self.sourcecode) - - def native_tree_repr(self, node=None, indent=''): - """get a nice representation of the _ast tree""" - self._string = '' - if node is None: - node = self._ast_node - self._native_repr_tree(node, indent) - return self._string - - - def _native_repr_tree(self, node, indent, _done=None): - """recursive method for the native tree representation""" - from _ast import Load as _Load, Store as _Store, Del as _Del - from _ast import AST as Node - if _done is None: - _done = set() - if node in _done: - self._string += '\nloop in tree: %r (%s)' % ( - node, getattr(node, 'lineno', None)) - return - _done.add(node) - self._string += '\n' + indent + '<%s>' % node.__class__.__name__ - indent += self.indent - if not hasattr(node, '__dict__'): - self._string += '\n' + self.indent + " ** node has no __dict__ " + str(node) - return - node_dict = node.__dict__ - if hasattr(node, '_attributes'): - for a in node._attributes: - attr = node_dict[a] - if attr is None: - continue - if a in ("lineno", "col_offset") and not self.lineno: - continue - self._string += '\n' + indent + a + " = " + repr(attr) - for field in node._fields or (): - attr = node_dict[field] - if attr is None: - continue - if isinstance(attr, list): - if not attr: - continue - self._string += '\n' + indent + field + ' = [' - for elt in attr: - self._native_repr_tree(elt, indent, _done) - self._string += '\n' + indent + ']' - continue - if isinstance(attr, (_Load, _Store, _Del)): - continue - if isinstance(attr, Node): - self._string += '\n' + indent + field + " = " - self._native_repr_tree(attr, indent, _done) - else: - self._string += '\n' + indent + field + " = " + repr(attr) - - - def build_astroid_tree(self): - """build astroid tree from the _ast tree - """ - from astroid.builder import AstroidBuilder - tree = AstroidBuilder().string_build(self.sourcecode) - return tree - - def astroid_tree_repr(self, ids=False): - """build the astroid tree and return a nice tree representation""" - mod = self.build_astroid_tree() - return mod.repr_tree(ids) - - -__all__ = ('LocalsVisitor', 'ASTWalker',) - diff --git a/pymode/libs/pylint/__pkginfo__.py b/pymode/libs/pylint/__pkginfo__.py index 33ae5b64..f5eea15e 100644 --- a/pymode/libs/pylint/__pkginfo__.py +++ b/pymode/libs/pylint/__pkginfo__.py @@ -17,12 +17,23 @@ """pylint packaging information""" from __future__ import absolute_import +import sys +from os.path import join + + modname = distname = 'pylint' -numversion = (1, 4, 4) +numversion = (1, 5, 5) version = '.'.join([str(num) for num in numversion]) -install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.3.6', 'six'] +install_requires = [ + 'astroid>=1.4.5,<1.5.0', + 'six', +] + +if sys.platform == 'win32': + install_requires.append('colorama') + license = 'GPL' description = "python code static checker" @@ -62,7 +73,6 @@ Pylint is shipped with "pylint-gui", "pyreverse" (UML diagram generator) and "symilar" (an independent similarities checker).""" -from os.path import join scripts = [join('bin', filename) for filename in ('pylint', 'pylint-gui', "symilar", "epylint", "pyreverse")] diff --git a/pymode/libs/pylint/checkers/__init__.py b/pymode/libs/pylint/checkers/__init__.py index 51adb4d0..daa942aa 100644 --- a/pymode/libs/pylint/checkers/__init__.py +++ b/pymode/libs/pylint/checkers/__init__.py @@ -44,8 +44,7 @@ import tokenize import warnings -from logilab.common.configuration import OptionsProviderMixIn - +from pylint.config import OptionsProviderMixIn from pylint.reporters import diff_string from pylint.utils import register_plugins from pylint.interfaces import UNDEFINED diff --git a/pymode/libs/pylint/checkers/async.py b/pymode/libs/pylint/checkers/async.py new file mode 100644 index 00000000..16faac9e --- /dev/null +++ b/pymode/libs/pylint/checkers/async.py @@ -0,0 +1,82 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Checker for anything related to the async protocol (PEP 492).""" + +import astroid +from astroid import exceptions + +from pylint import checkers +from pylint.checkers import utils as checker_utils +from pylint import interfaces +from pylint import utils + + +class AsyncChecker(checkers.BaseChecker): + __implements__ = interfaces.IAstroidChecker + name = 'async' + msgs = { + 'E1700': ('Yield inside async function', + 'yield-inside-async-function', + 'Used when an `yield` or `yield from` statement is ' + 'found inside an async function.', + {'minversion': (3, 5)}), + 'E1701': ("Async context manager '%s' doesn't implement __aenter__ and __aexit__.", + 'not-async-context-manager', + 'Used when an async context manager is used with an object ' + 'that does not implement the async context management protocol.', + {'minversion': (3, 5)}), + } + + def open(self): + self._ignore_mixin_members = utils.get_global_option(self, 'ignore-mixin-members') + + @checker_utils.check_messages('yield-inside-async-function') + def visit_asyncfunctiondef(self, node): + for child in node.nodes_of_class(astroid.Yield): + if child.scope() is node: + self.add_message('yield-inside-async-function', node=child) + + @checker_utils.check_messages('not-async-context-manager') + def visit_asyncwith(self, node): + for ctx_mgr, _ in node.items: + infered = checker_utils.safe_infer(ctx_mgr) + if infered is None or infered is astroid.YES: + continue + + if isinstance(infered, astroid.Instance): + try: + infered.getattr('__aenter__') + infered.getattr('__aexit__') + except exceptions.NotFoundError: + if isinstance(infered, astroid.Instance): + # If we do not know the bases of this class, + # just skip it. + if not checker_utils.has_known_bases(infered): + continue + # Just ignore mixin classes. + if self._ignore_mixin_members: + if infered.name[-5:].lower() == 'mixin': + continue + else: + continue + + self.add_message('not-async-context-manager', + node=node, args=(infered.name, )) + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(AsyncChecker(linter)) diff --git a/pymode/libs/pylint/checkers/base.py b/pymode/libs/pylint/checkers/base.py index 6ce88251..9133b347 100644 --- a/pymode/libs/pylint/checkers/base.py +++ b/pymode/libs/pylint/checkers/base.py @@ -24,29 +24,31 @@ import six from six.moves import zip # pylint: disable=redefined-builtin -from logilab.common.ureports import Table - import astroid import astroid.bases +import astroid.scoped_nodes from astroid import are_exclusive, InferenceError -from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH -from pylint.utils import EmptyReport +from pylint.interfaces import (IAstroidChecker, ITokenChecker, INFERENCE, + INFERENCE_FAILURE, HIGH) +from pylint.utils import EmptyReport, deprecated_option from pylint.reporters import diff_string -from pylint.checkers import BaseChecker +from pylint.checkers import BaseChecker, BaseTokenChecker from pylint.checkers.utils import ( check_messages, clobber_in_except, is_builtin_object, is_inside_except, overrides_a_method, - safe_infer, get_argument_from_call, - has_known_bases, + node_frame_class, NoSuchArgumentError, - is_import_error, + error_of_type, unimplemented_abstract_methods, + has_known_bases, + safe_infer ) +from pylint.reporters.ureports.nodes import Table # regex for class/function/variable/constant name @@ -56,26 +58,41 @@ COMP_VAR_RGX = re.compile('[A-Za-z_][A-Za-z0-9_]*$') DEFAULT_NAME_RGX = re.compile('[a-z_][a-z0-9_]{2,30}$') CLASS_ATTRIBUTE_RGX = re.compile(r'([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$') -# do not require a doc string on system methods -NO_REQUIRED_DOC_RGX = re.compile('__.*__') -REVERSED_METHODS = (('__getitem__', '__len__'), - ('__reversed__', )) - +# do not require a doc string on private/system methods +NO_REQUIRED_DOC_RGX = re.compile('^_') +REVERSED_PROTOCOL_METHOD = '__reversed__' +SEQUENCE_PROTOCOL_METHODS = ('__getitem__', '__len__') +REVERSED_METHODS = (SEQUENCE_PROTOCOL_METHODS, + (REVERSED_PROTOCOL_METHOD, )) +TYPECHECK_COMPARISON_OPERATORS = frozenset(('is', 'is not', '==', + '!=', 'in', 'not in')) +LITERAL_NODE_TYPES = (astroid.Const, astroid.Dict, astroid.List, astroid.Set) +UNITTEST_CASE = 'unittest.case' +BUILTINS = six.moves.builtins.__name__ +TYPE_QNAME = "%s.type" % BUILTINS PY33 = sys.version_info >= (3, 3) PY3K = sys.version_info >= (3, 0) +PY35 = sys.version_info >= (3, 5) BAD_FUNCTIONS = ['map', 'filter'] if sys.version_info < (3, 0): BAD_FUNCTIONS.append('input') +# Some hints regarding the use of bad builtins. +BUILTIN_HINTS = { + 'map': 'Using a list comprehension can be clearer.', +} +BUILTIN_HINTS['filter'] = BUILTIN_HINTS['map'] + # Name categories that are always consistent with all naming conventions. EXEMPT_NAME_CATEGORIES = set(('exempt', 'ignore')) # A mapping from builtin-qname -> symbol, to be used when generating messages # about dangerous default values as arguments DEFAULT_ARGUMENT_SYMBOLS = dict( - zip(['.'.join([astroid.bases.BUILTINS, x]) for x in ('set', 'dict', 'list')], + zip(['.'.join([BUILTINS, x]) for x in ('set', 'dict', 'list')], ['set()', '{}', '[]']) ) +REVERSED_COMPS = {'<': '>', '<=': '>=', '>': '<', '>=': '<='} del re @@ -87,10 +104,10 @@ def _redefines_import(node): current = node while current and not isinstance(current.parent, astroid.ExceptHandler): current = current.parent - if not current or not is_import_error(current.parent): + if not current or not error_of_type(current.parent, ImportError): return False try_block = current.parent.parent - for import_node in try_block.nodes_of_class((astroid.From, astroid.Import)): + for import_node in try_block.nodes_of_class((astroid.ImportFrom, astroid.Import)): for name, alias in import_node.names: if alias: if alias == node.name: @@ -104,7 +121,7 @@ def in_loop(node): parent = node.parent while parent is not None: if isinstance(parent, (astroid.For, astroid.ListComp, astroid.SetComp, - astroid.DictComp, astroid.GenExpr)): + astroid.DictComp, astroid.GeneratorExp)): return True parent = parent.parent return False @@ -167,19 +184,25 @@ def _determine_function_name_type(node): # If the function is a property (decorated with @property # or @abc.abstractproperty), the name type is 'attr'. if (isinstance(decorator, astroid.Name) or - (isinstance(decorator, astroid.Getattr) and + (isinstance(decorator, astroid.Attribute) and decorator.attrname == 'abstractproperty')): infered = safe_infer(decorator) if infered and infered.qname() in PROPERTY_CLASSES: return 'attr' # If the function is decorated using the prop_method.{setter,getter} # form, treat it like an attribute as well. - elif (isinstance(decorator, astroid.Getattr) and + elif (isinstance(decorator, astroid.Attribute) and decorator.attrname in ('setter', 'deleter')): return 'attr' return 'method' +def _is_none(node): + return (node is None or + (isinstance(node, astroid.Const) and node.value is None) or + (isinstance(node, astroid.Name) and node.name == 'None') + ) + def _has_abstract_methods(node): """ @@ -242,11 +265,33 @@ def x(self, value): self._x = value """ if node.decorators: for decorator in node.decorators.nodes: - if (isinstance(decorator, astroid.Getattr) and + if (isinstance(decorator, astroid.Attribute) and getattr(decorator.expr, 'name', None) == node.name): return True return False + +def _node_type(node): + """Return the inferred type for `node` + + If there is more than one possible type, or if inferred type is YES or None, + return None + """ + # check there is only one possible type for the assign node. Else we + # don't handle it for now + types = set() + try: + for var_type in node.infer(): + if var_type == astroid.YES or _is_none(var_type): + continue + types.add(var_type) + if len(types) > 1: + return + except InferenceError: + return + return types.pop() if types else None + + class _BasicChecker(BaseChecker): __implements__ = IAstroidChecker name = 'basic' @@ -298,32 +343,89 @@ class BasicErrorChecker(_BasicChecker): 'Loops should only have an else clause if they can exit early ' 'with a break statement, otherwise the statements under else ' 'should be on the same scope as the loop itself.'), + 'E0112': ('More than one starred expression in assignment', + 'too-many-star-expressions', + 'Emitted when there are more than one starred ' + 'expressions (`*x`) in an assignment. This is a SyntaxError.', + {'minversion': (3, 0)}), + 'E0113': ('Starred assignment target must be in a list or tuple', + 'invalid-star-assignment-target', + 'Emitted when a star expression is used as a starred ' + 'assignment target.', + {'minversion': (3, 0)}), + 'E0114': ('Can use starred expression only in assignment target', + 'star-needs-assignment-target', + 'Emitted when a star expression is not used in an ' + 'assignment target.', + {'minversion': (3, 0)}), + 'E0115': ('Name %r is nonlocal and global', + 'nonlocal-and-global', + 'Emitted when a name is both nonlocal and global.', + {'minversion': (3, 0)}), + 'E0116': ("'continue' not supported inside 'finally' clause", + 'continue-in-finally', + 'Emitted when the `continue` keyword is found ' + 'inside a finally clause, which is a SyntaxError.'), + 'E0117': ("nonlocal name %s found without binding", + 'nonlocal-without-binding', + 'Emitted when a nonlocal variable does not have an attached ' + 'name somewhere in the parent scopes', + {'minversion': (3, 0)}), } @check_messages('function-redefined') - def visit_class(self, node): + def visit_classdef(self, node): self._check_redefinition('class', node) + @check_messages('too-many-star-expressions', + 'invalid-star-assignment-target') + def visit_assign(self, node): + starred = list(node.targets[0].nodes_of_class(astroid.Starred)) + if len(starred) > 1: + self.add_message('too-many-star-expressions', node=node) + + # Check *a = b + if isinstance(node.targets[0], astroid.Starred): + self.add_message('invalid-star-assignment-target', node=node) + + @check_messages('star-needs-assignment-target') + def visit_starred(self, node): + """Check that a Starred expression is used in an assignment target.""" + if isinstance(node.parent, astroid.Call): + # f(*args) is converted to Call(args=[Starred]), so ignore + # them for this check. + return + if PY35 and isinstance(node.parent, + (astroid.List, astroid.Tuple, + astroid.Set, astroid.Dict)): + # PEP 448 unpacking. + return + + stmt = node.statement() + if not isinstance(stmt, astroid.Assign): + return + + if stmt.value is node or stmt.value.parent_of(node): + self.add_message('star-needs-assignment-target', node=node) + @check_messages('init-is-generator', 'return-in-init', 'function-redefined', 'return-arg-in-generator', - 'duplicate-argument-name') - def visit_function(self, node): + 'duplicate-argument-name', 'nonlocal-and-global') + def visit_functiondef(self, node): + self._check_nonlocal_and_global(node) if not redefined_by_decorator(node): self._check_redefinition(node.is_method() and 'method' or 'function', node) # checks for max returns, branch, return in __init__ returns = node.nodes_of_class(astroid.Return, - skip_klass=(astroid.Function, astroid.Class)) + skip_klass=(astroid.FunctionDef, + astroid.ClassDef)) if node.is_method() and node.name == '__init__': if node.is_generator(): self.add_message('init-is-generator', node=node) else: values = [r.value for r in returns] # Are we returning anything but None from constructors - if [v for v in values - if not (v is None or - (isinstance(v, astroid.Const) and v.value is None) or - (isinstance(v, astroid.Name) and v.name == 'None') - )]: + if [v for v in values if not _is_none(v)]: self.add_message('return-in-init', node=node) elif node.is_generator(): # make sure we don't mix non-None returns and yields @@ -341,18 +443,38 @@ def visit_function(self, node): else: args.add(name) + visit_asyncfunctiondef = visit_functiondef + + def _check_nonlocal_and_global(self, node): + """Check that a name is both nonlocal and global.""" + def same_scope(current): + return current.scope() is node + + from_iter = itertools.chain.from_iterable + nonlocals = set(from_iter( + child.names for child in node.nodes_of_class(astroid.Nonlocal) + if same_scope(child))) + global_vars = set(from_iter( + child.names for child in node.nodes_of_class(astroid.Global) + if same_scope(child))) + for name in nonlocals.intersection(global_vars): + self.add_message('nonlocal-and-global', + args=(name, ), node=node) @check_messages('return-outside-function') def visit_return(self, node): - if not isinstance(node.frame(), astroid.Function): + if not isinstance(node.frame(), astroid.FunctionDef): self.add_message('return-outside-function', node=node) @check_messages('yield-outside-function') def visit_yield(self, node): - if not isinstance(node.frame(), (astroid.Function, astroid.Lambda)): - self.add_message('yield-outside-function', node=node) + self._check_yield_outside_func(node) - @check_messages('not-in-loop') + @check_messages('yield-outside-function') + def visit_yieldfrom(self, node): + self._check_yield_outside_func(node) + + @check_messages('not-in-loop', 'continue-in-finally') def visit_continue(self, node): self._check_in_loop(node, 'continue') @@ -376,8 +498,33 @@ def visit_unaryop(self, node): (node.operand.op == node.op)): self.add_message('nonexistent-operator', node=node, args=node.op*2) + def _check_nonlocal_without_binding(self, node, name): + current_scope = node.scope() + while True: + if current_scope.parent is None: + break + + if not isinstance(current_scope, astroid.FunctionDef): + self.add_message('nonlocal-without-binding', args=(name, ), + node=node) + return + else: + if name not in current_scope.locals: + current_scope = current_scope.parent.scope() + continue + else: + # Okay, found it. + return + + self.add_message('nonlocal-without-binding', args=(name, ), node=node) + + @check_messages('nonlocal-without-binding') + def visit_nonlocal(self, node): + for name in node.names: + self._check_nonlocal_without_binding(node, name) + @check_messages('abstract-class-instantiated') - def visit_callfunc(self, node): + def visit_call(self, node): """ Check instantiating abstract class with abc.ABCMeta as metaclass. """ @@ -385,8 +532,18 @@ def visit_callfunc(self, node): infered = next(node.func.infer()) except astroid.InferenceError: return - if not isinstance(infered, astroid.Class): + + if not isinstance(infered, astroid.ClassDef): + return + + klass = node_frame_class(node) + if klass is infered: + # Don't emit the warning if the class is instantiated + # in its own body or if the call is not an instance + # creation. If the class is instantiated into its own + # body, we're expecting that it knows what it is doing. return + # __init__ was called metaclass = infered.metaclass() abstract_methods = _has_abstract_methods(infered) @@ -405,6 +562,10 @@ def visit_callfunc(self, node): args=(infered.name, ), node=node) + def _check_yield_outside_func(self, node): + if not isinstance(node.frame(), (astroid.FunctionDef, astroid.Lambda)): + self.add_message('yield-outside-function', node=node) + def _check_else_on_loop(self, node): """Check that any loop with an else clause has a break statement.""" if node.orelse and not _loop_exits_early(node): @@ -419,10 +580,19 @@ def _check_in_loop(self, node, node_name): _node = node.parent while _node: if isinstance(_node, (astroid.For, astroid.While)): + if node not in _node.orelse: + return + + if isinstance(_node, (astroid.ClassDef, astroid.FunctionDef)): break + if (isinstance(_node, astroid.TryFinally) + and node in _node.finalbody + and isinstance(node, astroid.Continue)): + self.add_message('continue-in-finally', node=node) + _node = _node.parent - else: - self.add_message('not-in-loop', node=node, args=node_name) + + self.add_message('not-in-loop', node=node, args=node_name) def _check_redefinition(self, redeftype, node): """check for redefinition of a function / method / class name""" @@ -491,7 +661,7 @@ class BasicChecker(_BasicChecker): 'usage. Consider using `ast.literal_eval` for safely evaluating ' 'strings containing Python expressions ' 'from untrusted sources. '), - 'W0141': ('Used builtin function %r', + 'W0141': ('Used builtin function %s', 'bad-builtin', 'Used when a black listed builtin function is used (see the ' 'bad-function option). Usual black listed functions are the ones ' @@ -508,13 +678,18 @@ class BasicChecker(_BasicChecker): 'A call of assert on a tuple will always evaluate to true if ' 'the tuple is not empty, and will always evaluate to false if ' 'it is.'), - 'C0121': ('Missing required attribute "%s"', # W0103 - 'missing-module-attribute', - 'Used when an attribute required for modules is missing.'), - - 'E0109': ('Missing argument to reversed()', - 'missing-reversed-argument', - 'Used when reversed() builtin didn\'t receive an argument.'), + 'W0124': ('Following "as" with another context manager looks like a tuple.', + 'confusing-with-statement', + 'Emitted when a `with` statement component returns multiple values ' + 'and uses name binding with `as` only for a part of those values, ' + 'as in with ctx() as a, b. This can be misleading, since it\'s not ' + 'clear if the context manager returns a tuple or if the node without ' + 'a name binding is another context manager.'), + 'W0125': ('Using a conditional statement with a constant value', + 'using-constant-test', + 'Emitted when a conditional statement (If or ternary if) ' + 'uses a constant value for its test. This might not be what ' + 'the user intended to do.'), 'E0111': ('The first reversed() argument is not a sequence', 'bad-reversed-sequence', 'Used when the first argument to reversed() builtin ' @@ -524,11 +699,10 @@ class BasicChecker(_BasicChecker): } options = (('required-attributes', - {'default' : (), 'type' : 'csv', - 'metavar' : '', - 'help' : 'Required attributes for module, separated by a ' - 'comma'} - ), + deprecated_option(opt_type='csv', + help_msg="Required attributes for module. " + "This option is obsolete.")), + ('bad-functions', {'default' : BAD_FUNCTIONS, 'type' :'csv', 'metavar' : '', @@ -550,16 +724,52 @@ def open(self): self.stats = self.linter.add_stats(module=0, function=0, method=0, class_=0) - @check_messages('missing-module-attribute') - def visit_module(self, node): + @check_messages('using-constant-test') + def visit_if(self, node): + self._check_using_constant_test(node, node.test) + + @check_messages('using-constant-test') + def visit_ifexp(self, node): + self._check_using_constant_test(node, node.test) + + @check_messages('using-constant-test') + def visit_comprehension(self, node): + if node.ifs: + for if_test in node.ifs: + self._check_using_constant_test(node, if_test) + + def _check_using_constant_test(self, node, test): + const_nodes = ( + astroid.Module, + astroid.scoped_nodes.GeneratorExp, + astroid.Lambda, astroid.FunctionDef, astroid.ClassDef, + astroid.bases.Generator, astroid.UnboundMethod, + astroid.BoundMethod, astroid.Module) + structs = (astroid.Dict, astroid.Tuple, astroid.Set) + + # These nodes are excepted, since they are not constant + # values, requiring a computation to happen. The only type + # of node in this list which doesn't have this property is + # Getattr, which is excepted because the conditional statement + # can be used to verify that the attribute was set inside a class, + # which is definitely a valid use case. + except_nodes = (astroid.Attribute, astroid.Call, + astroid.BinOp, astroid.BoolOp, astroid.UnaryOp, + astroid.Subscript) + inferred = None + emit = isinstance(test, (astroid.Const, ) + structs + const_nodes) + if not isinstance(test, except_nodes): + inferred = safe_infer(test) + + if emit or isinstance(inferred, const_nodes): + self.add_message('using-constant-test', node=node) + + def visit_module(self, _): """check module name, docstring and required arguments """ self.stats['module'] += 1 - for attr in self.config.required_attributes: - if attr not in node: - self.add_message('missing-module-attribute', node=node, args=attr) - def visit_class(self, node): # pylint: disable=unused-argument + def visit_classdef(self, node): # pylint: disable=unused-argument """check module name, docstring and redefinition increment branch counter """ @@ -567,7 +777,7 @@ def visit_class(self, node): # pylint: disable=unused-argument @check_messages('pointless-statement', 'pointless-string-statement', 'expression-not-assigned') - def visit_discard(self, node): + def visit_expr(self, node): """check for various kind of statements without effect""" expr = node.value if isinstance(expr, astroid.Const) and isinstance(expr.value, @@ -577,8 +787,8 @@ def visit_discard(self, node): # An attribute docstring is defined as being a string right after # an assignment at the module level, class level or __init__ level. scope = expr.scope() - if isinstance(scope, (astroid.Class, astroid.Module, astroid.Function)): - if isinstance(scope, astroid.Function) and scope.name != '__init__': + if isinstance(scope, (astroid.ClassDef, astroid.Module, astroid.FunctionDef)): + if isinstance(scope, astroid.FunctionDef) and scope.name != '__init__': pass else: sibling = expr.previous_sibling() @@ -593,16 +803,40 @@ def visit_discard(self, node): # * a yield (which are wrapped by a discard node in _ast XXX) # warn W0106 if we have any underlying function call (we can't predict # side effects), else pointless-statement - if (isinstance(expr, (astroid.Yield, astroid.CallFunc)) or + if (isinstance(expr, (astroid.Yield, astroid.Await, astroid.Call)) or (isinstance(node.parent, astroid.TryExcept) and node.parent.body == [node])): return - if any(expr.nodes_of_class(astroid.CallFunc)): + if any(expr.nodes_of_class(astroid.Call)): self.add_message('expression-not-assigned', node=node, args=expr.as_string()) else: self.add_message('pointless-statement', node=node) + @staticmethod + def _filter_vararg(node, call_args): + # Return the arguments for the given call which are + # not passed as vararg. + for arg in call_args: + if isinstance(arg, astroid.Starred): + if (isinstance(arg.value, astroid.Name) + and arg.value.name != node.args.vararg): + yield arg + else: + yield arg + + @staticmethod + def _has_variadic_argument(args, variadic_name): + if not args: + return True + for arg in args: + if isinstance(arg.value, astroid.Name): + if arg.value.name != variadic_name: + return True + else: + return True + return False + @check_messages('unnecessary-lambda') def visit_lambda(self, node): """check whether or not the lambda is suspicious @@ -618,56 +852,53 @@ def visit_lambda(self, node): # of the lambda. return call = node.body - if not isinstance(call, astroid.CallFunc): + if not isinstance(call, astroid.Call): # The body of the lambda must be a function call expression # for the lambda to be unnecessary. return - # XXX are lambda still different with astroid >= 0.18 ? - # *args and **kwargs need to be treated specially, since they - # are structured differently between the lambda and the function - # call (in the lambda they appear in the args.args list and are - # indicated as * and ** by two bits in the lambda's flags, but - # in the function call they are omitted from the args list and - # are indicated by separate attributes on the function call node). + if (isinstance(node.body.func, astroid.Attribute) and + isinstance(node.body.func.expr, astroid.Call)): + # Chained call, the intermediate call might + # return something else (but we don't check that, yet). + return + ordinary_args = list(node.args.args) + new_call_args = list(self._filter_vararg(node, call.args)) if node.args.kwarg: - if (not call.kwargs - or not isinstance(call.kwargs, astroid.Name) - or node.args.kwarg != call.kwargs.name): + if self._has_variadic_argument(call.kwargs, node.args.kwarg): return - elif call.kwargs: + elif call.kwargs or call.keywords: return + if node.args.vararg: - if (not call.starargs - or not isinstance(call.starargs, astroid.Name) - or node.args.vararg != call.starargs.name): + if self._has_variadic_argument(call.starargs, node.args.vararg): return elif call.starargs: return + # The "ordinary" arguments must be in a correspondence such that: # ordinary_args[i].name == call.args[i].name. - if len(ordinary_args) != len(call.args): + if len(ordinary_args) != len(new_call_args): return - for i in range(len(ordinary_args)): - if not isinstance(call.args[i], astroid.Name): + for arg, passed_arg in zip(ordinary_args, new_call_args): + if not isinstance(passed_arg, astroid.Name): return - if node.args.args[i].name != call.args[i].name: + if arg.name != passed_arg.name: return - if (isinstance(node.body.func, astroid.Getattr) and - isinstance(node.body.func.expr, astroid.CallFunc)): - # Chained call, the intermediate call might - # return something else (but we don't check that, yet). - return - self.add_message('unnecessary-lambda', line=node.fromlineno, node=node) + + self.add_message('unnecessary-lambda', line=node.fromlineno, + node=node) @check_messages('dangerous-default-value') - def visit_function(self, node): + def visit_functiondef(self, node): """check function name, docstring, arguments, redefinition, variable names, max locals """ self.stats[node.is_method() and 'method' or 'function'] += 1 self._check_dangerous_default(node) + visit_asyncfunctiondef = visit_functiondef + def _check_dangerous_default(self, node): # check for dangerous default values as arguments is_iterable = lambda n: isinstance(n, (astroid.List, @@ -684,7 +915,7 @@ def _check_dangerous_default(self, node): if value is default: msg = DEFAULT_ARGUMENT_SYMBOLS[value.qname()] - elif type(value) is astroid.Instance or is_iterable(value): + elif isinstance(value, astroid.Instance) or is_iterable(value): # We are here in the following situation(s): # * a dict/set/list/tuple call which wasn't inferred # to a syntax node ({}, () etc.). This can happen @@ -694,7 +925,7 @@ def _check_dangerous_default(self, node): # or a dict. if is_iterable(default): msg = value.pytype() - elif isinstance(default, astroid.CallFunc): + elif isinstance(default, astroid.Call): msg = '%s() (%s)' % (value.name, value.qname()) else: msg = '%s (%s)' % (default.as_string(), value.qname()) @@ -715,7 +946,7 @@ def visit_return(self, node): """ self._check_unreachable(node) # Is it inside final body of a try...finally bloc ? - self._check_not_in_finally(node, 'return', (astroid.Function,)) + self._check_not_in_finally(node, 'return', (astroid.FunctionDef,)) @check_messages('unreachable') def visit_continue(self, node): @@ -749,9 +980,8 @@ def visit_exec(self, node): self.add_message('exec-used', node=node) @check_messages('bad-builtin', 'eval-used', - 'exec-used', 'missing-reversed-argument', - 'bad-reversed-sequence') - def visit_callfunc(self, node): + 'exec-used', 'bad-reversed-sequence') + def visit_call(self, node): """visit a CallFunc node -> check if this is not a blacklisted builtin call and check for * or ** use """ @@ -768,7 +998,12 @@ def visit_callfunc(self, node): elif name == 'eval': self.add_message('eval-used', node=node) if name in self.config.bad_functions: - self.add_message('bad-builtin', node=node, args=name) + hint = BUILTIN_HINTS.get(name) + if hint: + args = "%r. %s" % (name, hint) + else: + args = repr(name) + self.add_message('bad-builtin', node=node, args=args) @check_messages('assert-on-tuple') def visit_assert(self, node): @@ -825,14 +1060,14 @@ def _check_reversed(self, node): try: argument = safe_infer(get_argument_from_call(node, position=0)) except NoSuchArgumentError: - self.add_message('missing-reversed-argument', node=node) + pass else: if argument is astroid.YES: return if argument is None: # Nothing was infered. # Try to see if we have iter(). - if isinstance(node.args[0], astroid.CallFunc): + if isinstance(node.args[0], astroid.Call): try: func = next(node.args[0].func.infer()) except InferenceError: @@ -849,8 +1084,12 @@ def _check_reversed(self, node): return elif any(ancestor.name == 'dict' and is_builtin_object(ancestor) for ancestor in argument._proxied.ancestors()): - # mappings aren't accepted by reversed() - self.add_message('bad-reversed-sequence', node=node) + # Mappings aren't accepted by reversed(), unless + # they provide explicitly a __reversed__ method. + try: + argument.locals[REVERSED_PROTOCOL_METHOD] + except KeyError: + self.add_message('bad-reversed-sequence', node=node) return for methods in REVERSED_METHODS: @@ -862,16 +1101,39 @@ def _check_reversed(self, node): else: break else: - # Check if it is a .deque. It doesn't seem that - # we can retrieve special methods - # from C implemented constructs. - if argument._proxied.qname().endswith(".deque"): - return self.add_message('bad-reversed-sequence', node=node) elif not isinstance(argument, (astroid.List, astroid.Tuple)): # everything else is not a proper sequence for reversed() self.add_message('bad-reversed-sequence', node=node) + @check_messages('confusing-with-statement') + def visit_with(self, node): + if not PY3K: + # in Python 2 a "with" statement with multiple managers coresponds + # to multiple nested AST "With" nodes + pairs = [] + parent_node = node.parent + if isinstance(parent_node, astroid.With): + # we only care about the direct parent, since this method + # gets called for each with node anyway + pairs.extend(parent_node.items) + pairs.extend(node.items) + else: + # in PY3K a "with" statement with multiple managers coresponds + # to one AST "With" node with multiple items + pairs = node.items + if pairs: + for prev_pair, pair in zip(pairs, pairs[1:]): + if (isinstance(prev_pair[1], astroid.AssignName) and + (pair[1] is None and not isinstance(pair[0], astroid.Call))): + # don't emit a message if the second is a function call + # there's no way that can be mistaken for a name assignment + if PY3K or node.lineno == node.parent.lineno: + # if the line number doesn't match + # we assume it's a nested "with" + self.add_message('confusing-with-statement', node=node) + + _NAME_TYPES = { 'module': (MOD_NAME_RGX, 'module'), 'const': (CONST_NAME_RGX, 'constant'), @@ -980,14 +1242,14 @@ def leave_module(self, node): # pylint: disable=unused-argument self._raise_name_warning(*args) @check_messages('blacklisted-name', 'invalid-name') - def visit_class(self, node): + def visit_classdef(self, node): self._check_name('class', node.name, node) for attr, anodes in six.iteritems(node.instance_attrs): - if not list(node.instance_attr_ancestors(attr)): + if not any(node.instance_attr_ancestors(attr)): self._check_name('attr', attr, anodes[0]) @check_messages('blacklisted-name', 'invalid-name') - def visit_function(self, node): + def visit_functiondef(self, node): # Do not emit any warnings if the method is just an implementation # of a base class method. confidence = HIGH @@ -1004,21 +1266,23 @@ def visit_function(self, node): if args is not None: self._recursive_check_names(args, node) + visit_asyncfunctiondef = visit_functiondef + @check_messages('blacklisted-name', 'invalid-name') def visit_global(self, node): for name in node.names: self._check_name('const', name, node) @check_messages('blacklisted-name', 'invalid-name') - def visit_assname(self, node): + def visit_assignname(self, node): """check module level assigned names""" frame = node.frame() - ass_type = node.ass_type() + ass_type = node.assign_type() if isinstance(ass_type, astroid.Comprehension): self._check_name('inlinevar', node.name, node) elif isinstance(frame, astroid.Module): if isinstance(ass_type, astroid.Assign) and not in_loop(ass_type): - if isinstance(safe_infer(ass_type.value), astroid.Class): + if isinstance(safe_infer(ass_type.value), astroid.ClassDef): self._check_name('class', node.name, node) else: if not _redefines_import(node): @@ -1027,19 +1291,19 @@ def visit_assname(self, node): self._check_name('const', node.name, node) elif isinstance(ass_type, astroid.ExceptHandler): self._check_name('variable', node.name, node) - elif isinstance(frame, astroid.Function): + elif isinstance(frame, astroid.FunctionDef): # global introduced variable aren't in the function locals if node.name in frame and node.name not in frame.argnames(): if not _redefines_import(node): self._check_name('variable', node.name, node) - elif isinstance(frame, astroid.Class): + elif isinstance(frame, astroid.ClassDef): if not list(frame.local_attr_ancestors(node.name)): self._check_name('class_attribute', node.name, node) def _recursive_check_names(self, args, node): """check names in a possibly recursive list """ for arg in args: - if isinstance(arg, astroid.AssName): + if isinstance(arg, astroid.AssignName): self._check_name('argument', arg.name, node) else: self._recursive_check_names(arg.elts, node) @@ -1119,22 +1383,34 @@ def visit_module(self, node): self._check_docstring('module', node) @check_messages('missing-docstring', 'empty-docstring') - def visit_class(self, node): + def visit_classdef(self, node): if self.config.no_docstring_rgx.match(node.name) is None: self._check_docstring('class', node) + @staticmethod + def _is_setter_or_deleter(node): + names = {'setter', 'deleter'} + for decorator in node.decorators.nodes: + if (isinstance(decorator, astroid.Attribute) + and decorator.attrname in names): + return True + return False + @check_messages('missing-docstring', 'empty-docstring') - def visit_function(self, node): + def visit_functiondef(self, node): if self.config.no_docstring_rgx.match(node.name) is None: ftype = node.is_method() and 'method' or 'function' - if isinstance(node.parent.frame(), astroid.Class): + if node.decorators and self._is_setter_or_deleter(node): + return + + if isinstance(node.parent.frame(), astroid.ClassDef): overridden = False confidence = (INFERENCE if has_known_bases(node.parent.frame()) else INFERENCE_FAILURE) # check if node is from a method overridden by its ancestor for ancestor in node.parent.frame().ancestors(): if node.name in ancestor and \ - isinstance(ancestor[node.name], astroid.Function): + isinstance(ancestor[node.name], astroid.FunctionDef): overridden = True break self._check_docstring(ftype, node, @@ -1143,6 +1419,8 @@ def visit_function(self, node): else: self._check_docstring(ftype, node) + visit_asyncfunctiondef = visit_functiondef + def _check_docstring(self, node_type, node, report_missing=True, confidence=HIGH): """check the node has a non empty docstring""" @@ -1164,8 +1442,8 @@ def _check_docstring(self, node_type, node, report_missing=True, if node_type != 'module' and max_lines > -1 and lines < max_lines: return self.stats['undocumented_'+node_type] += 1 - if (node.body and isinstance(node.body[0], astroid.Discard) and - isinstance(node.body[0].value, astroid.CallFunc)): + if (node.body and isinstance(node.body[0], astroid.Expr) and + isinstance(node.body[0].value, astroid.Call)): # Most likely a string with a format call. Let's see. func = safe_infer(node.body[0].value.func) if (isinstance(func, astroid.BoundMethod) @@ -1212,7 +1490,7 @@ class LambdaForComprehensionChecker(_BasicChecker): } @check_messages('deprecated-lambda') - def visit_callfunc(self, node): + def visit_call(self, node): """visit a CallFunc node, check if map or filter are called with a lambda """ @@ -1226,6 +1504,497 @@ def visit_callfunc(self, node): self.add_message('deprecated-lambda', node=node) +class RecommandationChecker(_BasicChecker): + msgs = {'C0200': ('Consider using enumerate instead of iterating with range and len', + 'consider-using-enumerate', + 'Emitted when code that iterates with range and len is ' + 'encountered. Such code can be simplified by using the ' + 'enumerate builtin.'), + } + + @staticmethod + def _is_builtin(node, function): + inferred = safe_infer(node) + if not inferred: + return False + return is_builtin_object(inferred) and inferred.name == function + + @check_messages('consider-using-enumerate') + def visit_for(self, node): + """Emit a convention whenever range and len are used for indexing.""" + # Verify that we have a `range(len(...))` call and that the object + # which is iterated is used as a subscript in the body of the for. + + # Is it a proper range call? + if not isinstance(node.iter, astroid.Call): + return + if not self._is_builtin(node.iter.func, 'range'): + return + if len(node.iter.args) != 1: + return + + # Is it a proper len call? + if not isinstance(node.iter.args[0], astroid.Call): + return + second_func = node.iter.args[0].func + if not self._is_builtin(second_func, 'len'): + return + len_args = node.iter.args[0].args + if not len_args or len(len_args) != 1: + return + iterating_object = len_args[0] + if not isinstance(iterating_object, astroid.Name): + return + + # Verify that the body of the for loop uses a subscript + # with the object that was iterated. This uses some heuristics + # in order to make sure that the same object is used in the + # for body. + for child in node.body: + for subscript in child.nodes_of_class(astroid.Subscript): + if not isinstance(subscript.value, astroid.Name): + continue + if not isinstance(subscript.slice, astroid.Index): + continue + if not isinstance(subscript.slice.value, astroid.Name): + continue + if subscript.slice.value.name != node.target.name: + continue + if iterating_object.name != subscript.value.name: + continue + if subscript.value.scope() != node.scope(): + # Ignore this subscript if it's not in the same + # scope. This means that in the body of the for + # loop, another scope was created, where the same + # name for the iterating object was used. + continue + self.add_message('consider-using-enumerate', node=node) + return + + +def _is_one_arg_pos_call(call): + """Is this a call with exactly 1 argument, + where that argument is positional? + """ + return (isinstance(call, astroid.Call) + and len(call.args) == 1 and not call.keywords) + + +class ComparisonChecker(_BasicChecker): + """Checks for comparisons + + - singleton comparison: 'expr == True', 'expr == False' and 'expr == None' + - yoda condition: 'const "comp" right' where comp can be '==', '!=', '<', + '<=', '>' or '>=', and right can be a variable, an attribute, a method or + a function + """ + msgs = {'C0121': ('Comparison to %s should be %s', + 'singleton-comparison', + 'Used when an expression is compared to singleton ' + 'values like True, False or None.'), + 'C0122': ('Comparison should be %s', + 'misplaced-comparison-constant', + 'Used when the constant is placed on the left side' + 'of a comparison. It is usually clearer in intent to ' + 'place it in the right hand side of the comparison.'), + 'C0123': ('Using type() instead of isinstance() for a typecheck.', + 'unidiomatic-typecheck', + 'The idiomatic way to perform an explicit typecheck in ' + 'Python is to use isinstance(x, Y) rather than ' + 'type(x) == Y, type(x) is Y. Though there are unusual ' + 'situations where these give different results.', + {'old_names': [('W0154', 'unidiomatic-typecheck')]}), + } + + def _check_singleton_comparison(self, singleton, root_node): + if singleton.value is True: + suggestion = "just 'expr' or 'expr is True'" + self.add_message('singleton-comparison', + node=root_node, + args=(True, suggestion)) + elif singleton.value is False: + suggestion = "'not expr' or 'expr is False'" + self.add_message('singleton-comparison', + node=root_node, + args=(False, suggestion)) + elif singleton.value is None: + self.add_message('singleton-comparison', + node=root_node, + args=(None, "'expr is None'")) + + def _check_misplaced_constant(self, node, left, right, operator): + if isinstance(right, astroid.Const): + return + operator = REVERSED_COMPS.get(operator, operator) + suggestion = '%s %s %r' % (right.as_string(), operator, left.value) + self.add_message('misplaced-comparison-constant', node=node, + args=(suggestion,)) + + @check_messages('singleton-comparison', 'misplaced-comparison-constant', + 'unidiomatic-typecheck') + def visit_compare(self, node): + self._check_unidiomatic_typecheck(node) + # NOTE: this checker only works with binary comparisons like 'x == 42' + # but not 'x == y == 42' + if len(node.ops) != 1: + return + left = node.left + operator, right = node.ops[0] + if (operator in ('<', '<=', '>', '>=', '!=', '==') + and isinstance(left, astroid.Const)): + self._check_misplaced_constant(node, left, right, operator) + + if operator == '==': + if isinstance(left, astroid.Const): + self._check_singleton_comparison(left, node) + elif isinstance(right, astroid.Const): + self._check_singleton_comparison(right, node) + + def _check_unidiomatic_typecheck(self, node): + operator, right = node.ops[0] + if operator in TYPECHECK_COMPARISON_OPERATORS: + left = node.left + if _is_one_arg_pos_call(left): + self._check_type_x_is_y(node, left, operator, right) + + def _check_type_x_is_y(self, node, left, operator, right): + """Check for expressions like type(x) == Y.""" + left_func = safe_infer(left.func) + if not (isinstance(left_func, astroid.ClassDef) + and left_func.qname() == TYPE_QNAME): + return + + if operator in ('is', 'is not') and _is_one_arg_pos_call(right): + right_func = safe_infer(right.func) + if (isinstance(right_func, astroid.ClassDef) + and right_func.qname() == TYPE_QNAME): + # type(x) == type(a) + right_arg = safe_infer(right.args[0]) + if not isinstance(right_arg, LITERAL_NODE_TYPES): + # not e.g. type(x) == type([]) + return + self.add_message('unidiomatic-typecheck', node=node) + + +class ElifChecker(BaseTokenChecker): + """Checks needing to distinguish "else if" from "elif" + + This checker mixes the astroid and the token approaches in order to create + knowledge about whether a "else if" node is a true "else if" node, or a + "elif" node. + + The following checks depend on this implementation: + - check for too many nested blocks (if/elif structures aren't considered + as nested) + - to be continued + """ + __implements__ = (ITokenChecker, IAstroidChecker) + name = 'elif' + msgs = {'R0101': ('Too many nested blocks (%s/%s)', + 'too-many-nested-blocks', + 'Used when a function or a method has too many nested ' + 'blocks. This makes the code less understandable and ' + 'maintainable.'), + 'R0102': ('The if statement can be replaced with %s', + 'simplifiable-if-statement', + 'Used when an if statement can be replaced with ' + '\'bool(test)\'. '), + } + options = (('max-nested-blocks', + {'default' : 5, 'type' : 'int', 'metavar' : '', + 'help': 'Maximum number of nested blocks for function / ' + 'method body'} + ),) + + def __init__(self, linter=None): + BaseTokenChecker.__init__(self, linter) + self._init() + + def _init(self): + self._nested_blocks = [] + self._elifs = [] + self._if_counter = 0 + self._nested_blocks_msg = None + + @staticmethod + def _is_bool_const(node): + return (isinstance(node.value, astroid.Const) + and isinstance(node.value.value, bool)) + + def _is_actual_elif(self, node): + """Check if the given node is an actual elif + + This is a problem we're having with the builtin ast module, + which splits `elif` branches into a separate if statement. + Unfortunately we need to know the exact type in certain + cases. + """ + + if isinstance(node.parent, astroid.If): + orelse = node.parent.orelse + # current if node must directly follow a "else" + if orelse and orelse == [node]: + if self._elifs[self._if_counter]: + return True + return False + + def _check_simplifiable_if(self, node): + """Check if the given if node can be simplified. + + The if statement can be reduced to a boolean expression + in some cases. For instance, if there are two branches + and both of them return a boolean value that depends on + the result of the statement's test, then this can be reduced + to `bool(test)` without losing any functionality. + """ + + if self._is_actual_elif(node): + # Not interested in if statements with multiple branches. + return + if len(node.orelse) != 1 or len(node.body) != 1: + return + + # Check if both branches can be reduced. + first_branch = node.body[0] + else_branch = node.orelse[0] + if isinstance(first_branch, astroid.Return): + if not isinstance(else_branch, astroid.Return): + return + first_branch_is_bool = self._is_bool_const(first_branch) + else_branch_is_bool = self._is_bool_const(else_branch) + reduced_to = "'return bool(test)'" + elif isinstance(first_branch, astroid.Assign): + if not isinstance(else_branch, astroid.Assign): + return + first_branch_is_bool = self._is_bool_const(first_branch) + else_branch_is_bool = self._is_bool_const(else_branch) + reduced_to = "'var = bool(test)'" + else: + return + + if not first_branch_is_bool or not else_branch_is_bool: + return + if not first_branch.value.value: + # This is a case that can't be easily simplified and + # if it can be simplified, it will usually result in a + # code that's harder to understand and comprehend. + # Let's take for instance `arg and arg <= 3`. This could theoretically be + # reduced to `not arg or arg > 3`, but the net result is that now the + # condition is harder to understand, because it requires understanding of + # an extra clause: + # * first, there is the negation of truthness with `not arg` + # * the second clause is `arg > 3`, which occurs when arg has a + # a truth value, but it implies that `arg > 3` is equivalent + # with `arg and arg > 3`, which means that the user must + # think about this assumption when evaluating `arg > 3`. + # The original form is easier to grasp. + return + + self.add_message('simplifiable-if-statement', node=node, + args=(reduced_to, )) + + def process_tokens(self, tokens): + # Process tokens and look for 'if' or 'elif' + for _, token, _, _, _ in tokens: + if token == 'elif': + self._elifs.append(True) + elif token == 'if': + self._elifs.append(False) + + def leave_module(self, _): + self._init() + + @check_messages('too-many-nested-blocks') + def visit_tryexcept(self, node): + self._check_nested_blocks(node) + + visit_tryfinally = visit_tryexcept + visit_while = visit_tryexcept + visit_for = visit_while + + def visit_ifexp(self, _): + self._if_counter += 1 + + def visit_comprehension(self, node): + self._if_counter += len(node.ifs) + + @check_messages('too-many-nested-blocks', 'simplifiable-if-statement') + def visit_if(self, node): + self._check_simplifiable_if(node) + self._check_nested_blocks(node) + self._if_counter += 1 + + @check_messages('too-many-nested-blocks') + def leave_functiondef(self, _): + # new scope = reinitialize the stack of nested blocks + self._nested_blocks = [] + # if there is a waiting message left, send it + if self._nested_blocks_msg: + self.add_message('too-many-nested-blocks', + node=self._nested_blocks_msg[0], + args=self._nested_blocks_msg[1]) + self._nested_blocks_msg = None + + def _check_nested_blocks(self, node): + """Update and check the number of nested blocks + """ + # only check block levels inside functions or methods + if not isinstance(node.scope(), astroid.FunctionDef): + return + # messages are triggered on leaving the nested block. Here we save the + # stack in case the current node isn't nested in the previous one + nested_blocks = self._nested_blocks[:] + if node.parent == node.scope(): + self._nested_blocks = [node] + else: + # go through ancestors from the most nested to the less + for ancestor_node in reversed(self._nested_blocks): + if ancestor_node == node.parent: + break + self._nested_blocks.pop() + # if the node is a elif, this should not be another nesting level + if isinstance(node, astroid.If) and self._elifs[self._if_counter]: + if self._nested_blocks: + self._nested_blocks.pop() + self._nested_blocks.append(node) + # send message only once per group of nested blocks + if len(nested_blocks) > self.config.max_nested_blocks: + if len(nested_blocks) > len(self._nested_blocks): + self.add_message('too-many-nested-blocks', node=nested_blocks[0], + args=(len(nested_blocks), + self.config.max_nested_blocks)) + self._nested_blocks_msg = None + else: + # if time has not come yet to send the message (ie the stack of + # nested nodes is still increasing), save it in case the + # current node is the last one of the function + self._nested_blocks_msg = (self._nested_blocks[0], + (len(self._nested_blocks), + self.config.max_nested_blocks)) + +class NotChecker(_BasicChecker): + """checks for too many not in comparison expressions + + - "not not" should trigger a warning + - "not" followed by a comparison should trigger a warning + """ + msgs = {'C0113': ('Consider changing "%s" to "%s"', + 'unneeded-not', + 'Used when a boolean expression contains an unneeded ' + 'negation.'), + } + + reverse_op = {'<': '>=', '<=': '>', '>': '<=', '>=': '<', '==': '!=', + '!=': '==', 'in': 'not in', 'is': 'is not'} + # sets are not ordered, so for example "not set(LEFT_VALS) <= set(RIGHT_VALS)" is + # not equivalent to "set(LEFT_VALS) > set(RIGHT_VALS)" + skipped_nodes = (astroid.Set, ) + # 'builtins' py3, '__builtin__' py2 + skipped_classnames = ['%s.%s' % (six.moves.builtins.__name__, qname) + for qname in ('set', 'frozenset')] + + @check_messages('unneeded-not') + def visit_unaryop(self, node): + if node.op != 'not': + return + operand = node.operand + + if isinstance(operand, astroid.UnaryOp) and operand.op == 'not': + self.add_message('unneeded-not', node=node, + args=(node.as_string(), + operand.operand.as_string())) + elif isinstance(operand, astroid.Compare): + left = operand.left + # ignore multiple comparisons + if len(operand.ops) > 1: + return + operator, right = operand.ops[0] + if operator not in self.reverse_op: + return + # Ignore __ne__ as function of __eq__ + frame = node.frame() + if frame.name == '__ne__' and operator == '==': + return + for _type in (_node_type(left), _node_type(right)): + if not _type: + return + if isinstance(_type, self.skipped_nodes): + return + if (isinstance(_type, astroid.Instance) and + _type.qname() in self.skipped_classnames): + return + suggestion = '%s %s %s' % (left.as_string(), + self.reverse_op[operator], + right.as_string()) + self.add_message('unneeded-not', node=node, + args=(node.as_string(), suggestion)) + + +class MultipleTypesChecker(BaseChecker): + """Checks for variable type redefinitions (NoneType excepted) + + At a function, method, class or module scope + + This rule could be improved: + - Currently, if an attribute is set to different types in 2 methods of a + same class, it won't be detected (see functional test) + - One could improve the support for inference on assignment with tuples, + ifexpr, etc. Also it would be great to have support for inference on + str.split() + """ + __implements__ = IAstroidChecker + + name = 'multiple_types' + msgs = {'R0204': ('Redefinition of %s type from %s to %s', + 'redefined-variable-type', + 'Used when the type of a variable changes inside a ' + 'method or a function.' + ), + } + + def visit_classdef(self, _): + self._assigns.append({}) + + @check_messages('redefined-variable-type') + def leave_classdef(self, _): + self._check_and_add_messages() + + visit_functiondef = visit_classdef + leave_functiondef = leave_module = leave_classdef + + def visit_module(self, _): + self._assigns = [{}] + + def _check_and_add_messages(self): + assigns = self._assigns.pop() + for name, args in assigns.items(): + if len(args) <= 1: + continue + _, orig_type = args[0] + # Check if there is a type in the following nodes that would be + # different from orig_type. + for redef_node, redef_type in args[1:]: + if redef_type != orig_type: + orig_type = orig_type.replace(BUILTINS + ".", '') + redef_type = redef_type.replace(BUILTINS + ".", '') + self.add_message('redefined-variable-type', node=redef_node, + args=(name, orig_type, redef_type)) + break + + def visit_assign(self, node): + # we don't handle multiple assignment nor slice assignment + target = node.targets[0] + if isinstance(target, (astroid.Tuple, astroid.Subscript)): + return + # ignore NoneType + if _is_none(node): + return + _type = _node_type(node.value) + if _type: + self._assigns[-1].setdefault(target.as_string(), []).append( + (node, _type.pytype())) + + def register(linter): """required method to auto register this checker""" linter.register_checker(BasicErrorChecker(linter)) @@ -1234,3 +2003,8 @@ def register(linter): linter.register_checker(DocStringChecker(linter)) linter.register_checker(PassChecker(linter)) linter.register_checker(LambdaForComprehensionChecker(linter)) + linter.register_checker(ComparisonChecker(linter)) + linter.register_checker(NotChecker(linter)) + linter.register_checker(RecommandationChecker(linter)) + linter.register_checker(ElifChecker(linter)) + linter.register_checker(MultipleTypesChecker(linter)) diff --git a/pymode/libs/pylint/checkers/classes.py b/pymode/libs/pylint/checkers/classes.py index 87e3bcfe..373dfff0 100644 --- a/pymode/libs/pylint/checkers/classes.py +++ b/pymode/libs/pylint/checkers/classes.py @@ -20,30 +20,68 @@ import sys from collections import defaultdict +import six + import astroid -from astroid import YES, Instance, are_exclusive, AssAttr, Class from astroid.bases import Generator, BUILTINS -from astroid.inference import InferenceContext - +from astroid.exceptions import InconsistentMroError, DuplicateBasesError +from astroid import objects +from astroid.scoped_nodes import function_to_method from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker from pylint.checkers.utils import ( - PYMETHODS, overrides_a_method, check_messages, is_attr_private, - is_attr_protected, node_frame_class, safe_infer, is_builtin_object, - decorated_with_property, unimplemented_abstract_methods) -import six + PYMETHODS, SPECIAL_METHODS_PARAMS, + overrides_a_method, check_messages, is_attr_private, + is_attr_protected, node_frame_class, is_builtin_object, + decorated_with_property, unimplemented_abstract_methods, + decorated_with, class_is_abstract, + safe_infer, has_known_bases) +from pylint.utils import deprecated_option, get_global_option + if sys.version_info >= (3, 0): NEXT_METHOD = '__next__' else: NEXT_METHOD = 'next' ITER_METHODS = ('__iter__', '__getitem__') +INVALID_BASE_CLASSES = {'bool', 'range', 'slice', 'memoryview'} + + +def _get_method_args(method): + args = method.args.args + if method.type in ('classmethod', 'method'): + return len(args) - 1 + return len(args) + + +def _is_invalid_base_class(cls): + return cls.name in INVALID_BASE_CLASSES and is_builtin_object(cls) + + +def _has_data_descriptor(cls, attr): + attributes = cls.getattr(attr) + for attribute in attributes: + try: + for inferred in attribute.infer(): + if isinstance(inferred, astroid.Instance): + try: + inferred.getattr('__get__') + inferred.getattr('__set__') + except astroid.NotFoundError: + continue + else: + return True + except astroid.InferenceError: + # Can't infer, avoid emitting a false positive in this case. + return True + return False + def _called_in_methods(func, klass, methods): """ Check if the func was called in any of the given methods, belonging to the *klass*. Returns True if so, False otherwise. """ - if not isinstance(func, astroid.Function): + if not isinstance(func, astroid.FunctionDef): return False for method in methods: try: @@ -51,7 +89,7 @@ def _called_in_methods(func, klass, methods): except astroid.NotFoundError: continue for infer_method in infered: - for callfunc in infer_method.nodes_of_class(astroid.CallFunc): + for callfunc in infer_method.nodes_of_class(astroid.Call): try: bound = next(callfunc.func.infer()) except (astroid.InferenceError, StopIteration): @@ -65,15 +103,6 @@ def _called_in_methods(func, klass, methods): return True return False -def class_is_abstract(node): - """return true if the given class node should be considered as an abstract - class - """ - for method in node.methods(): - if method.parent.frame() is node: - if method.is_abstract(pass_is_abstract=False): - return True - return False def _is_attribute_property(name, klass): """ Check if the given attribute *name* is a property @@ -96,13 +125,43 @@ def _is_attribute_property(name, klass): infered = next(attr.infer()) except astroid.InferenceError: continue - if (isinstance(infered, astroid.Function) and + if (isinstance(infered, astroid.FunctionDef) and decorated_with_property(infered)): return True if infered.pytype() == property_name: return True return False +def _has_bare_super_call(fundef_node): + for call in fundef_node.nodes_of_class(astroid.Call): + func = call.func + if (isinstance(func, astroid.Name) and + func.name == 'super' and + not call.args): + return True + return False + +def _safe_infer_call_result(node, caller, context=None): + """ + Safely infer the return value of a function. + + Returns None if inference failed or if there is some ambiguity (more than + one node has been inferred). Otherwise returns infered value. + """ + try: + inferit = node.infer_call_result(caller, context=context) + value = next(inferit) + except astroid.InferenceError: + return # inference failed + except StopIteration: + return # no values infered + try: + next(inferit) + return # there is ambiguity on the inferred node + except astroid.InferenceError: + return # there is some kind of ambiguity + except StopIteration: + return value MSGS = { 'F0202': ('Unable to check methods signature (%s / %s)', @@ -170,15 +229,6 @@ def _is_attribute_property(name, klass): 'Used when a method doesn\'t use its bound instance, and so could ' 'be written as a function.' ), - - 'E0221': ('Interface resolved to %s is not a class', - 'interface-is-not-class', - 'Used when a class claims to implement an interface which is not ' - 'a class.'), - 'E0222': ('Missing method %r from %s interface', - 'missing-interface-method', - 'Used when a method declared in an interface is missing from a ' - 'class implementing this interface'), 'W0221': ('Arguments number differs from %s %r method', 'arguments-differ', 'Used when a method has a different number of arguments than in ' @@ -192,12 +242,6 @@ def _is_attribute_property(name, klass): 'Used when an abstract method (i.e. raise NotImplementedError) is ' 'not overridden in concrete class.' ), - 'F0220': ('failed to resolve interfaces implemented by %s (%s)', - 'unresolved-interface', - 'Used when a Pylint as failed to find interfaces implemented by ' - ' a class'), - - 'W0231': ('__init__ method from base class %r is not called', 'super-init-not-called', 'Used when an ancestor class method has an __init__ method ' @@ -210,15 +254,6 @@ def _is_attribute_property(name, klass): 'non-parent-init-called', 'Used when an __init__ method is called on a class which is not ' 'in the direct ancestors for the analysed class.'), - 'W0234': ('__iter__ returns non-iterator', - 'non-iterator-returned', - 'Used when an __iter__ method returns something which is not an ' - 'iterable (i.e. has no `%s` method)' % NEXT_METHOD), - 'E0235': ('__exit__ must accept 3 arguments: type, value, traceback', - 'bad-context-manager', - 'Used when the __exit__ special method, belonging to a ' - 'context manager, does not accept 3 arguments ' - '(type, value, traceback).'), 'E0236': ('Invalid object %r in __slots__, must contain ' 'only non empty strings', 'invalid-slots-object', @@ -235,8 +270,20 @@ def _is_attribute_property(name, klass): 'inherit-non-class', 'Used when a class inherits from something which is not a ' 'class.'), - - + 'E0240': ('Inconsistent method resolution order for class %r', + 'inconsistent-mro', + 'Used when a class has an inconsistent method resolutin order.'), + 'E0241': ('Duplicate bases for class %r', + 'duplicate-bases', + 'Used when a class has duplicate bases.'), + 'R0202': ('Consider using a decorator instead of calling classmethod', + 'no-classmethod-decorator', + 'Used when a class method is defined without using the decorator ' + 'syntax.'), + 'R0203': ('Consider using a decorator instead of calling staticmethod', + 'no-staticmethod-decorator', + 'Used when a static method is defined without using the decorator ' + 'syntax.'), } @@ -246,7 +293,6 @@ class ClassChecker(BaseChecker): * overridden methods signature * access only to existent members via self * attributes not defined in the __init__ method - * supported interfaces implementation * unreachable code """ @@ -259,21 +305,10 @@ class ClassChecker(BaseChecker): 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' : '', - '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.'} + # TODO(cpopa): remove this in Pylint 1.6. + deprecated_option(opt_type="csv", + help_msg="This is deprecated, because " + "it is not used anymore.") ), ('defining-attr-methods', {'default' : ('__init__', '__new__', 'setUp'), @@ -313,22 +348,33 @@ def __init__(self, linter=None): self._first_attrs = [] self._meth_could_be_func = None - def visit_class(self, node): - """init visit variable _accessed and check interfaces + def visit_classdef(self, node): + """init visit variable _accessed """ self._accessed.append(defaultdict(list)) self._check_bases_classes(node) - self._check_interfaces(node) - # if not an interface, exception, metaclass - if node.type == 'class': + # if not an exception or a metaclass + if node.type == 'class' and has_known_bases(node): try: node.local_attr('__init__') except astroid.NotFoundError: self.add_message('no-init', args=node, node=node) self._check_slots(node) self._check_proper_bases(node) + self._check_consistent_mro(node) + + def _check_consistent_mro(self, node): + """Detect that a class has a consistent mro or duplicate bases.""" + try: + node.mro() + except InconsistentMroError: + self.add_message('inconsistent-mro', args=node.name, node=node) + except DuplicateBasesError: + self.add_message('duplicate-bases', args=node.name, node=node) + except NotImplementedError: + # Old style class, there's no mro so don't do anything. + pass - @check_messages('inherit-non-class') def _check_proper_bases(self, node): """ Detect that a class inherits something which is not @@ -336,23 +382,31 @@ def _check_proper_bases(self, node): """ for base in node.bases: ancestor = safe_infer(base) - if ancestor in (YES, None): + if ancestor in (astroid.YES, None): continue if (isinstance(ancestor, astroid.Instance) and ancestor.is_subtype_of('%s.type' % (BUILTINS,))): continue - if not isinstance(ancestor, astroid.Class): + + if (not isinstance(ancestor, astroid.ClassDef) or + _is_invalid_base_class(ancestor)): self.add_message('inherit-non-class', args=base.as_string(), node=node) - @check_messages('access-member-before-definition', - 'attribute-defined-outside-init') - def leave_class(self, cnode): + def leave_classdef(self, cnode): """close a class node: check that instance attributes are defined in __init__ and check access to existent members """ # check access to existent members on non metaclass classes + ignore_mixins = get_global_option(self, 'ignore-mixin-members', + default=True) + if ignore_mixins and cnode.name[-5:].lower() == 'mixin': + # We are in a mixin class. No need to try to figure out if + # something is missing, since it is most likely that it will + # miss. + return + accessed = self._accessed.pop() if cnode.type != 'metaclass': self._check_accessed_members(cnode, accessed) @@ -400,7 +454,7 @@ def leave_class(self, cnode): self.add_message('attribute-defined-outside-init', args=attr, node=node) - def visit_function(self, node): + def visit_functiondef(self, node): """check method arguments, overriding""" # ignore actual functions if not node.is_method(): @@ -422,13 +476,13 @@ def visit_function(self, node): # dictionary. # This may happen with astroid build from living objects continue - if not isinstance(meth_node, astroid.Function): + if not isinstance(meth_node, astroid.FunctionDef): continue - self._check_signature(node, meth_node, 'overridden') + self._check_signature(node, meth_node, 'overridden', klass) break if node.decorators: for decorator in node.decorators.nodes: - if isinstance(decorator, astroid.Getattr) and \ + if isinstance(decorator, astroid.Attribute) and \ decorator.attrname in ('getter', 'setter', 'deleter'): # attribute affectation will call this method, not hiding it return @@ -440,21 +494,17 @@ def visit_function(self, node): try: overridden = klass.instance_attr(node.name)[0] # XXX overridden_frame = overridden.frame() - if (isinstance(overridden_frame, astroid.Function) + if (isinstance(overridden_frame, astroid.FunctionDef) and overridden_frame.type == 'method'): overridden_frame = overridden_frame.parent.frame() - if (isinstance(overridden_frame, Class) + if (isinstance(overridden_frame, astroid.ClassDef) and klass.is_subtype_of(overridden_frame.qname())): args = (overridden.root().name, overridden.fromlineno) self.add_message('method-hidden', args=args, node=node) except astroid.NotFoundError: pass - # check non-iterators in __iter__ - if node.name == '__iter__': - self._check_iter(node) - elif node.name == '__exit__': - self._check_exit(node) + visit_asyncfunctiondef = visit_functiondef def _check_slots(self, node): if '__slots__' not in node.locals: @@ -482,7 +532,7 @@ def _check_slots(self, node): values = [item[0] for item in slots.items] else: values = slots.itered() - if values is YES: + if values is astroid.YES: return for elt in values: @@ -493,7 +543,7 @@ def _check_slots(self, node): def _check_slots_elt(self, elt): for infered in elt.infer(): - if infered is YES: + if infered is astroid.YES: continue if (not isinstance(infered, astroid.Const) or not isinstance(infered.value, six.string_types)): @@ -506,39 +556,11 @@ def _check_slots_elt(self, elt): args=infered.as_string(), node=elt) - def _check_iter(self, node): - try: - infered = node.infer_call_result(node) - except astroid.InferenceError: - return - - for infered_node in infered: - if (infered_node is YES - or isinstance(infered_node, Generator)): - continue - if isinstance(infered_node, astroid.Instance): - try: - infered_node.local_attr(NEXT_METHOD) - except astroid.NotFoundError: - self.add_message('non-iterator-returned', - node=node) - break - - def _check_exit(self, node): - positional = sum(1 for arg in node.args.args if arg.name != 'self') - if positional < 3 and not node.args.vararg: - self.add_message('bad-context-manager', - node=node) - elif positional > 3: - self.add_message('bad-context-manager', - node=node) - - def leave_function(self, node): + def leave_functiondef(self, node): """on method node, check if this method couldn't be a function ignore class, static and abstract methods, initializer, - methods overridden from a parent class and any - kind of method defined in an interface for this warning + methods overridden from a parent class. """ if node.is_method(): if node.args.args is not None: @@ -547,13 +569,14 @@ def leave_function(self, node): return class_node = node.parent.frame() if (self._meth_could_be_func and node.type == 'method' - and not node.name in PYMETHODS + and node.name not in PYMETHODS and not (node.is_abstract() or - overrides_a_method(class_node, node.name)) - and class_node.type != 'interface'): + overrides_a_method(class_node, node.name) or + decorated_with_property(node) or + (six.PY3 and _has_bare_super_call(node)))): self.add_message('no-self-use', node=node) - def visit_getattr(self, node): + def visit_attribute(self, node): """check if the getattr is an access to a class member if so, register it. Also check for access to protected class member from outside its class (but ignore __special__ @@ -569,8 +592,8 @@ class member from outside its class (but ignore __special__ self._check_protected_attribute_access(node) - def visit_assattr(self, node): - if isinstance(node.ass_type(), astroid.AugAssign) and self.is_first_attr(node): + def visit_assignattr(self, node): + if isinstance(node.assign_type(), astroid.AugAssign) and self.is_first_attr(node): self._accessed[-1][node.attrname].append(node) self._check_in_slots(node) @@ -579,7 +602,7 @@ def _check_in_slots(self, node): is defined in the class slots. """ infered = safe_infer(node.expr) - if infered and isinstance(infered, Instance): + if infered and isinstance(infered, astroid.Instance): klass = infered._proxied if '__slots__' not in klass.locals or not klass.newstyle: return @@ -602,20 +625,60 @@ def _check_in_slots(self, node): # Properties circumvent the slots mechanism, # so we should not emit a warning for them. return + if (node.attrname in klass.locals + and _has_data_descriptor(klass, node.attrname)): + # Descriptors circumvent the slots mechanism as well. + return self.add_message('assigning-non-slot', args=(node.attrname, ), node=node) - @check_messages('protected-access') + @check_messages('protected-access', 'no-classmethod-decorator', + 'no-staticmethod-decorator') def visit_assign(self, assign_node): + self._check_classmethod_declaration(assign_node) node = assign_node.targets[0] - if not isinstance(node, AssAttr): + if not isinstance(node, astroid.AssignAttr): return if self.is_first_attr(node): return - self._check_protected_attribute_access(node) + def _check_classmethod_declaration(self, node): + """Checks for uses of classmethod() or staticmethod() + + When a @classmethod or @staticmethod decorator should be used instead. + A message will be emitted only if the assignment is at a class scope + and only if the classmethod's argument belongs to the class where it + is defined. + `node` is an assign node. + """ + if not isinstance(node.value, astroid.Call): + return + + # check the function called is "classmethod" or "staticmethod" + func = node.value.func + if (not isinstance(func, astroid.Name) or + func.name not in ('classmethod', 'staticmethod')): + return + + msg = ('no-classmethod-decorator' if func.name == 'classmethod' else + 'no-staticmethod-decorator') + # assignment must be at a class scope + parent_class = node.scope() + if not isinstance(parent_class, astroid.ClassDef): + return + + # Check if the arg passed to classmethod is a class member + classmeth_arg = node.value.args[0] + if not isinstance(classmeth_arg, astroid.Name): + return + + method_name = classmeth_arg.name + if any(method_name == member.name + for member in parent_class.mymethods()): + self.add_message(msg, node=node.targets[0]) + def _check_protected_attribute_access(self, node): '''Given an attribute access node (set or get), check if attribute access is legitimate. Call _check_first_attr with node before calling @@ -644,7 +707,7 @@ def _check_protected_attribute_access(self, node): return # If the expression begins with a call to super, that's ok. - if isinstance(node.expr, astroid.CallFunc) and \ + if isinstance(node.expr, astroid.Call) and \ isinstance(node.expr.func, astroid.Name) and \ node.expr.func.name == 'super': return @@ -659,16 +722,13 @@ def _check_protected_attribute_access(self, node): # b = property(lambda: self._b) stmt = node.parent.statement() - try: - if (isinstance(stmt, astroid.Assign) and - (stmt in klass.body or klass.parent_of(stmt)) and - isinstance(stmt.value, astroid.CallFunc) and - isinstance(stmt.value.func, astroid.Name) and - stmt.value.func.name == 'property' and - is_builtin_object(next(stmt.value.func.infer(), None))): + if (isinstance(stmt, astroid.Assign) + and len(stmt.targets) == 1 + and isinstance(stmt.targets[0], astroid.AssignName)): + name = stmt.targets[0].name + if _is_attribute_property(name, klass): return - except astroid.InferenceError: - pass + self.add_message('protected-access', node=node, args=attrname) def visit_name(self, node): @@ -682,9 +742,8 @@ def visit_name(self, node): 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 + excs = ('AttributeError', 'Exception', 'BaseException') for attr, nodes in six.iteritems(accessed): - # deactivate "except doesn't do anything", that's expected - # pylint: disable=W0704 try: # is it a class attribute ? node.local_attr(attr) @@ -726,8 +785,7 @@ def _check_accessed_members(self, node, accessed): lno = defstmt.fromlineno for _node in nodes: if _node.frame() is frame and _node.fromlineno < lno \ - and not are_exclusive(_node.statement(), defstmt, - ('AttributeError', 'Exception', 'BaseException')): + and not astroid.are_exclusive(_node.statement(), defstmt, excs): self.add_message('access-member-before-definition', node=_node, args=(attr, lno)) @@ -823,55 +881,6 @@ def is_abstract(method): self.add_message('abstract-method', node=node, args=(name, owner.name)) - def _check_interfaces(self, node): - """check that the given class node really implements declared - interfaces - """ - e0221_hack = [False] - def iface_handler(obj): - """filter interface objects, it should be classes""" - if not isinstance(obj, astroid.Class): - e0221_hack[0] = True - self.add_message('interface-is-not-class', node=node, - args=(obj.as_string(),)) - return False - return True - ignore_iface_methods = self.config.ignore_iface_methods - try: - for iface in node.interfaces(handler_func=iface_handler): - for imethod in iface.methods(): - name = imethod.name - if name.startswith('_') or name in ignore_iface_methods: - # don't check method beginning with an underscore, - # usually belonging to the interface implementation - continue - # get class method astroid - try: - method = node_method(node, name) - except astroid.NotFoundError: - self.add_message('missing-interface-method', - args=(name, iface.name), - node=node) - continue - # ignore inherited methods - if method.parent.frame() is not node: - continue - # check signature - self._check_signature(method, imethod, - '%s interface' % iface.name) - except astroid.InferenceError: - if e0221_hack[0]: - return - implements = Instance(node).getattr('__implements__')[0] - assignment = implements.parent - assert isinstance(assignment, astroid.Assign) - # assignment.expr can be a Name or a Tuple or whatever. - # Use as_string() for the message - # FIXME: in case of multiple interfaces, find which one could not - # be resolved - self.add_message('unresolved-interface', node=implements, - args=(node.name, assignment.value.as_string())) - def _check_init(self, node): """check that the __init__ method call super or ancestors'__init__ method @@ -882,19 +891,19 @@ def _check_init(self, node): klass_node = node.parent.frame() to_call = _ancestors_to_call(klass_node) not_called_yet = dict(to_call) - for stmt in node.nodes_of_class(astroid.CallFunc): + for stmt in node.nodes_of_class(astroid.Call): expr = stmt.func - if not isinstance(expr, astroid.Getattr) \ + if not isinstance(expr, astroid.Attribute) \ or expr.attrname != '__init__': continue # skip the test if using super - if isinstance(expr.expr, astroid.CallFunc) and \ + if isinstance(expr.expr, astroid.Call) and \ isinstance(expr.expr.func, astroid.Name) and \ expr.expr.func.name == 'super': return try: for klass in expr.expr.infer(): - if klass is YES: + if klass is astroid.YES: continue # The infered klass can be super(), which was # assigned to a variable and the `__init__` @@ -904,10 +913,12 @@ def _check_init(self, node): # base.__init__(...) if (isinstance(klass, astroid.Instance) and - isinstance(klass._proxied, astroid.Class) and + isinstance(klass._proxied, astroid.ClassDef) and is_builtin_object(klass._proxied) and klass._proxied.name == 'super'): return + elif isinstance(klass, objects.Super): + return try: del not_called_yet[klass] except KeyError: @@ -921,25 +932,39 @@ def _check_init(self, node): continue self.add_message('super-init-not-called', args=klass.name, node=node) - def _check_signature(self, method1, refmethod, class_type): + def _check_signature(self, method1, refmethod, class_type, cls): """check that the signature of the two given methods match - - class_type is in 'class', 'interface' """ - if not (isinstance(method1, astroid.Function) - and isinstance(refmethod, astroid.Function)): + if not (isinstance(method1, astroid.FunctionDef) + and isinstance(refmethod, astroid.FunctionDef)): self.add_message('method-check-failed', args=(method1, refmethod), node=method1) return - # don't care about functions with unknown argument (builtins) + + instance = cls.instanciate_class() + method1 = function_to_method(method1, instance) + refmethod = function_to_method(refmethod, instance) + + # Don't care about functions with unknown argument (builtins). if method1.args.args is None or refmethod.args.args is None: return - # if we use *args, **kwargs, skip the below checks + # If we use *args, **kwargs, skip the below checks. if method1.args.vararg or method1.args.kwarg: return + # Ignore private to class methods. if is_attr_private(method1.name): return - if len(method1.args.args) != len(refmethod.args.args): + # Ignore setters, they have an implicit extra argument, + # which shouldn't be taken in consideration. + if method1.decorators: + for decorator in method1.decorators.nodes: + if (isinstance(decorator, astroid.Attribute) and + decorator.attrname == 'setter'): + return + + method1_args = _get_method_args(method1) + refmethod_args = _get_method_args(refmethod) + if method1_args != refmethod_args: self.add_message('arguments-differ', args=(class_type, method1.name), node=method1) @@ -955,6 +980,118 @@ def is_first_attr(self, node): return self._first_attrs and isinstance(node.expr, astroid.Name) and \ node.expr.name == self._first_attrs[-1] + +class SpecialMethodsChecker(BaseChecker): + """Checker which verifies that special methods + are implemented correctly. + """ + __implements__ = (IAstroidChecker, ) + name = 'classes' + msgs = { + 'E0301': ('__iter__ returns non-iterator', + 'non-iterator-returned', + 'Used when an __iter__ method returns something which is not an ' + 'iterable (i.e. has no `%s` method)' % NEXT_METHOD, + {'old_names': [('W0234', 'non-iterator-returned'), + ('E0234', 'non-iterator-returned')]}), + 'E0302': ('The special method %r expects %s param(s), %d %s given', + 'unexpected-special-method-signature', + 'Emitted when a special method was defined with an ' + 'invalid number of parameters. If it has too few or ' + 'too many, it might not work at all.', + {'old_names': [('E0235', 'bad-context-manager')]}), + } + priority = -2 + + @check_messages('unexpected-special-method-signature', + 'non-iterator-returned') + def visit_functiondef(self, node): + if not node.is_method(): + return + if node.name == '__iter__': + self._check_iter(node) + if node.name in PYMETHODS: + self._check_unexpected_method_signature(node) + + visit_asyncfunctiondef = visit_functiondef + + def _check_unexpected_method_signature(self, node): + expected_params = SPECIAL_METHODS_PARAMS[node.name] + + if expected_params is None: + # This can support a variable number of parameters. + return + if not len(node.args.args) and not node.args.vararg: + # Method has no parameter, will be catched + # by no-method-argument. + return + + if decorated_with(node, [BUILTINS + ".staticmethod"]): + # We expect to not take in consideration self. + all_args = node.args.args + else: + all_args = node.args.args[1:] + mandatory = len(all_args) - len(node.args.defaults) + optional = len(node.args.defaults) + current_params = mandatory + optional + + if isinstance(expected_params, tuple): + # The expected number of parameters can be any value from this + # tuple, although the user should implement the method + # to take all of them in consideration. + emit = mandatory not in expected_params + expected_params = "between %d or %d" % expected_params + else: + # If the number of mandatory parameters doesn't + # suffice, the expected parameters for this + # function will be deduced from the optional + # parameters. + rest = expected_params - mandatory + if rest == 0: + emit = False + elif rest < 0: + emit = True + elif rest > 0: + emit = not ((optional - rest) >= 0 or node.args.vararg) + + if emit: + verb = "was" if current_params <= 1 else "were" + self.add_message('unexpected-special-method-signature', + args=(node.name, expected_params, current_params, verb), + node=node) + + @staticmethod + def _is_iterator(node): + if node is astroid.YES: + # Just ignore YES objects. + return True + if isinstance(node, Generator): + # Generators can be itered. + return True + + if isinstance(node, astroid.Instance): + try: + node.local_attr(NEXT_METHOD) + return True + except astroid.NotFoundError: + pass + elif isinstance(node, astroid.ClassDef): + metaclass = node.metaclass() + if metaclass and isinstance(metaclass, astroid.ClassDef): + try: + metaclass.local_attr(NEXT_METHOD) + return True + except astroid.NotFoundError: + pass + return False + + def _check_iter(self, node): + infered = _safe_infer_call_result(node, node) + if infered is not None: + if not self._is_iterator(infered): + self.add_message('non-iterator-returned', node=node) + + 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 @@ -972,11 +1109,12 @@ def node_method(node, method_name): """get astroid for on the given class node, ensuring it is a Function node """ - for n in node.local_attr(method_name): - if isinstance(n, astroid.Function): - return n + for node_attr in node.local_attr(method_name): + if isinstance(node_attr, astroid.Function): + return node_attr raise astroid.NotFoundError(method_name) def register(linter): """required method to auto register this checker """ linter.register_checker(ClassChecker(linter)) + linter.register_checker(SpecialMethodsChecker(linter)) diff --git a/pymode/libs/pylint/checkers/design_analysis.py b/pymode/libs/pylint/checkers/design_analysis.py index 9ff10bf3..3a96c510 100644 --- a/pymode/libs/pylint/checkers/design_analysis.py +++ b/pymode/libs/pylint/checkers/design_analysis.py @@ -18,7 +18,7 @@ import re from collections import defaultdict -from astroid import If, InferenceError +from astroid import If, BoolOp from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker @@ -64,12 +64,27 @@ 'too-many-statements', 'Used when a function or method has too many statements. You \ should then split it in smaller functions / methods.'), - 'R0923': ('Interface not implemented', - 'interface-not-implemented', - 'Used when an interface class is not implemented anywhere.'), + 'R0916': ('Too many boolean expressions in if statement (%s/%s)', + 'too-many-boolean-expressions', + 'Used when a if statement contains too many boolean ' + 'expressions'), } +def _count_boolean_expressions(bool_op): + """Counts the number of boolean expressions in BoolOp `bool_op` (recursive) + + example: a and (b or c or (d and e)) ==> 5 boolean expressions + """ + nb_bool_expr = 0 + for bool_expr in bool_op.get_children(): + if isinstance(bool_expr, BoolOp): + nb_bool_expr += _count_boolean_expressions(bool_expr) + else: + nb_bool_expr += 1 + return nb_bool_expr + + class MisdesignChecker(BaseChecker): """checks for sign of poor/misdesign: * number of methods, attributes, local variables... @@ -139,6 +154,13 @@ class MisdesignChecker(BaseChecker): 'help' : 'Maximum number of public methods for a class \ (see R0904).'} ), + ('max-bool-expr', + {'default': 5, + 'type': 'int', + 'metavar': '', + 'help': 'Maximum number of boolean expressions in a if ' + 'statement'} + ), ) def __init__(self, linter=None): @@ -146,8 +168,6 @@ def __init__(self, linter=None): self.stats = None self._returns = None self._branches = None - self._used_ifaces = None - self._ifaces = None self._stmts = 0 def open(self): @@ -155,19 +175,10 @@ def open(self): self.stats = self.linter.add_stats() self._returns = [] self._branches = defaultdict(int) - self._used_ifaces = {} - self._ifaces = [] - - def close(self): - """check that interface classes are used""" - for iface in self._ifaces: - if not iface in self._used_ifaces: - self.add_message('interface-not-implemented', node=iface) @check_messages('too-many-ancestors', 'too-many-instance-attributes', - 'too-few-public-methods', 'too-many-public-methods', - 'interface-not-implemented') - def visit_class(self, node): + 'too-few-public-methods', 'too-many-public-methods') + def visit_classdef(self, node): """check size of inheritance hierarchy and number of instance attributes """ # Is the total inheritance hierarchy is 7 or less? @@ -182,22 +193,9 @@ def visit_class(self, node): self.add_message('too-many-instance-attributes', node=node, args=(len(node.instance_attrs), self.config.max_attributes)) - # update interface classes structures - if 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 @check_messages('too-few-public-methods', 'too-many-public-methods') - def leave_class(self, node): + def leave_classdef(self, node): """check number of public methods""" my_methods = sum(1 for method in node.mymethods() if not method.name.startswith('_')) @@ -230,7 +228,7 @@ def leave_class(self, node): @check_messages('too-many-return-statements', 'too-many-branches', 'too-many-arguments', 'too-many-locals', 'too-many-statements') - def visit_function(self, node): + def visit_functiondef(self, node): """check function name, docstring, arguments, redefinition, variable names, max locals """ @@ -256,10 +254,12 @@ def visit_function(self, node): # init statements counter self._stmts = 1 + visit_asyncfunctiondef = visit_functiondef + @check_messages('too-many-return-statements', 'too-many-branches', 'too-many-arguments', 'too-many-locals', 'too-many-statements') - def leave_function(self, node): + def leave_functiondef(self, node): """most of the work is done here on close: checks for max returns, branch, return in __init__ """ @@ -276,6 +276,8 @@ def leave_function(self, node): self.add_message('too-many-statements', node=node, args=(self._stmts, self.config.max_statements)) + leave_asyncfunctiondef = leave_functiondef + def visit_return(self, _): """count number of returns""" if not self._returns: @@ -302,8 +304,10 @@ def visit_tryfinally(self, node): self._inc_branch(node, 2) self._stmts += 2 + @check_messages('too-many-boolean-expressions') def visit_if(self, node): - """increments the branches counter""" + """increments the branches counter and checks boolean expressions""" + self._check_boolean_expressions(node) branches = 1 # don't double count If nodes coming from some 'elif' if node.orelse and (len(node.orelse) > 1 or @@ -312,6 +316,19 @@ def visit_if(self, node): self._inc_branch(node, branches) self._stmts += branches + def _check_boolean_expressions(self, node): + """Go through "if" node `node` and counts its boolean expressions + + if the "if" node test is a BoolOp node + """ + condition = node.test + if not isinstance(condition, BoolOp): + return + nb_bool_expr = _count_boolean_expressions(condition) + if nb_bool_expr > self.config.max_bool_expr: + self.add_message('too-many-boolean-expressions', node=condition, + args=(nb_bool_expr, self.config.max_bool_expr)) + def visit_while(self, node): """increments the branches counter""" branches = 1 diff --git a/pymode/libs/pylint/checkers/exceptions.py b/pymode/libs/pylint/checkers/exceptions.py index 88a8f225..e60f69b9 100644 --- a/pymode/libs/pylint/checkers/exceptions.py +++ b/pymode/libs/pylint/checkers/exceptions.py @@ -14,22 +14,30 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """exceptions handling (raising, catching, exceptions classes) checker """ +import inspect import sys -import astroid -from astroid import YES, Instance, unpack_infer, List, Tuple -from logilab.common.compat import builtins +import six +from six.moves import builtins +import astroid from pylint.checkers import BaseChecker from pylint.checkers.utils import ( - is_empty, is_raising, check_messages, inherit_from_std_ex, EXCEPTIONS_MODULE, - has_known_bases, - safe_infer) -from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE + safe_infer, + has_known_bases) +from pylint.interfaces import IAstroidChecker + + +def _builtin_exceptions(): + def predicate(obj): + return isinstance(obj, type) and issubclass(obj, BaseException) + + members = inspect.getmembers(six.moves.builtins, predicate) + return {exc.__name__ for (_, exc) in members} def _annotated_unpack_infer(stmt, context=None): @@ -39,14 +47,14 @@ def _annotated_unpack_infer(stmt, context=None): Returns an iterator which yields tuples in the format ('original node', 'infered node'). """ - if isinstance(stmt, (List, Tuple)): + if isinstance(stmt, (astroid.List, astroid.Tuple)): for elt in stmt.elts: inferred = safe_infer(elt) - if inferred and inferred is not YES: + if inferred and inferred is not astroid.YES: yield elt, inferred return for infered in stmt.infer(context): - if infered is YES: + if infered is astroid.YES: continue yield stmt, infered @@ -54,6 +62,7 @@ def _annotated_unpack_infer(stmt, context=None): PY3K = sys.version_info >= (3, 0) OVERGENERAL_EXCEPTIONS = ('Exception',) BUILTINS_NAME = builtins.__name__ + MSGS = { 'E0701': ('Bad except clauses order (%s)', 'bad-except-order', @@ -71,6 +80,14 @@ def _annotated_unpack_infer(stmt, context=None): 'where the exception context is not an exception, ' 'nor None.', {'minversion': (3, 0)}), + 'E0704': ('The raise statement is not inside an except clause', + 'misplaced-bare-raise', + 'Used when a bare raise is not used inside an except clause. ' + 'This generates an error, since there are no active exceptions ' + 'to be reraised. An exception to this rule is represented by ' + 'a bare raise inside a finally clause, which might work, as long ' + 'as an exception is raised inside the try block, but it is ' + 'nevertheless a code smell that must not be relied upon.'), 'E0710': ('Raising a new style class which doesn\'t inherit from BaseException', 'raising-non-exception', 'Used when a new style class which doesn\'t inherit from \ @@ -91,10 +108,10 @@ def _annotated_unpack_infer(stmt, context=None): 'broad-except', 'Used when an except catches a too general exception, \ possibly burying unrelated errors.'), - 'W0704': ('Except doesn\'t do anything', - 'pointless-except', - 'Used when an except clause does nothing but "pass" and there is\ - no "else" clause.'), + 'W0705': ('Catching previously caught exception type %s', + 'duplicate-except', + 'Used when an except catches a type that was already caught by ' + 'a previous handler.'), 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', 'nonstandard-exception', 'Used when a custom exception class is raised but doesn\'t \ @@ -128,13 +145,17 @@ class ExceptionsChecker(BaseChecker): ), ) - @check_messages('nonstandard-exception', + def open(self): + self.builtin_exceptions = _builtin_exceptions() + super(ExceptionsChecker, self).open() + + @check_messages('nonstandard-exception', 'misplaced-bare-raise', 'raising-bad-type', 'raising-non-exception', 'notimplemented-raised', 'bad-exception-context') def visit_raise(self, node): """visit raise possibly inferring value""" - # ignore empty raise if node.exc is None: + self._check_misplaced_bare_raise(node) return if PY3K and node.cause: self._check_bad_exception_context(node) @@ -144,24 +165,44 @@ def visit_raise(self, node): return else: try: - value = next(unpack_infer(expr)) + value = next(astroid.unpack_infer(expr)) except astroid.InferenceError: return self._check_raise_value(node, value) + def _check_misplaced_bare_raise(self, node): + # Filter out if it's present in __exit__. + scope = node.scope() + if (isinstance(scope, astroid.FunctionDef) + and scope.is_method() + and scope.name == '__exit__'): + return + + current = node + # Stop when a new scope is generated or when the raise + # statement is found inside a TryFinally. + ignores = (astroid.ExceptHandler, astroid.FunctionDef, astroid.TryFinally) + while current and not isinstance(current.parent, ignores): + current = current.parent + + expected = (astroid.ExceptHandler,) + if (not current + or not isinstance(current.parent, expected)): + self.add_message('misplaced-bare-raise', node=node) + def _check_bad_exception_context(self, node): """Verify that the exception context is properly set. An exception context can be only `None` or an exception. """ cause = safe_infer(node.cause) - if cause in (YES, None): + if cause in (astroid.YES, None): return if isinstance(cause, astroid.Const): if cause.value is not None: self.add_message('bad-exception-context', node=node) - elif (not isinstance(cause, astroid.Class) and + elif (not isinstance(cause, astroid.ClassDef) and not inherit_from_std_ex(cause)): self.add_message('bad-exception-context', node=node) @@ -179,9 +220,9 @@ def _check_raise_value(self, node, expr): elif ((isinstance(expr, astroid.Name) and expr.name in ('None', 'True', 'False')) or isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, - astroid.Module, astroid.Function))): + astroid.Module, astroid.FunctionDef))): emit = True - if not PY3K and isinstance(expr, astroid.Tuple): + if not PY3K and isinstance(expr, astroid.Tuple) and expr.elts: # On Python 2, using the following is not an error: # raise (ZeroDivisionError, None) # raise (ZeroDivisionError, ) @@ -191,11 +232,11 @@ def _check_raise_value(self, node, expr): # the scope of this check. first = expr.elts[0] inferred = safe_infer(first) - if isinstance(inferred, Instance): + if isinstance(inferred, astroid.Instance): # pylint: disable=protected-access inferred = inferred._proxied - if (inferred is YES or - isinstance(inferred, astroid.Class) + if (inferred is astroid.YES or + isinstance(inferred, astroid.ClassDef) and inherit_from_std_ex(inferred)): emit = False if emit: @@ -203,26 +244,21 @@ def _check_raise_value(self, node, expr): node=node, args=expr.name) elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') - or (isinstance(expr, astroid.CallFunc) and + or (isinstance(expr, astroid.Call) and isinstance(expr.func, astroid.Name) and expr.func.name == 'NotImplemented')): self.add_message('notimplemented-raised', node=node) - elif isinstance(expr, (Instance, astroid.Class)): - if isinstance(expr, Instance): + elif isinstance(expr, (astroid.Instance, astroid.ClassDef)): + if isinstance(expr, astroid.Instance): # pylint: disable=protected-access expr = expr._proxied - if (isinstance(expr, astroid.Class) and - not inherit_from_std_ex(expr)): + if (isinstance(expr, astroid.ClassDef) and + not inherit_from_std_ex(expr) and + has_known_bases(expr)): if expr.newstyle: self.add_message('raising-non-exception', node=node) else: - if has_known_bases(expr): - confidence = INFERENCE - else: - confidence = INFERENCE_FAILURE - self.add_message( - 'nonstandard-exception', node=node, - confidence=confidence) + self.add_message('nonstandard-exception', node=node) else: value_found = False else: @@ -240,7 +276,7 @@ def _check_catching_non_exception(self, handler, exc, part): for node in inferred): return - if not isinstance(exc, astroid.Class): + if not isinstance(exc, astroid.ClassDef): # Don't emit the warning if the infered stmt # is None, but the exception handler is something else, # maybe it was redefined. @@ -261,25 +297,22 @@ def _check_catching_non_exception(self, handler, exc, part): node=handler.type, args=(part.as_string(), )) return + if (not inherit_from_std_ex(exc) and - exc.root().name != BUILTINS_NAME): + exc.name not in self.builtin_exceptions): if has_known_bases(exc): self.add_message('catching-non-exception', node=handler.type, args=(exc.name, )) - @check_messages('bare-except', 'broad-except', 'pointless-except', + @check_messages('bare-except', 'broad-except', 'binary-op-exception', 'bad-except-order', - 'catching-non-exception') + 'catching-non-exception', 'duplicate-except') def visit_tryexcept(self, node): """check for empty except""" exceptions_classes = [] nb_handlers = len(node.handlers) for index, handler in enumerate(node.handlers): - # single except doing nothing but "pass" without else clause - if is_empty(handler.body) and not node.orelse: - self.add_message('pointless-except', - node=handler.type or handler.body[0]) if handler.type is None: if not is_raising(handler.body): self.add_message('bare-except', node=handler) @@ -298,7 +331,7 @@ def visit_tryexcept(self, node): except astroid.InferenceError: continue for part, exc in excs: - if exc is YES: + if exc is astroid.YES: continue if (isinstance(exc, astroid.Instance) and inherit_from_std_ex(exc)): @@ -307,11 +340,11 @@ def visit_tryexcept(self, node): self._check_catching_non_exception(handler, exc, part) - if not isinstance(exc, astroid.Class): + if not isinstance(exc, astroid.ClassDef): continue exc_ancestors = [anc for anc in exc.ancestors() - if isinstance(anc, astroid.Class)] + if isinstance(anc, astroid.ClassDef)] for previous_exc in exceptions_classes: if previous_exc in exc_ancestors: msg = '%s is an ancestor class of %s' % ( @@ -324,6 +357,10 @@ def visit_tryexcept(self, node): self.add_message('broad-except', args=exc.name, node=handler.type) + if exc in exceptions_classes: + self.add_message('duplicate-except', + args=exc.name, node=handler.type) + exceptions_classes += [exc for _, exc in excs] diff --git a/pymode/libs/pylint/checkers/format.py b/pymode/libs/pylint/checkers/format.py index 8c496ac1..7930074d 100644 --- a/pymode/libs/pylint/checkers/format.py +++ b/pymode/libs/pylint/checkers/format.py @@ -60,7 +60,9 @@ # Whitespace checking config constants _DICT_SEPARATOR = 'dict-separator' _TRAILING_COMMA = 'trailing-comma' -_NO_SPACE_CHECK_CHOICES = [_TRAILING_COMMA, _DICT_SEPARATOR] +_EMPTY_LINE = 'empty-line' +_NO_SPACE_CHECK_CHOICES = [_TRAILING_COMMA, _DICT_SEPARATOR, _EMPTY_LINE] +_DEFAULT_NO_SPACE_CHECK_CHOICES = [_TRAILING_COMMA, _DICT_SEPARATOR] MSGS = { 'C0301': ('Line too long (%s/%s)', @@ -81,7 +83,7 @@ 'bad-indentation', 'Used when an unexpected number of indentation\'s tabulations or ' 'spaces has been found.'), - 'C0330': ('Wrong %s indentation%s.\n%s%s', + 'C0330': ('Wrong %s indentation%s%s.\n%s%s', 'bad-continuation', 'TODO'), 'W0312': ('Found indentation with %ss instead of %ss', @@ -124,8 +126,11 @@ def _underline_token(token): length = token[3][1] - token[2][1] offset = token[2][1] - return token[4] + (' ' * offset) + ('^' * length) - + referenced_line = token[4] + # If the referenced line does not end with a newline char, fix it + if referenced_line[-1] != '\n': + referenced_line += '\n' + return referenced_line + (' ' * offset) + ('^' * length) def _column_distance(token1, token2): if token1 == token2: @@ -165,14 +170,22 @@ def _get_indent_length(line): def _get_indent_hint_line(bar_positions, bad_position): """Return a line with |s for each of the positions in the given lists.""" if not bar_positions: - return '' + return ('', '') + delta_message = '' markers = [(pos, '|') for pos in bar_positions] + if len(markers) == 1: + # if we have only one marker we'll provide an extra hint on how to fix + expected_position = markers[0][0] + delta = abs(expected_position - bad_position) + direction = 'add' if expected_position > bad_position else 'remove' + delta_message = _CONTINUATION_HINT_MESSAGE % ( + direction, delta, 's' if delta > 1 else '') markers.append((bad_position, '^')) markers.sort() line = [' '] * (markers[-1][0] + 1) for position, marker in markers: line[position] = marker - return ''.join(line) + return (''.join(line), delta_message) class _ContinuedIndent(object): @@ -218,6 +231,7 @@ def __init__(self, CONTINUED_BLOCK: ('continued', ' before block'), } +_CONTINUATION_HINT_MESSAGE = ' (%s %d space%s)' # Ex: (remove 2 spaces) def _Offsets(*args): """Valid indentation offsets for a continued line.""" @@ -423,11 +437,17 @@ class FormatChecker(BaseTokenChecker): 'help' : ('Allow the body of an if to be on the same ' 'line as the test if there is no else.')}), ('no-space-check', - {'default': ','.join(_NO_SPACE_CHECK_CHOICES), + {'default': ','.join(_DEFAULT_NO_SPACE_CHECK_CHOICES), + 'metavar': ','.join(_NO_SPACE_CHECK_CHOICES), 'type': 'multiple_choice', 'choices': _NO_SPACE_CHECK_CHOICES, 'help': ('List of optional constructs for which whitespace ' - 'checking is disabled')}), + 'checking is disabled. ' + '`'+ _DICT_SEPARATOR + '` is used to allow tabulation ' + 'in dicts, etc.: {1 : 1,\\n222: 2}. ' + '`'+ _TRAILING_COMMA + '` allows a space between comma ' + 'and closing bracket: (a, ). ' + '`'+ _EMPTY_LINE + '` allows space-only lines.')}), ('max-module-lines', {'default' : 1000, 'type' : 'int', 'metavar' : '', 'help': 'Maximum number of lines in a module'} @@ -510,27 +530,28 @@ def _check_keyword_parentheses(self, tokens, start): depth += 1 elif token[1] == ')': depth -= 1 - if not depth: - # ')' can't happen after if (foo), since it would be a syntax error. - if (tokens[i+1][1] in (':', ')', ']', '}', 'in') or - tokens[i+1][0] in (tokenize.NEWLINE, - tokenize.ENDMARKER, - tokenize.COMMENT)): - # The empty tuple () is always accepted. - if i == start + 2: - return - if keyword_token == 'not': - if not found_and_or: - self.add_message('superfluous-parens', line=line_num, - args=keyword_token) - elif keyword_token in ('return', 'yield'): + if depth: + continue + # ')' can't happen after if (foo), since it would be a syntax error. + if (tokens[i+1][1] in (':', ')', ']', '}', 'in') or + tokens[i+1][0] in (tokenize.NEWLINE, + tokenize.ENDMARKER, + tokenize.COMMENT)): + # The empty tuple () is always accepted. + if i == start + 2: + return + if keyword_token == 'not': + if not found_and_or: self.add_message('superfluous-parens', line=line_num, args=keyword_token) - elif keyword_token not in self._keywords_with_parens: - if not (tokens[i+1][1] == 'in' and found_and_or): - self.add_message('superfluous-parens', line=line_num, - args=keyword_token) - return + elif keyword_token in ('return', 'yield'): + self.add_message('superfluous-parens', line=line_num, + args=keyword_token) + elif keyword_token not in self._keywords_with_parens: + if not (tokens[i+1][1] == 'in' and found_and_or): + self.add_message('superfluous-parens', line=line_num, + args=keyword_token) + return elif depth == 1: # This is a tuple, which is always acceptable. if token[1] == ',': @@ -846,11 +867,12 @@ def same_token_around_nl(token_type): def _add_continuation_message(self, state, offsets, tokens, position): readable_type, readable_position = _CONTINUATION_MSG_PARTS[state.context_type] - hint_line = _get_indent_hint_line(offsets, tokens.start_col(position)) + hint_line, delta_message = _get_indent_hint_line(offsets, tokens.start_col(position)) self.add_message( 'bad-continuation', line=tokens.start_line(position), - args=(readable_type, readable_position, tokens.line(position), hint_line)) + args=(readable_type, readable_position, delta_message, + tokens.line(position), hint_line)) @check_messages('multiple-statements') def visit_default(self, node): @@ -920,7 +942,10 @@ def check_lines(self, lines, i): self.add_message('missing-final-newline', line=i) else: stripped_line = line.rstrip() - if line[len(stripped_line):] not in ('\n', '\r\n'): + if not stripped_line and _EMPTY_LINE in self.config.no_space_check: + # allow empty lines + pass + elif line[len(stripped_line):] not in ('\n', '\r\n'): self.add_message('trailing-whitespace', line=i) # Don't count excess whitespace in the line length. line = stripped_line diff --git a/pymode/libs/pylint/checkers/imports.py b/pymode/libs/pylint/checkers/imports.py index 1969eeb1..a8df9a8d 100644 --- a/pymode/libs/pylint/checkers/imports.py +++ b/pymode/libs/pylint/checkers/imports.py @@ -15,34 +15,56 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """imports checkers for Python code""" +import collections +from distutils import sysconfig +import os import sys -from collections import defaultdict import six -from six.moves import map # pylint: disable=redefined-builtin - -from logilab.common.graph import get_cycles, DotBackend -from logilab.common.ureports import VerbatimText, Paragraph import astroid from astroid import are_exclusive -from astroid.modutils import get_module_part, is_standard_module +from astroid.modutils import (get_module_part, is_standard_module, + file_from_modpath) from pylint.interfaces import IAstroidChecker -from pylint.utils import EmptyReport +from pylint.utils import EmptyReport, get_global_option from pylint.checkers import BaseChecker -from pylint.checkers.utils import check_messages, is_import_error +from pylint.checkers.utils import check_messages, node_ignores_exception +from pylint.graph import get_cycles, DotBackend +from pylint.reporters.ureports.nodes import VerbatimText, Paragraph + + +def _qualified_names(modname): + """Split the names of the given module into subparts -def _except_import_error(node): + For example, + _qualified_names('pylint.checkers.ImportsChecker') + returns + ['pylint', 'pylint.checkers', 'pylint.checkers.ImportsChecker'] """ - Check if the try-except node has an ImportError handler. - Return True if an ImportError handler was infered, False otherwise. + names = modname.split('.') + return ['.'.join(names[0:i+1]) for i in range(len(names))] + + +def _get_import_name(importnode, modname): + """Get a prepared module name from the given import node + + In the case of relative imports, this will return the + absolute qualified module name, which might be useful + for debugging. Otherwise, the initial module name + is returned unchanged. """ - if not isinstance(node, astroid.TryExcept): - return - return any(map(is_import_error, node.handlers)) + if isinstance(importnode, astroid.ImportFrom): + if importnode.level: + root = importnode.root() + if isinstance(root, astroid.Module): + modname = root.relative_to_absolute_name( + modname, level=importnode.level) + return modname + -def get_first_import(node, context, name, base, level): +def _get_first_import(node, context, name, base, level): """return the node where [base.] is imported or None if not found """ fullname = '%s.%s' % (base, name) if base else name @@ -58,7 +80,7 @@ def get_first_import(node, context, name, base, level): if any(fullname == iname[0] for iname in first.names): found = True break - elif isinstance(first, astroid.From): + elif isinstance(first, astroid.ImportFrom): if level == first.level and any( fullname == '%s.%s' % (first.modname, iname[0]) for iname in first.names): @@ -69,7 +91,7 @@ def get_first_import(node, context, name, base, level): # utilities to represents import dependencies as tree and dot graph ########### -def make_tree_defs(mod_files_list): +def _make_tree_defs(mod_files_list): """get a list of 2-uple (module, list_of_files_which_import_this_module), it will return a dictionary to represent this as a tree """ @@ -81,7 +103,8 @@ def make_tree_defs(mod_files_list): node[1] += files return tree_defs -def repr_tree_defs(data, indent_str=None): + +def _repr_tree_defs(data, indent_str=None): """return a string which represents imports as a tree""" lines = [] nodes = data.items() @@ -100,11 +123,11 @@ def repr_tree_defs(data, indent_str=None): else: sub_indent_str = '%s| ' % indent_str if sub: - lines.append(repr_tree_defs(sub, sub_indent_str)) + lines.append(_repr_tree_defs(sub, sub_indent_str)) return '\n'.join(lines) -def dependencies_graph(filename, dep_info): +def _dependencies_graph(filename, dep_info): """write dependencies as a dot (graphviz) file """ done = {} @@ -123,11 +146,11 @@ def dependencies_graph(filename, dep_info): printer.generate(filename) -def make_graph(filename, dep_info, sect, gtype): +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) + _dependencies_graph(filename, dep_info) sect.append(Paragraph('%simports graph has been written to %s' % (gtype, filename))) @@ -135,9 +158,10 @@ def make_graph(filename, dep_info, sect, gtype): # the import checker itself ################################################### MSGS = { - 'F0401': ('Unable to import %s', + 'E0401': ('Unable to import %s', 'import-error', - 'Used when pylint has been unable to import a module.'), + 'Used when pylint has been unable to import a module.', + {'old_names': [('F0401', 'import-error')]}), 'R0401': ('Cyclic import (%s)', 'cyclic-import', 'Used when a cyclic import between two or more modules is \ @@ -164,8 +188,23 @@ def make_graph(filename, dep_info, sect, gtype): 'W0410': ('__future__ import is not the first non docstring statement', 'misplaced-future', 'Python 2.5 and greater require __future__ import to be the \ - first non docstring statement in the module.', - {'maxversion': (3, 0)}), + first non docstring statement in the module.'), + + 'C0410': ('Multiple imports on one line (%s)', + 'multiple-imports', + 'Used when import statement importing multiple modules is ' + 'detected.'), + 'C0411': ('%s comes before %s', + 'wrong-import-order', + 'Used when PEP8 import order is not respected (standard imports ' + 'first, then third-party libraries, then local imports)'), + 'C0412': ('Imports from package %s are not grouped', + 'ungrouped-imports', + 'Used when imports are not grouped by packages'), + 'C0413': ('Import "%s" should be placed at the top of the ' + 'module', + 'wrong-import-position', + 'Used when code and imports are mixed'), } class ImportsChecker(BaseChecker): @@ -182,10 +221,10 @@ class ImportsChecker(BaseChecker): msgs = MSGS priority = -2 - if sys.version_info < (3,): + if six.PY2: deprecated_modules = ('regsub', 'TERMIOS', 'Bastion', 'rexec') else: - deprecated_modules = ('stringprep', 'optparse') + deprecated_modules = ('optparse', ) options = (('deprecated-modules', {'default' : deprecated_modules, 'type' : 'csv', @@ -220,19 +259,46 @@ def __init__(self, linter=None): BaseChecker.__init__(self, linter) self.stats = None self.import_graph = None + self._imports_stack = [] + self._first_non_import_node = None self.__int_dep_info = self.__ext_dep_info = None self.reports = (('RP0401', 'External dependencies', - self.report_external_dependencies), + self._report_external_dependencies), ('RP0402', 'Modules dependencies graph', - self.report_dependencies_graph), + self._report_dependencies_graph), ) + self._site_packages = self._compute_site_packages() + + @staticmethod + def _compute_site_packages(): + def _normalized_path(path): + return os.path.normcase(os.path.abspath(path)) + + paths = set() + real_prefix = getattr(sys, 'real_prefix', None) + for prefix in filter(None, (real_prefix, sys.prefix)): + path = sysconfig.get_python_lib(prefix=prefix) + path = _normalized_path(path) + paths.add(path) + + # Handle Debian's derivatives /usr/local. + if os.path.isfile("/etc/debian_version"): + for prefix in filter(None, (real_prefix, sys.prefix)): + libpython = os.path.join(prefix, "local", "lib", + "python" + sysconfig.get_python_version(), + "dist-packages") + paths.add(libpython) + return paths + def open(self): """called before visiting project (i.e set of modules)""" self.linter.add_stats(dependencies={}) self.linter.add_stats(cycles=[]) self.stats = self.linter.stats - self.import_graph = defaultdict(set) + self.import_graph = collections.defaultdict(set) + self._ignored_modules = get_global_option( + self, 'ignored-modules', default=[]) def close(self): """called before visiting project (i.e set of modules)""" @@ -242,56 +308,211 @@ def close(self): for cycle in get_cycles(self.import_graph, vertices=vertices): self.add_message('cyclic-import', args=' -> '.join(cycle)) + @check_messages('wrong-import-position', 'multiple-imports', + 'relative-import', 'reimported') def visit_import(self, node): """triggered when an import statement is seen""" + self._check_reimport(node) + modnode = node.root() - for name, _ in node.names: + names = [name for name, _ in node.names] + if len(names) >= 2: + self.add_message('multiple-imports', args=', '.join(names), node=node) + + for name in names: + self._check_deprecated_module(node, name) importedmodnode = self.get_imported_module(node, name) + if isinstance(node.scope(), astroid.Module): + self._check_position(node) + self._record_import(node, importedmodnode) + if importedmodnode is None: continue + self._check_relative_import(modnode, node, importedmodnode, name) self._add_imported_module(node, importedmodnode.name) - self._check_deprecated_module(node, name) - self._check_reimport(node, name) - # TODO This appears to be the list of all messages of the checker... - # @check_messages('W0410', 'W0401', 'W0403', 'W0402', 'W0404', 'W0406', 'F0401') @check_messages(*(MSGS.keys())) - def visit_from(self, node): + def visit_importfrom(self, node): """triggered when a from statement is seen""" basename = node.modname - if basename == '__future__': - # check if this is the first non-docstring statement in the module - prev = node.previous_sibling() - if prev: - # consecutive future statements are possible - if not (isinstance(prev, astroid.From) - and prev.modname == '__future__'): - self.add_message('misplaced-future', node=node) - return - for name, _ in node.names: - if name == '*': - self.add_message('wildcard-import', args=basename, node=node) + self._check_misplaced_future(node) + self._check_deprecated_module(node, basename) + self._check_wildcard_imports(node) + self._check_same_line_imports(node) + self._check_reimport(node, basename=basename, level=node.level) + modnode = node.root() importedmodnode = self.get_imported_module(node, basename) + if isinstance(node.scope(), astroid.Module): + self._check_position(node) + self._record_import(node, importedmodnode) if importedmodnode is None: return self._check_relative_import(modnode, node, importedmodnode, basename) - self._check_deprecated_module(node, basename) + for name, _ in node.names: if name != '*': self._add_imported_module(node, '%s.%s' % (importedmodnode.name, name)) - self._check_reimport(node, name, basename, node.level) + + @check_messages('wrong-import-order', 'ungrouped-imports', + 'wrong-import-position') + def leave_module(self, node): + # Check imports are grouped by category (standard, 3rd party, local) + std_imports, ext_imports, loc_imports = self._check_imports_order(node) + + # Check imports are grouped by package within a given category + met = set() + current_package = None + for import_node, import_name in std_imports + ext_imports + loc_imports: + package, _, _ = import_name.partition('.') + if current_package and current_package != package and package in met: + self.add_message('ungrouped-imports', node=import_node, + args=package) + current_package = package + met.add(package) + + self._imports_stack = [] + self._first_non_import_node = None + + def visit_if(self, node): + # if the node does not contain an import instruction, and if it is the + # first node of the module, keep a track of it (all the import positions + # of the module will be compared to the position of this first + # instruction) + if self._first_non_import_node: + return + if not isinstance(node.parent, astroid.Module): + return + if any(node.nodes_of_class((astroid.Import, astroid.ImportFrom))): + return + self._first_non_import_node = node + + visit_tryfinally = visit_tryexcept = visit_assignattr = visit_assign \ + = visit_ifexp = visit_comprehension = visit_if + + def visit_functiondef(self, node): + # If it is the first non import instruction of the module, record it. + if self._first_non_import_node: + return + + # Check if the node belongs to an `If` or a `Try` block. If they + # contain imports, skip recording this node. + if not isinstance(node.parent.scope(), astroid.Module): + return + + root = node + while not isinstance(root.parent, astroid.Module): + root = root.parent + + if isinstance(root, (astroid.If, astroid.TryFinally, astroid.TryExcept)): + if any(root.nodes_of_class((astroid.Import, astroid.ImportFrom))): + return + + self._first_non_import_node = node + + visit_classdef = visit_for = visit_while = visit_functiondef + + def _check_misplaced_future(self, node): + basename = node.modname + if basename == '__future__': + # check if this is the first non-docstring statement in the module + prev = node.previous_sibling() + if prev: + # consecutive future statements are possible + if not (isinstance(prev, astroid.ImportFrom) + and prev.modname == '__future__'): + self.add_message('misplaced-future', node=node) + return + + def _check_same_line_imports(self, node): + # Detect duplicate imports on the same line. + names = (name for name, _ in node.names) + counter = collections.Counter(names) + for name, count in counter.items(): + if count > 1: + self.add_message('reimported', node=node, + args=(name, node.fromlineno)) + + def _check_position(self, node): + """Check `node` import or importfrom node position is correct + + Send a message if `node` comes before another instruction + """ + # if a first non-import instruction has already been encountered, + # it means the import comes after it and therefore is not well placed + if self._first_non_import_node: + self.add_message('wrong-import-position', node=node, + args=node.as_string()) + + def _record_import(self, node, importedmodnode): + """Record the package `node` imports from""" + importedname = importedmodnode.name if importedmodnode else None + if not importedname: + importedname = node.names[0][0].split('.')[0] + self._imports_stack.append((node, importedname)) + + @staticmethod + def _is_fallback_import(node, imports): + imports = [import_node for (import_node, _) in imports] + return any(astroid.are_exclusive(import_node, node) + for import_node in imports) + + def _check_imports_order(self, node): + """Checks imports of module `node` are grouped by category + + Imports must follow this order: standard, 3rd party, local + """ + extern_imports = [] + local_imports = [] + std_imports = [] + for node, modname in self._imports_stack: + package = modname.split('.')[0] + if is_standard_module(modname): + std_imports.append((node, package)) + wrong_import = extern_imports or local_imports + if not wrong_import: + continue + if self._is_fallback_import(node, wrong_import): + continue + self.add_message('wrong-import-order', node=node, + args=('standard import "%s"' % node.as_string(), + '"%s"' % wrong_import[0][0].as_string())) + else: + try: + filename = file_from_modpath([package]) + except ImportError: + continue + if not filename: + continue + + filename = os.path.normcase(os.path.abspath(filename)) + if not any(filename.startswith(path) for path in self._site_packages): + local_imports.append((node, package)) + continue + extern_imports.append((node, package)) + if not local_imports: + continue + self.add_message('wrong-import-order', node=node, + args=('external import "%s"' % node.as_string(), + '"%s"' % local_imports[0][0].as_string())) + return std_imports, extern_imports, local_imports def get_imported_module(self, importnode, modname): try: return importnode.do_import_module(modname) except astroid.InferenceError as ex: + dotted_modname = _get_import_name(importnode, modname) if str(ex) != modname: - args = '%r (%s)' % (modname, ex) + args = '%r (%s)' % (dotted_modname, ex) else: - args = repr(modname) - if not _except_import_error(importnode.parent): + args = repr(dotted_modname) + + for submodule in _qualified_names(modname): + if submodule in self._ignored_modules: + return None + + if not node_ignores_exception(importnode, ImportError): self.add_message("import-error", args=args, node=importnode) def _check_relative_import(self, modnode, importnode, importedmodnode, @@ -315,19 +536,32 @@ def _check_relative_import(self, modnode, importnode, importedmodnode, def _add_imported_module(self, node, importedmodname): """notify an imported module, used to analyze dependencies""" + module_file = node.root().file + context_name = node.root().name + base = os.path.splitext(os.path.basename(module_file))[0] + + # Determine if we have a `from .something import` in a package's + # __init__. This means the module will never be able to import + # itself using this condition (the level will be bigger or + # if the same module is named as the package, it will be different + # anyway). + if isinstance(node, astroid.ImportFrom): + if node.level and node.level > 0 and base == '__init__': + return + try: - importedmodname = get_module_part(importedmodname) + importedmodname = get_module_part(importedmodname, + module_file) except ImportError: pass - context_name = node.root().name + if context_name == importedmodname: - # module importing itself ! self.add_message('import-self', node=node) elif not is_standard_module(importedmodname): # handle dependencies importedmodnames = self.stats['dependencies'].setdefault( importedmodname, set()) - if not context_name in importedmodnames: + if context_name not in importedmodnames: importedmodnames.add(context_name) # update import graph mgraph = self.import_graph[context_name] @@ -340,31 +574,33 @@ def _check_deprecated_module(self, node, mod_path): if mod_path == mod_name or mod_path.startswith(mod_name + '.'): self.add_message('deprecated-module', node=node, args=mod_path) - def _check_reimport(self, node, name, basename=None, level=None): + def _check_reimport(self, node, basename=None, level=None): """check if the import is necessary (i.e. not already done)""" if not self.linter.is_message_enabled('reimported'): return + frame = node.frame() root = node.root() contexts = [(frame, level)] if root is not frame: contexts.append((root, None)) - for context, level in contexts: - first = get_first_import(node, context, name, basename, level) - if first is not None: - self.add_message('reimported', node=node, - args=(name, first.fromlineno)) + for context, level in contexts: + for name, _ in node.names: + first = _get_first_import(node, context, name, basename, level) + if first is not None: + self.add_message('reimported', node=node, + args=(name, first.fromlineno)) - def report_external_dependencies(self, sect, _, dummy): + def _report_external_dependencies(self, sect, _, dummy): """return a verbatim layout for displaying dependencies""" - dep_info = make_tree_defs(six.iteritems(self._external_dependencies_info())) + dep_info = _make_tree_defs(six.iteritems(self._external_dependencies_info())) if not dep_info: raise EmptyReport() - tree_str = repr_tree_defs(dep_info) + tree_str = _repr_tree_defs(dep_info) sect.append(VerbatimText(tree_str)) - def report_dependencies_graph(self, sect, _, dummy): + 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 @@ -373,15 +609,15 @@ def report_dependencies_graph(self, sect, _, dummy): raise EmptyReport() filename = self.config.import_graph if filename: - make_graph(filename, dep_info, sect, '') + _make_graph(filename, dep_info, sect, '') filename = self.config.ext_import_graph if filename: - make_graph(filename, self._external_dependencies_info(), - sect, 'external ') + _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 ') + _make_graph(filename, self._internal_dependencies_info(), + sect, 'internal ') def _external_dependencies_info(self): """return cached external dependencies information or build and @@ -407,6 +643,11 @@ def _internal_dependencies_info(self): result[importee] = importers return self.__int_dep_info + def _check_wildcard_imports(self, node): + for name, _ in node.names: + if name == '*': + self.add_message('wildcard-import', args=node.modname, node=node) + def register(linter): """required method to auto register this checker """ diff --git a/pymode/libs/pylint/checkers/logging.py b/pymode/libs/pylint/checkers/logging.py index 897c1c7f..d20ff3ed 100644 --- a/pymode/libs/pylint/checkers/logging.py +++ b/pymode/libs/pylint/checkers/logging.py @@ -14,13 +14,15 @@ """checker for use of Python logging """ +import six + import astroid + from pylint import checkers from pylint import interfaces from pylint.checkers import utils from pylint.checkers.utils import check_messages -import six MSGS = { @@ -35,7 +37,7 @@ 'interpolation in those cases in which no message will be ' 'logged. For more, see ' 'http://www.python.org/dev/peps/pep-0282/.'), - 'W1202': ('Use % formatting in logging functions but pass the % ' + 'W1202': ('Use % formatting in logging functions and pass the % ' 'parameters as arguments', 'logging-format-interpolation', 'Used when a logging statement has a call form of ' @@ -76,7 +78,7 @@ def is_method_call(callfunc_node, types=(), methods=()): True, if the node represents a method call for the given type and method names, False otherwise. """ - if not isinstance(callfunc_node, astroid.CallFunc): + if not isinstance(callfunc_node, astroid.Call): return False func = utils.safe_infer(callfunc_node.func) return (isinstance(func, astroid.BoundMethod) @@ -117,7 +119,7 @@ def visit_module(self, node): # pylint: disable=unused-argument if len(parts) > 1: self._from_imports[parts[0]] = parts[1] - def visit_from(self, node): + def visit_importfrom(self, node): """Checks to see if a module uses a non-Python logging module.""" try: logging_name = self._from_imports[node.modname] @@ -134,10 +136,10 @@ def visit_import(self, node): self._logging_names.add(as_name or module) @check_messages(*(MSGS.keys())) - def visit_callfunc(self, node): + def visit_call(self, node): """Checks calls to logging methods.""" def is_logging_name(): - return (isinstance(node.func, astroid.Getattr) and + return (isinstance(node.func, astroid.Attribute) and isinstance(node.func.expr, astroid.Name) and node.func.expr.name in self._logging_names) @@ -146,7 +148,7 @@ def is_logger_class(): for inferred in node.func.infer(): if isinstance(inferred, astroid.BoundMethod): parent = inferred._proxied.parent - if (isinstance(parent, astroid.Class) and + if (isinstance(parent, astroid.ClassDef) and (parent.qname() == 'logging.Logger' or any(ancestor.qname() == 'logging.Logger' for ancestor in parent.ancestors()))): @@ -182,7 +184,7 @@ def _check_log_method(self, node, name): if isinstance(node.args[format_pos], astroid.BinOp) and node.args[format_pos].op == '%': self.add_message('logging-not-lazy', node=node) - elif isinstance(node.args[format_pos], astroid.CallFunc): + elif isinstance(node.args[format_pos], astroid.Call): self._check_call_func(node.args[format_pos]) elif isinstance(node.args[format_pos], astroid.Const): self._check_format_string(node, format_pos) diff --git a/pymode/libs/pylint/checkers/misc.py b/pymode/libs/pylint/checkers/misc.py index 7fbe70bf..e01dfcb6 100644 --- a/pymode/libs/pylint/checkers/misc.py +++ b/pymode/libs/pylint/checkers/misc.py @@ -19,9 +19,10 @@ import re +import six + from pylint.interfaces import IRawChecker from pylint.checkers import BaseChecker -import six MSGS = { diff --git a/pymode/libs/pylint/checkers/newstyle.py b/pymode/libs/pylint/checkers/newstyle.py index f74e7f15..1d1f91dc 100644 --- a/pymode/libs/pylint/checkers/newstyle.py +++ b/pymode/libs/pylint/checkers/newstyle.py @@ -23,8 +23,8 @@ from pylint.checkers import BaseChecker from pylint.checkers.utils import ( check_messages, - has_known_bases, node_frame_class, + has_known_bases ) MSGS = { @@ -77,7 +77,7 @@ class NewStyleConflictChecker(BaseChecker): options = () @check_messages('slots-on-old-class', 'old-style-class') - def visit_class(self, node): + def visit_classdef(self, node): """ Check __slots__ in old style classes and old style class definition. """ @@ -95,10 +95,10 @@ def visit_class(self, node): self.add_message('old-style-class', node=node, confidence=HIGH) @check_messages('property-on-old-class') - def visit_callfunc(self, node): + def visit_call(self, node): """check property usage""" parent = node.parent.frame() - if (isinstance(parent, astroid.Class) and + if (isinstance(parent, astroid.ClassDef) and not parent.newstyle and isinstance(node.func, astroid.Name)): confidence = (INFERENCE if has_known_bases(parent) @@ -109,62 +109,59 @@ def visit_callfunc(self, node): confidence=confidence) @check_messages('super-on-old-class', 'bad-super-call', 'missing-super-argument') - def visit_function(self, node): + def visit_functiondef(self, node): """check use of super""" # ignore actual functions or method within a new style class if not node.is_method(): return klass = node.parent.frame() - for stmt in node.nodes_of_class(astroid.CallFunc): + for stmt in node.nodes_of_class(astroid.Call): if node_frame_class(stmt) != node_frame_class(node): # Don't look down in other scopes. continue expr = stmt.func - if not isinstance(expr, astroid.Getattr): + if not isinstance(expr, astroid.Attribute): continue call = expr.expr # skip the test if using super - if isinstance(call, astroid.CallFunc) and \ - isinstance(call.func, astroid.Name) and \ - call.func.name == 'super': - confidence = (INFERENCE if has_known_bases(klass) - else INFERENCE_FAILURE) - if not klass.newstyle: - # super should not be used on an old style class - self.add_message('super-on-old-class', node=node, - confidence=confidence) - else: - # super first arg should be the class - if not call.args and sys.version_info[0] == 3: - # unless Python 3 - continue - - try: - supcls = (call.args and next(call.args[0].infer()) - or None) - except astroid.InferenceError: - continue - - if supcls is None: - self.add_message('missing-super-argument', node=call, - confidence=confidence) - continue - - if klass is not supcls: - name = None - # if supcls is not YES, then supcls was infered - # and use its name. Otherwise, try to look - # for call.args[0].name - if supcls is not astroid.YES: - name = supcls.name - else: - if hasattr(call.args[0], 'name'): - name = call.args[0].name - if name is not None: - self.add_message('bad-super-call', - node=call, - args=(name, ), - confidence=confidence) + if not (isinstance(call, astroid.Call) and + isinstance(call.func, astroid.Name) and + call.func.name == 'super'): + continue + + if not klass.newstyle and has_known_bases(klass): + # super should not be used on an old style class + self.add_message('super-on-old-class', node=node) + else: + # super first arg should be the class + if not call.args and sys.version_info[0] == 3: + # unless Python 3 + continue + + try: + supcls = (call.args and next(call.args[0].infer()) + or None) + except astroid.InferenceError: + continue + + if supcls is None: + self.add_message('missing-super-argument', node=call) + continue + + if klass is not supcls: + name = None + # if supcls is not YES, then supcls was infered + # and use its name. Otherwise, try to look + # for call.args[0].name + if supcls is not astroid.YES: + name = supcls.name + else: + if hasattr(call.args[0], 'name'): + name = call.args[0].name + if name is not None: + self.add_message('bad-super-call', node=call, args=(name, )) + + visit_asyncfunctiondef = visit_functiondef def register(linter): diff --git a/pymode/libs/pylint/checkers/python3.py b/pymode/libs/pylint/checkers/python3.py index 837cbef1..723fe231 100644 --- a/pymode/libs/pylint/checkers/python3.py +++ b/pymode/libs/pylint/checkers/python3.py @@ -19,6 +19,7 @@ import astroid from astroid import bases + from pylint import checkers, interfaces from pylint.utils import WarningScope from pylint.checkers import utils @@ -50,7 +51,7 @@ def _check_dict_node(node): def _is_builtin(node): return getattr(node, 'name', None) in ('__builtin__', 'builtins') -_accepts_iterator = {'iter', 'list', 'tuple', 'sorted', 'set', 'sum', 'any', +_ACCEPTS_ITERATOR = {'iter', 'list', 'tuple', 'sorted', 'set', 'sum', 'any', 'all', 'enumerate', 'dict'} def _in_iterating_context(node): @@ -71,12 +72,12 @@ def _in_iterating_context(node): return True # Various built-ins can take in an iterable or list and lead to the same # value. - elif isinstance(parent, astroid.CallFunc): + elif isinstance(parent, astroid.Call): if isinstance(parent.func, astroid.Name): parent_scope = parent.func.lookup(parent.func.name)[0] - if _is_builtin(parent_scope) and parent.func.name in _accepts_iterator: + if _is_builtin(parent_scope) and parent.func.name in _ACCEPTS_ITERATOR: return True - elif isinstance(parent.func, astroid.Getattr): + elif isinstance(parent.func, astroid.Attribute): if parent.func.attrname == 'join': return True # If the call is in an unpacking, there's no need to warn, @@ -130,6 +131,11 @@ class Python3Checker(checkers.BaseChecker): {'scope': WarningScope.NODE, 'maxversion': (3, 0), 'old_names': [('W0333', 'backtick')]}), + 'E1609': ('Import * only allowed at module level', + 'import-star-module-level', + 'Used when the import star syntax is used somewhere ' + 'else than the module level.', + {'maxversion': (3, 0)}), 'W1601': ('apply built-in referenced', 'apply-builtin', 'Used when the apply built-in function is referenced ' @@ -242,7 +248,7 @@ class Python3Checker(checkers.BaseChecker): "Used when an object's next() method is called " '(Python 3 uses the next() built-in function)', {'maxversion': (3, 0)}), - 'W1623': ("Assigning to a class' __metaclass__ attribute", + 'W1623': ("Assigning to a class's __metaclass__ attribute", 'metaclass-assignment', "Used when a metaclass is specified by assigning to __metaclass__ " '(Python 3 specifies the metaclass as a class statement argument)', @@ -378,7 +384,7 @@ def visit_module(self, node): # pylint: disable=unused-argument self._future_division = False self._future_absolute_import = False - def visit_function(self, node): + def visit_functiondef(self, node): if node.is_method() and node.name in self._unused_magic_methods: method_name = node.name if node.name.startswith('__'): @@ -403,8 +409,7 @@ def visit_name(self, node): def visit_print(self, node): self.add_message('print-statement', node=node) - @utils.check_messages('no-absolute-import') - def visit_from(self, node): + def visit_importfrom(self, node): if node.modname == '__future__': for name, _ in node.names: if name == 'division': @@ -412,7 +417,12 @@ def visit_from(self, node): elif name == 'absolute_import': self._future_absolute_import = True elif not self._future_absolute_import: - self.add_message('no-absolute-import', node=node) + if self.linter.is_message_enabled('no-absolute-import'): + self.add_message('no-absolute-import', node=node) + if node.names[0][0] == '*': + if self.linter.is_message_enabled('import-star-module-level'): + if not isinstance(node.scope(), astroid.Module): + self.add_message('import-star-module-level', node=node) @utils.check_messages('no-absolute-import') def visit_import(self, node): @@ -420,7 +430,7 @@ def visit_import(self, node): self.add_message('no-absolute-import', node=node) @utils.check_messages('metaclass-assignment') - def visit_class(self, node): + def visit_classdef(self, node): if '__metaclass__' in node.locals: self.add_message('metaclass-assignment', node=node) @@ -435,8 +445,8 @@ def visit_binop(self, node): def _check_cmp_argument(self, node): # Check that the `cmp` argument is used - args = [] - if (isinstance(node.func, astroid.Getattr) + kwargs = [] + if (isinstance(node.func, astroid.Attribute) and node.func.attrname == 'sort'): inferred = utils.safe_infer(node.func.expr) if not inferred: @@ -445,28 +455,28 @@ def _check_cmp_argument(self, node): builtins_list = "{}.list".format(bases.BUILTINS) if (isinstance(inferred, astroid.List) or inferred.qname() == builtins_list): - args = node.args + kwargs = node.keywords elif (isinstance(node.func, astroid.Name) - and node.func.name == 'sorted'): + and node.func.name == 'sorted'): inferred = utils.safe_infer(node.func) if not inferred: return builtins_sorted = "{}.sorted".format(bases.BUILTINS) if inferred.qname() == builtins_sorted: - args = node.args + kwargs = node.keywords - for arg in args: - if isinstance(arg, astroid.Keyword) and arg.arg == 'cmp': + for kwarg in kwargs or []: + if kwarg.arg == 'cmp': self.add_message('using-cmp-argument', node=node) return - def visit_callfunc(self, node): + def visit_call(self, node): self._check_cmp_argument(node) - if isinstance(node.func, astroid.Getattr): - if any([node.args, node.starargs, node.kwargs]): + if isinstance(node.func, astroid.Attribute): + if any([node.args, node.keywords]): return if node.func.attrname == 'next': self.add_message('next-method-called', node=node) @@ -504,7 +514,7 @@ def visit_excepthandler(self, node): self.add_message('unpacking-in-except', node=node) @utils.check_messages('backtick') - def visit_backquote(self, node): + def visit_repr(self, node): self.add_message('backtick', node=node) @utils.check_messages('raising-string', 'old-raise-syntax') diff --git a/pymode/libs/pylint/checkers/raw_metrics.py b/pymode/libs/pylint/checkers/raw_metrics.py index 71fecf68..5846cd10 100644 --- a/pymode/libs/pylint/checkers/raw_metrics.py +++ b/pymode/libs/pylint/checkers/raw_metrics.py @@ -25,12 +25,12 @@ #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 ITokenChecker from pylint.utils import EmptyReport from pylint.checkers import BaseTokenChecker from pylint.reporters import diff_string +from pylint.reporters.ureports.nodes import Table + def report_raw_stats(sect, stats, old_stats): """calculate percentage of code / doc / comment / empty @@ -118,7 +118,7 @@ def get_type(tokens, start_index): i += 1 if line_type is None: line_type = 'empty_lines' - elif i < len(tokens) and tok_type == tokenize.NEWLINE: + elif i < len(tokens) and tokens[i][0] == tokenize.NEWLINE: i += 1 return i, pos[0] - start[0] + 1, line_type diff --git a/pymode/libs/pylint/checkers/similar.py b/pymode/libs/pylint/checkers/similar.py index 95420776..fe0d9056 100644 --- a/pymode/libs/pylint/checkers/similar.py +++ b/pymode/libs/pylint/checkers/similar.py @@ -20,13 +20,12 @@ import sys from collections import defaultdict -from logilab.common.ureports import Table +import six +from six.moves import zip from pylint.interfaces import IRawChecker from pylint.checkers import BaseChecker, table_lines_from_stats - -import six -from six.moves import zip +from pylint.reporters.ureports.nodes import Table class Similar(object): @@ -306,7 +305,7 @@ def process_module(self, node): def close(self): """compute and display similarities on closing (i.e. end of parsing)""" - total = sum([len(lineset) for lineset in self.linesets]) + total = sum(len(lineset) for lineset in self.linesets) duplicated = 0 stats = self.stats for num, couples in self._compute_sims(): diff --git a/pymode/libs/pylint/checkers/spelling.py b/pymode/libs/pylint/checkers/spelling.py index f6edd5db..bd03f379 100644 --- a/pymode/libs/pylint/checkers/spelling.py +++ b/pymode/libs/pylint/checkers/spelling.py @@ -15,24 +15,26 @@ """Checker for spelling errors in comments and docstrings. """ +import os import sys import tokenize import string import re -if sys.version_info[0] >= 3: - maketrans = str.maketrans -else: - maketrans = string.maketrans +try: + import enchant +except ImportError: + enchant = None +import six from pylint.interfaces import ITokenChecker, IAstroidChecker from pylint.checkers import BaseTokenChecker from pylint.checkers.utils import check_messages -try: - import enchant -except ImportError: - enchant = None +if sys.version_info[0] >= 3: + maketrans = str.maketrans +else: + maketrans = string.maketrans if enchant is not None: br = enchant.Broker() @@ -48,6 +50,7 @@ table = maketrans("", "") + class SpellingChecker(BaseTokenChecker): """Check spelling in comments and docstrings""" __implements__ = (ITokenChecker, IAstroidChecker) @@ -105,6 +108,11 @@ def open(self): # "pylint" appears in comments in pylint pragmas. self.ignore_list.extend(["param", "pylint"]) + # Expand tilde to allow e.g. spelling-private-dict-file = ~/.pylintdict + if self.config.spelling_private_dict_file: + self.config.spelling_private_dict_file = os.path.expanduser( + self.config.spelling_private_dict_file) + if self.config.spelling_private_dict_file: self.spelling_dict = enchant.DictWithPWL( dict_name, self.config.spelling_private_dict_file) @@ -220,17 +228,19 @@ def visit_module(self, node): self._check_docstring(node) @check_messages('wrong-spelling-in-docstring') - def visit_class(self, node): + def visit_classdef(self, node): if not self.initialized: return self._check_docstring(node) @check_messages('wrong-spelling-in-docstring') - def visit_function(self, node): + def visit_functiondef(self, node): if not self.initialized: return self._check_docstring(node) + visit_asyncfunctiondef = visit_functiondef + def _check_docstring(self, node): """check the node has any spelling errors""" docstring = node.doc @@ -238,6 +248,10 @@ def _check_docstring(self, node): return start_line = node.lineno + 1 + if six.PY2: + encoding = node.root().file_encoding + docstring = docstring.decode(encoding or sys.getdefaultencoding(), + 'replace') # Go through lines of docstring for idx, line in enumerate(docstring.splitlines()): diff --git a/pymode/libs/pylint/checkers/stdlib.py b/pymode/libs/pylint/checkers/stdlib.py index a3a61063..0ec3d6cf 100644 --- a/pymode/libs/pylint/checkers/stdlib.py +++ b/pymode/libs/pylint/checkers/stdlib.py @@ -15,26 +15,23 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """Checkers for various standard library functions.""" -import six import sys +import six + import astroid from astroid.bases import Instance - from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker from pylint.checkers import utils -TYPECHECK_COMPARISON_OPERATORS = frozenset(('is', 'is not', '==', '!=', 'in', 'not in')) -LITERAL_NODE_TYPES = (astroid.Const, astroid.Dict, astroid.List, astroid.Set) - +OPEN_FILES = {'open', 'file'} +UNITTEST_CASE = 'unittest.case' if sys.version_info >= (3, 0): OPEN_MODULE = '_io' - TYPE_QNAME = 'builtins.type' else: OPEN_MODULE = '__builtin__' - TYPE_QNAME = '__builtin__.type' def _check_mode_str(mode): @@ -79,15 +76,6 @@ def _check_mode_str(mode): return True -def _is_one_arg_pos_call(call): - """Is this a call with exactly 1 argument, - where that argument is positional? - """ - return (isinstance(call, astroid.CallFunc) - and len(call.args) == 1 - and not isinstance(call.args[0], astroid.Keyword)) - - class StdlibChecker(BaseChecker): __implements__ = (IAstroidChecker,) name = 'stdlib' @@ -112,25 +100,98 @@ class StdlibChecker(BaseChecker): 'a condition. If a constant is passed as parameter, that ' 'condition will be always true. In this case a warning ' 'should be emitted.'), - 'W1504': ('Using type() instead of isinstance() for a typecheck.', - 'unidiomatic-typecheck', - 'The idiomatic way to perform an explicit typecheck in ' - 'Python is to use isinstance(x, Y) rather than ' - 'type(x) == Y, type(x) is Y. Though there are unusual ' - 'situations where these give different results.') + 'W1505': ('Using deprecated method %s()', + 'deprecated-method', + 'The method is marked as deprecated and will be removed in ' + 'a future version of Python. Consider looking for an ' + 'alternative in the documentation.'), } - @utils.check_messages('bad-open-mode', 'redundant-unittest-assert') - def visit_callfunc(self, node): + deprecated = { + 0: [ + 'cgi.parse_qs', 'cgi.parse_qsl', + 'ctypes.c_buffer', + 'distutils.command.register.register.check_metadata', + 'distutils.command.sdist.sdist.check_metadata', + 'tkinter.Misc.tk_menuBar', + 'tkinter.Menu.tk_bindForTraversal', + ], + 2: { + (2, 6): [ + 'commands.getstatus', + 'os.popen2', + 'os.popen3', + 'os.popen4', + 'macostools.touched', + ], + (2, 7): [ + 'unittest.case.TestCase.assertEquals', + 'unittest.case.TestCase.assertNotEquals', + 'unittest.case.TestCase.assertAlmostEquals', + 'unittest.case.TestCase.assertNotAlmostEquals', + 'unittest.case.TestCase.assert_', + 'xml.etree.ElementTree.Element.getchildren', + 'xml.etree.ElementTree.Element.getiterator', + 'xml.etree.ElementTree.XMLParser.getiterator', + 'xml.etree.ElementTree.XMLParser.doctype', + ], + }, + 3: { + (3, 0): [ + 'inspect.getargspec', + 'unittest.case.TestCase._deprecate.deprecated_func', + ], + (3, 1): [ + 'base64.encodestring', 'base64.decodestring', + 'ntpath.splitunc', + ], + (3, 2): [ + 'cgi.escape', + 'configparser.RawConfigParser.readfp', + 'xml.etree.ElementTree.Element.getchildren', + 'xml.etree.ElementTree.Element.getiterator', + 'xml.etree.ElementTree.XMLParser.getiterator', + 'xml.etree.ElementTree.XMLParser.doctype', + ], + (3, 3): [ + 'inspect.getmoduleinfo', 'inspect.getmodulename', + 'logging.warn', 'logging.Logger.warn', + 'logging.LoggerAdapter.warn', + 'nntplib._NNTPBase.xpath', + 'platform.popen', + ], + (3, 4): [ + 'asyncio.tasks.async', + 'importlib.find_loader', + 'plistlib.readPlist', 'plistlib.writePlist', + 'plistlib.readPlistFromBytes', + 'plistlib.writePlistToBytes', + 'xml.etree.ElementTree.iterparse', + ], + (3, 5): [ + 'fractions.gcd', + 'inspect.getfullargspec', 'inspect.getargvalues', + 'inspect.formatargspec', 'inspect.formatargvalues', + 'inspect.getcallargs', + 'platform.linux_distribution', 'platform.dist', + ], + }, + } + + @utils.check_messages('bad-open-mode', 'redundant-unittest-assert', + 'deprecated-method') + def visit_call(self, node): """Visit a CallFunc node.""" - if hasattr(node, 'func'): - infer = utils.safe_infer(node.func) - if infer: - if infer.root().name == OPEN_MODULE: - if getattr(node.func, 'name', None) in ('open', 'file'): + try: + for inferred in node.func.infer(): + if inferred.root().name == OPEN_MODULE: + if getattr(node.func, 'name', None) in OPEN_FILES: self._check_open_mode(node) - if infer.root().name == 'unittest.case': - self._check_redundant_assert(node, infer) + if inferred.root().name == UNITTEST_CASE: + self._check_redundant_assert(node, inferred) + self._check_deprecated_method(node, inferred) + except astroid.InferenceError: + return @utils.check_messages('boolean-datetime') def visit_unaryop(self, node): @@ -150,13 +211,34 @@ def visit_boolop(self, node): for value in node.values: self._check_datetime(value) - @utils.check_messages('unidiomatic-typecheck') - def visit_compare(self, node): - operator, right = node.ops[0] - if operator in TYPECHECK_COMPARISON_OPERATORS: - left = node.left - if _is_one_arg_pos_call(left): - self._check_type_x_is_y(node, left, operator, right) + def _check_deprecated_method(self, node, inferred): + py_vers = sys.version_info[0] + + if isinstance(node.func, astroid.Attribute): + func_name = node.func.attrname + elif isinstance(node.func, astroid.Name): + func_name = node.func.name + else: + # Not interested in other nodes. + return + + # Reject nodes which aren't of interest to us. + acceptable_nodes = (astroid.BoundMethod, + astroid.UnboundMethod, + astroid.FunctionDef) + if not isinstance(inferred, acceptable_nodes): + return + + qname = inferred.qname() + if qname in self.deprecated[0]: + self.add_message('deprecated-method', node=node, + args=(func_name, )) + else: + for since_vers, func_list in self.deprecated[py_vers].items(): + if since_vers <= sys.version_info and qname in func_list: + self.add_message('deprecated-method', node=node, + args=(func_name, )) + break def _check_redundant_assert(self, node, infer): if (isinstance(infer, astroid.BoundMethod) and @@ -192,24 +274,6 @@ def _check_open_mode(self, node): self.add_message('bad-open-mode', node=node, args=mode_arg.value) - def _check_type_x_is_y(self, node, left, operator, right): - """Check for expressions like type(x) == Y.""" - left_func = utils.safe_infer(left.func) - if not (isinstance(left_func, astroid.Class) - and left_func.qname() == TYPE_QNAME): - return - - if operator in ('is', 'is not') and _is_one_arg_pos_call(right): - right_func = utils.safe_infer(right.func) - if (isinstance(right_func, astroid.Class) - and right_func.qname() == TYPE_QNAME): - # type(x) == type(a) - right_arg = utils.safe_infer(right.args[0]) - if not isinstance(right_arg, LITERAL_NODE_TYPES): - # not e.g. type(x) == type([]) - return - self.add_message('unidiomatic-typecheck', node=node) - def register(linter): """required method to auto register this checker """ diff --git a/pymode/libs/pylint/checkers/strings.py b/pymode/libs/pylint/checkers/strings.py index 8892c2cc..eca7dcdf 100644 --- a/pymode/libs/pylint/checkers/strings.py +++ b/pymode/libs/pylint/checkers/strings.py @@ -23,15 +23,14 @@ import string import numbers -import astroid +import six +import astroid from pylint.interfaces import ITokenChecker, IAstroidChecker, IRawChecker from pylint.checkers import BaseChecker, BaseTokenChecker from pylint.checkers import utils from pylint.checkers.utils import check_messages -import six - _PY3K = sys.version_info[:2] >= (3, 0) _PY27 = sys.version_info[:2] == (2, 7) @@ -62,7 +61,7 @@ 'W1301': ("Unused key %r in format string dictionary", "unused-format-string-key", "Used when a format string that uses named conversion specifiers \ - is used with a dictionary that conWtains keys not required by the \ + is used with a dictionary that contains keys not required by the \ format string."), 'E1304': ("Missing key %r in format string dictionary", "missing-format-string-key", @@ -77,7 +76,10 @@ "too-few-format-args", "Used when a format string that uses unnamed conversion \ specifiers is given too few arguments"), - + 'E1310': ("Suspicious argument in %s.%s call", + "bad-str-strip-call", + "The argument to a str.{l,r,}strip call contains a" + " duplicate character, "), 'W1302': ("Invalid format string", "bad-format-string", "Used when a PEP 3101 format string is invalid.", @@ -114,12 +116,12 @@ {'minversion': (2, 7)}) } -OTHER_NODES = (astroid.Const, astroid.List, astroid.Backquote, - astroid.Lambda, astroid.Function, - astroid.ListComp, astroid.SetComp, astroid.GenExpr) +OTHER_NODES = (astroid.Const, astroid.List, astroid.Repr, + astroid.Lambda, astroid.FunctionDef, + astroid.ListComp, astroid.SetComp, astroid.GeneratorExp) if _PY3K: - import _string + import _string # pylint: disable=wrong-import-position, wrong-import-order def split_format_field_names(format_string): return _string.formatter_field_name_split(format_string) @@ -157,9 +159,18 @@ def collect_string_fields(format_string): if nested: for field in collect_string_fields(nested): yield field - except ValueError: - # probably the format string is invalid - # should we check the argument of the ValueError? + except ValueError as exc: + # Probably the format string is invalid. + if exc.args[0].startswith("cannot switch from manual"): + # On Jython, parsing a string with both manual + # and automatic positions will fail with a ValueError, + # while on CPython it will simply return the fields, + # the validation being done in the interpreter (?). + # We're just returning two mixed fields in order + # to trigger the format-combined-specification check. + yield "" + yield "1" + return raise utils.IncompleteFormatString(format_string) def parse_format_method_string(format_string): @@ -189,19 +200,18 @@ def parse_format_method_string(format_string): return keys, num_args, len(manual_pos_arg) def get_args(callfunc): - """ Get the arguments from the given `CallFunc` node. + """Get the arguments from the given `CallFunc` node. + Return a tuple, where the first element is the number of positional arguments and the second element is the keyword arguments in a dict. """ - positional = 0 - named = {} - - for arg in callfunc.args: - if isinstance(arg, astroid.Keyword): - named[arg.arg] = utils.safe_infer(arg.value) - else: - positional += 1 + if callfunc.keywords: + named = {arg.arg: utils.safe_infer(arg.value) + for arg in callfunc.keywords} + else: + named = {} + positional = len(callfunc.args) return positional, named def get_access_path(key, parts): @@ -312,18 +322,8 @@ def visit_binop(self, node): self.add_message('too-few-format-args', node=node) -class StringMethodsChecker(BaseChecker): - __implements__ = (IAstroidChecker,) - name = 'string' - msgs = { - 'E1310': ("Suspicious argument in %s.%s call", - "bad-str-strip-call", - "The argument to a str.{l,r,}strip call contains a" - " duplicate character, "), - } - @check_messages(*(MSGS.keys())) - def visit_callfunc(self, node): + def visit_call(self, node): func = utils.safe_infer(node.func) if (isinstance(func, astroid.BoundMethod) and isinstance(func.bound, astroid.Instance) @@ -352,7 +352,7 @@ def _check_new_format(self, node, func): # # fmt = 'some string {}'.format # fmt('arg') - if (isinstance(node.func, astroid.Getattr) + if (isinstance(node.func, astroid.Attribute) and not isinstance(node.func.expr, astroid.Const)): return try: @@ -361,8 +361,10 @@ def _check_new_format(self, node, func): return if not isinstance(strnode, astroid.Const): return + if not isinstance(strnode.value, six.string_types): + return + if node.starargs or node.kwargs: - # TODO: Don't complicate the logic, skip these for now. return try: positional, named = get_args(node) @@ -482,6 +484,8 @@ def _check_new_format_specifiers(self, node, fields, named): previous = previous.getitem(specifier) except (IndexError, TypeError): warn_error = True + except astroid.InferenceError: + break else: try: # Lookup __getitem__ in the current node, @@ -611,5 +615,4 @@ def process_non_raw_string_token(self, prefix, string_body, start_row): def register(linter): """required method to auto register this checker """ linter.register_checker(StringFormatChecker(linter)) - linter.register_checker(StringMethodsChecker(linter)) linter.register_checker(StringConstantChecker(linter)) diff --git a/pymode/libs/pylint/checkers/typecheck.py b/pymode/libs/pylint/checkers/typecheck.py index 9f074ae0..8d12039b 100644 --- a/pymode/libs/pylint/checkers/typecheck.py +++ b/pymode/libs/pylint/checkers/typecheck.py @@ -16,18 +16,79 @@ """try to find more bugs in the code using astroid inference capabilities """ +import collections +import fnmatch import re import shlex +import sys + +import six import astroid -from astroid import InferenceError, NotFoundError, YES, Instance -from astroid.bases import BUILTINS +import astroid.context +import astroid.arguments +from astroid import exceptions +from astroid import objects +from astroid import bases from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE from pylint.checkers import BaseChecker from pylint.checkers.utils import ( - safe_infer, is_super, - check_messages, decorated_with_property) + is_super, check_messages, decorated_with_property, + decorated_with, node_ignores_exception, + is_iterable, is_mapping, supports_membership_test, + is_comprehension, is_inside_abstract_class, + supports_subscript, + safe_infer, + has_known_bases) +from pylint import utils + + +_ZOPE_DEPRECATED = ( + "This option is deprecated. Use generated-members instead." +) +BUILTINS = six.moves.builtins.__name__ +STR_FORMAT = "%s.str.format" % BUILTINS + + +def _unflatten(iterable): + for index, elem in enumerate(iterable): + if (isinstance(elem, collections.Sequence) and + not isinstance(elem, six.string_types)): + for elem in _unflatten(elem): + yield elem + elif elem and not index: + # We're interested only in the first element. + yield elem + + +def _is_owner_ignored(owner, name, ignored_classes, ignored_modules): + """Check if the given owner should be ignored + + This will verify if the owner's module is in *ignored_modules* + or the owner's module fully qualified name is in *ignored_modules* + or if the *ignored_modules* contains a pattern which catches + the fully qualified name of the module. + + Also, similar checks are done for the owner itself, if its name + matches any name from the *ignored_classes* or if its qualified + name can be found in *ignored_classes*. + """ + ignored_modules = set(ignored_modules) + module_name = owner.root().name + module_qname = owner.root().qname() + if any(module_name in ignored_modules or + module_qname in ignored_modules or + fnmatch.fnmatch(module_qname, ignore) for ignore in ignored_modules): + return True + + ignored_classes = set(ignored_classes) + if hasattr(owner, 'qname'): + qname = owner.qname() + else: + qname = '' + return any(name == ignore or qname == ignore for ignore in ignored_classes) + MSGS = { 'E1101': ('%s %r has no %r member', @@ -42,11 +103,6 @@ 'assignment-from-no-return', 'Used when an assignment is done on a function call but the \ inferred function doesn\'t return anything.'), - 'W1111': ('Assigning to function call which only returns None', - 'assignment-from-none', - 'Used when an assignment is done on a function call but the \ - inferred function returns nothing but None.'), - 'E1120': ('No value for argument %s in %s call', 'no-value-for-parameter', 'Used when a function call passes too few arguments.'), @@ -77,12 +133,81 @@ 'invalid-slice-index', 'Used when a slice index is not an integer, None, or an object \ with an __index__ method.'), + 'E1128': ('Assigning to function call which only returns None', + 'assignment-from-none', + 'Used when an assignment is done on a function call but the ' + 'inferred function returns nothing but None.', + {'old_names': [('W1111', 'assignment-from-none')]}), + 'E1129': ("Context manager '%s' doesn't implement __enter__ and __exit__.", + 'not-context-manager', + 'Used when an instance in a with statement doesn\'t implement ' + 'the context manager protocol(__enter__/__exit__).'), + 'E1130': ('%s', + 'invalid-unary-operand-type', + 'Emitted when an unary operand is used on an object which does not ' + 'support this type of operation'), + 'E1131': ('%s', + 'unsupported-binary-operation', + 'Emitted when a binary arithmetic operation between two ' + 'operands is not supported.'), + 'E1132': ('Got multiple values for keyword argument %r in function call', + 'repeated-keyword', + 'Emitted when a function call got multiple values for a keyword.'), + 'E1135': ("Value '%s' doesn't support membership test", + 'unsupported-membership-test', + 'Emitted when an instance in membership test expression doesn\'t' + 'implement membership protocol (__contains__/__iter__/__getitem__)'), + 'E1136': ("Value '%s' is unsubscriptable", + 'unsubscriptable-object', + "Emitted when a subscripted value doesn't support subscription" + "(i.e. doesn't define __getitem__ method)"), } # builtin sequence types in Python 2 and 3. SEQUENCE_TYPES = set(['str', 'unicode', 'list', 'tuple', 'bytearray', 'xrange', 'range', 'bytes', 'memoryview']) + +def _emit_no_member(node, owner, owner_name, ignored_mixins): + """Try to see if no-member should be emitted for the given owner. + + The following cases are ignored: + + * the owner is a function and it has decorators. + * the owner is an instance and it has __getattr__, __getattribute__ implemented + * the module is explicitly ignored from no-member checks + * the owner is a class and the name can be found in its metaclass. + * The access node is protected by an except handler, which handles + AttributeError, Exception or bare except. + """ + if node_ignores_exception(node, AttributeError): + return False + # skip None anyway + if isinstance(owner, astroid.Const) and owner.value is None: + return False + if is_super(owner) or getattr(owner, 'type', None) == 'metaclass': + return False + if ignored_mixins and owner_name[-5:].lower() == 'mixin': + return False + if isinstance(owner, astroid.FunctionDef) and owner.decorators: + return False + if isinstance(owner, astroid.Instance): + if owner.has_dynamic_getattr() or not has_known_bases(owner): + return False + if isinstance(owner, objects.Super): + # Verify if we are dealing with an invalid Super object. + # If it is invalid, then there's no point in checking that + # it has the required attribute. Also, don't fail if the + # MRO is invalid. + try: + owner.super_mro() + except (exceptions.MroError, exceptions.SuperError): + return False + if not all(map(has_known_bases, owner.type.mro())): + return False + return True + + def _determine_callable(callable_obj): # Ordering is important, since BoundMethod is a subclass of UnboundMethod, # and Function inherits Lambda. @@ -91,31 +216,36 @@ def _determine_callable(callable_obj): return callable_obj, 1, callable_obj.type elif isinstance(callable_obj, astroid.UnboundMethod): return callable_obj, 0, 'unbound method' - elif isinstance(callable_obj, astroid.Function): + elif isinstance(callable_obj, astroid.FunctionDef): return callable_obj, 0, callable_obj.type elif isinstance(callable_obj, astroid.Lambda): return callable_obj, 0, 'lambda' - elif isinstance(callable_obj, astroid.Class): + elif isinstance(callable_obj, astroid.ClassDef): # Class instantiation, lookup __new__ instead. # If we only find object.__new__, we can safely check __init__ - # instead. + # instead. If __new__ belongs to builtins, then we look + # again for __init__ in the locals, since we won't have + # argument information for the builtin __new__ function. try: # Use the last definition of __new__. new = callable_obj.local_attr('__new__')[-1] - except astroid.NotFoundError: + except exceptions.NotFoundError: new = None - if not new or new.parent.scope().name == 'object': + from_object = new and new.parent.scope().name == 'object' + from_builtins = new and new.root().name in sys.builtin_module_names + + if not new or from_object or from_builtins: try: # Use the last definition of __init__. callable_obj = callable_obj.local_attr('__init__')[-1] - except astroid.NotFoundError: + except exceptions.NotFoundError: # do nothing, covered by no-init. raise ValueError else: callable_obj = new - if not isinstance(callable_obj, astroid.Function): + if not isinstance(callable_obj, astroid.FunctionDef): raise ValueError # both have an extra implicit 'cls'/'self' argument. return callable_obj, 1, 'constructor' @@ -144,93 +274,87 @@ class should be ignored. A mixin class is detected if its name ends with \ {'default': (), 'type': 'csv', 'metavar': '', - 'help': 'List of module names for which member attributes \ -should not be checked (useful for modules/projects where namespaces are \ -manipulated during runtime and thus existing member attributes cannot be \ -deduced by static analysis'}, + 'help': 'List of module names for which member attributes ' + 'should not be checked (useful for modules/projects ' + 'where namespaces are manipulated during runtime and ' + 'thus existing member attributes cannot be ' + 'deduced by static analysis. It supports qualified ' + 'module names, as well as Unix pattern matching.'} ), ('ignored-classes', - {'default' : ('SQLObject',), + {'default' : (), 'type' : 'csv', 'metavar' : '', - 'help' : 'List of classes names for which member attributes \ -should not be checked (useful for classes with attributes dynamically set).'} + 'help' : 'List of classes names for which member attributes ' + 'should not be checked (useful for classes with ' + 'attributes dynamically set). This supports ' + 'can work with qualified names.'} ), - ('zope', - {'default' : False, 'type' : 'yn', 'metavar': '', - 'help' : 'When zope mode is activated, add a predefined set \ -of Zope acquired attributes to generated-members.'} - ), + ('zope', utils.deprecated_option(opt_type='yn', + help_msg=_ZOPE_DEPRECATED)), + ('generated-members', - {'default' : ('REQUEST', 'acl_users', 'aq_parent'), + {'default' : (), 'type' : 'string', 'metavar' : '', 'help' : 'List of members which are set dynamically and \ -missed by pylint inference system, and so shouldn\'t trigger E0201 when \ +missed by pylint inference system, and so shouldn\'t trigger E1101 when \ accessed. Python regular expressions are accepted.'} ), ) def open(self): # do this in open since config not fully initialized in __init__ - self.generated_members = list(self.config.generated_members) - if self.config.zope: - self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent')) + # generated_members may contain regular expressions + # (surrounded by quote `"` and followed by a comma `,`) + # REQUEST,aq_parent,"[a-zA-Z]+_set{1,2}"' => + # ('REQUEST', 'aq_parent', '[a-zA-Z]+_set{1,2}') + if isinstance(self.config.generated_members, str): + gen = shlex.shlex(self.config.generated_members) + gen.whitespace += ',' + gen.wordchars += '[]-+' + self.config.generated_members = tuple(tok.strip('"') for tok in gen) - def visit_assattr(self, node): - if isinstance(node.ass_type(), astroid.AugAssign): - self.visit_getattr(node) + def visit_assignattr(self, node): + if isinstance(node.assign_type(), astroid.AugAssign): + self.visit_attribute(node) def visit_delattr(self, node): - self.visit_getattr(node) + self.visit_attribute(node) @check_messages('no-member') - def visit_getattr(self, node): + def visit_attribute(self, node): """check that the accessed attribute exists - to avoid to much false positives for now, we'll consider the code as + to avoid too much false positives for now, we'll consider the code as correct if a single of the inferred nodes has the accessed attribute. function/method, super call and metaclasses are ignored """ - # generated_members may containt regular expressions - # (surrounded by quote `"` and followed by a comma `,`) - # REQUEST,aq_parent,"[a-zA-Z]+_set{1,2}"' => - # ('REQUEST', 'aq_parent', '[a-zA-Z]+_set{1,2}') - if isinstance(self.config.generated_members, str): - gen = shlex.shlex(self.config.generated_members) - gen.whitespace += ',' - gen.wordchars += '[]-+' - self.config.generated_members = tuple(tok.strip('"') for tok in gen) for pattern in self.config.generated_members: # attribute is marked as generated, stop here if re.match(pattern, node.attrname): return + try: infered = list(node.expr.infer()) - except InferenceError: + except exceptions.InferenceError: return # list of (node, nodename) which are missing the attribute missingattr = set() - ignoremim = self.config.ignore_mixin_members inference_failure = False for owner in infered: # skip yes object - if owner is YES: + if owner is astroid.YES: inference_failure = True continue - # skip None anyway - if isinstance(owner, astroid.Const) and owner.value is None: - continue - # XXX "super" / metaclass call - if is_super(owner) or getattr(owner, 'type', None) == 'metaclass': - continue - name = getattr(owner, 'name', 'None') - if name in self.config.ignored_classes: - continue - if ignoremim and name[-5:].lower() == 'mixin': + + name = getattr(owner, 'name', None) + if _is_owner_ignored(owner, name, self.config.ignored_classes, + self.config.ignored_modules): continue + try: if not [n for n in owner.getattr(node.attrname) if not isinstance(n.statement(), astroid.AugAssign)]: @@ -239,28 +363,17 @@ def visit_getattr(self, node): except AttributeError: # XXX method / function continue - except NotFoundError: - if isinstance(owner, astroid.Function) and owner.decorators: - continue - if isinstance(owner, Instance) and owner.has_dynamic_getattr(): - continue - # explicit skipping of module member access - if owner.root().name in self.config.ignored_modules: + except exceptions.NotFoundError: + # This can't be moved before the actual .getattr call, + # because there can be more values inferred and we are + # stopping after the first one which has the attribute in question. + # The problem is that if the first one has the attribute, + # but we continue to the next values which doesn't have the + # attribute, then we'll have a false positive. + # So call this only after the call has been made. + if not _emit_no_member(node, owner, name, + self.config.ignore_mixin_members): continue - if isinstance(owner, astroid.Class): - # Look up in the metaclass only if the owner is itself - # a class. - # TODO: getattr doesn't return by default members - # from the metaclass, because handling various cases - # of methods accessible from the metaclass itself - # and/or subclasses only is too complicated for little to - # no benefit. - metaclass = owner.metaclass() - try: - if metaclass and metaclass.getattr(node.attrname): - continue - except NotFoundError: - pass missingattr.add((owner, name)) continue # stop on the first found @@ -270,7 +383,7 @@ def visit_getattr(self, node): # message for infered nodes done = set() for owner, name in missingattr: - if isinstance(owner, Instance): + if isinstance(owner, astroid.Instance): actual = owner._proxied else: actual = owner @@ -288,18 +401,18 @@ def visit_assign(self, node): """check that if assigning to a function call, the function is possibly returning something valuable """ - if not isinstance(node.value, astroid.CallFunc): + if not isinstance(node.value, astroid.Call): return function_node = safe_infer(node.value.func) # skip class, generator and incomplete function definition - if not (isinstance(function_node, astroid.Function) and + if not (isinstance(function_node, astroid.FunctionDef) and function_node.root().fully_defined()): return if function_node.is_generator() \ or function_node.is_abstract(pass_is_abstract=False): return returns = list(function_node.nodes_of_class(astroid.Return, - skip_klass=astroid.Function)) + skip_klass=astroid.FunctionDef)) if len(returns) == 0: self.add_message('assignment-from-no-return', node=node) else: @@ -316,7 +429,7 @@ def _check_uninferable_callfunc(self, node): Check that the given uninferable CallFunc node does not call an actual function. """ - if not isinstance(node.func, astroid.Getattr): + if not isinstance(node.func, astroid.Attribute): return # Look for properties. First, obtain @@ -335,13 +448,13 @@ def _check_uninferable_callfunc(self, node): try: attrs = klass._proxied.getattr(node.func.attrname) - except astroid.NotFoundError: + except exceptions.NotFoundError: return for attr in attrs: if attr is astroid.YES: continue - if not isinstance(attr, astroid.Function): + if not isinstance(attr, astroid.FunctionDef): continue # Decorated, see if it is decorated with a property. @@ -355,21 +468,45 @@ def _check_uninferable_callfunc(self, node): args=node.func.as_string()) break + @staticmethod + def _no_context_variadic(node): + """Verify if the given call node has variadic nodes without context + + This is a workaround for handling cases of nested call functions + which don't have the specific call context at hand. + Variadic arguments (variable positional arguments and variable + keyword arguments) are inferred, inherently wrong, by astroid + as a Tuple, respectively a Dict with empty elements. + This can lead pylint to believe that a function call receives + too few arguments. + """ + for arg in node.args: + if not isinstance(arg, astroid.Starred): + continue + + inferred = safe_infer(arg.value) + if isinstance(inferred, astroid.Tuple): + length = len(inferred.elts) + elif isinstance(inferred, astroid.Dict): + length = len(inferred.items) + else: + return False + if not length and isinstance(inferred.statement(), astroid.FunctionDef): + return True + return False + @check_messages(*(list(MSGS.keys()))) - def visit_callfunc(self, node): + def visit_call(self, node): """check that called functions/methods are inferred to callable objects, and that the arguments passed to the function match the parameters in the inferred function's definition """ # Build the set of keyword arguments, checking for duplicate keywords, # and count the positional arguments. - keyword_args = set() - num_positional_args = 0 - for arg in node.args: - if isinstance(arg, astroid.Keyword): - keyword_args.add(arg.arg) - else: - num_positional_args += 1 + call_site = astroid.arguments.CallSite.from_call(node) + num_positional_args = len(call_site.positional_arguments) + keyword_args = list(call_site.keyword_arguments.keys()) + no_context_variadic = self._no_context_variadic(node) called = safe_infer(node.func) # only function, generator and object defining __call__ are allowed @@ -385,14 +522,24 @@ def visit_callfunc(self, node): # Any error occurred during determining the function type, most of # those errors are handled by different warnings. return + num_positional_args += implicit_args if called.args.args is None: # Built-in functions have no argument information. return if len(called.argnames()) != len(set(called.argnames())): - # Duplicate parameter name (see E9801). We can't really make sense - # of the function call in this case, so just return. + # Duplicate parameter name (see duplicate-argument). We can't really + # make sense of the function call in this case, so just return. + return + + # Warn about duplicated keyword arguments, such as `f=24, **{'f': 24}` + for keyword in call_site.duplicated_keywords: + self.add_message('repeated-keyword', + node=node, args=(keyword, )) + + if call_site.has_invalid_arguments() or call_site.has_invalid_keywords(): + # Can't make sense of this. return # Analyze the list of formal parameters. @@ -405,13 +552,10 @@ def visit_callfunc(self, node): # Don't store any parameter names within the tuple, since those # are not assignable from keyword arguments. else: - if isinstance(arg, astroid.Keyword): - name = arg.arg - else: - assert isinstance(arg, astroid.AssName) - # This occurs with: - # def f( (a), (b) ): pass - name = arg.name + assert isinstance(arg, astroid.AssignName) + # This occurs with: + # def f( (a), (b) ): pass + name = arg.name parameter_name_to_index[name] = i if i >= num_mandatory_parameters: defval = called.args.defaults[i - num_mandatory_parameters] @@ -424,7 +568,7 @@ def visit_callfunc(self, node): if isinstance(arg, astroid.Keyword): name = arg.arg else: - assert isinstance(arg, astroid.AssName) + assert isinstance(arg, astroid.AssignName) name = arg.name kwparams[name] = [called.args.kw_defaults[i], False] @@ -450,8 +594,15 @@ def visit_callfunc(self, node): i = parameter_name_to_index[keyword] if parameters[i][1]: # Duplicate definition of function parameter. - self.add_message('redundant-keyword-arg', - node=node, args=(keyword, callable_name)) + + # Might be too hardcoded, but this can actually + # happen when using str.format and `self` is passed + # by keyword argument, as in `.format(self=self)`. + # It's perfectly valid to so, so we're just skipping + # it if that's the case. + if not (keyword == 'self' and called.qname() == STR_FORMAT): + self.add_message('redundant-keyword-arg', + node=node, args=(keyword, callable_name)) else: parameters[i][1] = True elif keyword in kwparams: @@ -469,24 +620,8 @@ def visit_callfunc(self, node): self.add_message('unexpected-keyword-arg', node=node, args=(keyword, callable_name)) - # 3. Match the *args, if any. Note that Python actually processes - # *args _before_ any keyword arguments, but we wait until after - # looking at the keyword arguments so as to make a more conservative - # guess at how many values are in the *args sequence. - if node.starargs is not None: - for i in range(num_positional_args, len(parameters)): - [(name, defval), assigned] = parameters[i] - # Assume that *args provides just enough values for all - # non-default parameters after the last parameter assigned by - # the positional arguments but before the first parameter - # assigned by the keyword arguments. This is the best we can - # get without generating any false positives. - if (defval is not None) or assigned: - break - parameters[i][1] = True - - # 4. Match the **kwargs, if any. - if node.kwargs is not None: + # 3. Match the **kwargs, if any. + if node.kwargs: for i, [(name, defval), assigned] in enumerate(parameters): # Assume that *kwargs provides values for all remaining # unassigned named parameters. @@ -504,8 +639,10 @@ def visit_callfunc(self, node): display_name = '' else: display_name = repr(name) - self.add_message('no-value-for-parameter', node=node, - args=(display_name, callable_name)) + # TODO(cpopa): this should be removed after PyCQA/astroid/issues/177 + if not no_context_variadic: + self.add_message('no-value-for-parameter', node=node, + args=(display_name, callable_name)) for name in kwparams: defval, assigned = kwparams[name] @@ -523,13 +660,11 @@ def visit_extslice(self, node): def visit_index(self, node): if not node.parent or not hasattr(node.parent, "value"): return - # Look for index operations where the parent is a sequence type. # If the types can be determined, only allow indices to be int, # slice or instances with __index__. - parent_type = safe_infer(node.parent.value) - if not isinstance(parent_type, (astroid.Class, astroid.Instance)): + if not isinstance(parent_type, (astroid.ClassDef, astroid.Instance)): return # Determine what method on the parent this index will use @@ -552,10 +687,9 @@ def visit_index(self, node): if methods is astroid.YES: return itemmethod = methods[0] - except (astroid.NotFoundError, IndexError): + except (exceptions.NotFoundError, IndexError): return - - if not isinstance(itemmethod, astroid.Function): + if not isinstance(itemmethod, astroid.FunctionDef): return if itemmethod.root().name != BUILTINS: return @@ -573,7 +707,6 @@ def visit_index(self, node): index_type = safe_infer(node) if index_type is None or index_type is astroid.YES: return - # Constants must be of type int if isinstance(index_type, astroid.Const): if isinstance(index_type.value, int): @@ -585,8 +718,13 @@ def visit_index(self, node): try: index_type.getattr('__index__') return - except astroid.NotFoundError: + except exceptions.NotFoundError: pass + elif isinstance(index_type, astroid.Slice): + # Delegate to visit_slice. A slice can be present + # here after inferring the index node, which could + # be a `slice(...)` call for instance. + return self.visit_slice(index_type) # Anything else is an error self.add_message('invalid-sequence-index', node=node) @@ -616,12 +754,221 @@ def visit_slice(self, node): try: index_type.getattr('__index__') return - except astroid.NotFoundError: + except exceptions.NotFoundError: pass # Anything else is an error self.add_message('invalid-slice-index', node=node) + @check_messages('not-context-manager') + def visit_with(self, node): + for ctx_mgr, _ in node.items: + context = astroid.context.InferenceContext() + infered = safe_infer(ctx_mgr, context=context) + if infered is None or infered is astroid.YES: + continue + + if isinstance(infered, bases.Generator): + # Check if we are dealing with a function decorated + # with contextlib.contextmanager. + if decorated_with(infered.parent, ['contextlib.contextmanager']): + continue + # If the parent of the generator is not the context manager itself, + # that means that it could have been returned from another + # function which was the real context manager. + # The following approach is more of a hack rather than a real + # solution: walk all the inferred statements for the + # given *ctx_mgr* and if you find one function scope + # which is decorated, consider it to be the real + # manager and give up, otherwise emit not-context-manager. + # See the test file for not_context_manager for a couple + # of self explaining tests. + for path in six.moves.filter(None, _unflatten(context.path)): + scope = path.scope() + if not isinstance(scope, astroid.FunctionDef): + continue + if decorated_with(scope, ['contextlib.contextmanager']): + break + else: + self.add_message('not-context-manager', + node=node, args=(infered.name, )) + else: + try: + infered.getattr('__enter__') + infered.getattr('__exit__') + except exceptions.NotFoundError: + if isinstance(infered, astroid.Instance): + # If we do not know the bases of this class, + # just skip it. + if not has_known_bases(infered): + continue + # Just ignore mixin classes. + if self.config.ignore_mixin_members: + if infered.name[-5:].lower() == 'mixin': + continue + + self.add_message('not-context-manager', + node=node, args=(infered.name, )) + + # Disabled until we'll have a more capable astroid. + @check_messages('invalid-unary-operand-type') + def _visit_unaryop(self, node): + """Detect TypeErrors for unary operands.""" + + for error in node.type_errors(): + # Let the error customize its output. + self.add_message('invalid-unary-operand-type', + args=str(error), node=node) + + @check_messages('unsupported-binary-operation') + def _visit_binop(self, node): + """Detect TypeErrors for binary arithmetic operands.""" + self._check_binop_errors(node) + + @check_messages('unsupported-binary-operation') + def _visit_augassign(self, node): + """Detect TypeErrors for augmented binary arithmetic operands.""" + self._check_binop_errors(node) + + def _check_binop_errors(self, node): + for error in node.type_errors(): + # Let the error customize its output. + self.add_message('unsupported-binary-operation', + args=str(error), node=node) + + def _check_membership_test(self, node): + if is_inside_abstract_class(node): + return + if is_comprehension(node): + return + infered = safe_infer(node) + if infered is None or infered is astroid.YES: + return + if not supports_membership_test(infered): + self.add_message('unsupported-membership-test', + args=node.as_string(), + node=node) + + @check_messages('unsupported-membership-test') + def visit_compare(self, node): + if len(node.ops) != 1: + return + operator, right = node.ops[0] + if operator in ['in', 'not in']: + self._check_membership_test(right) + + @check_messages('unsubscriptable-object') + def visit_subscript(self, node): + if isinstance(node.value, (astroid.ListComp, astroid.DictComp)): + return + if isinstance(node.value, astroid.SetComp): + self.add_message('unsubscriptable-object', + args=node.value.as_string(), + node=node.value) + return + + infered = safe_infer(node.value) + if infered is None or infered is astroid.YES: + return + + if is_inside_abstract_class(node): + return + + if not supports_subscript(infered): + self.add_message('unsubscriptable-object', + args=node.value.as_string(), + node=node.value) + + + +class IterableChecker(BaseChecker): + """ + Checks for non-iterables used in an iterable context. + Contexts include: + - for-statement + - starargs in function call + - `yield from`-statement + - list, dict and set comprehensions + - generator expressions + Also checks for non-mappings in function call kwargs. + """ + + __implements__ = (IAstroidChecker,) + name = 'iterable_check' + + msgs = {'E1133': ('Non-iterable value %s is used in an iterating context', + 'not-an-iterable', + 'Used when a non-iterable value is used in place where' + 'iterable is expected'), + 'E1134': ('Non-mapping value %s is used in a mapping context', + 'not-a-mapping', + 'Used when a non-mapping value is used in place where' + 'mapping is expected'), + } + + def _check_iterable(self, node): + if is_inside_abstract_class(node): + return + if is_comprehension(node): + return + infered = safe_infer(node) + if infered is None or infered is astroid.YES: + return + if not is_iterable(infered): + self.add_message('not-an-iterable', + args=node.as_string(), + node=node) + + def _check_mapping(self, node): + if is_inside_abstract_class(node): + return + if isinstance(node, astroid.DictComp): + return + infered = safe_infer(node) + if infered is None or infered is astroid.YES: + return + if not is_mapping(infered): + self.add_message('not-a-mapping', + args=node.as_string(), + node=node) + + @check_messages('not-an-iterable') + def visit_for(self, node): + self._check_iterable(node.iter) + + @check_messages('not-an-iterable') + def visit_yieldfrom(self, node): + self._check_iterable(node.value) + + @check_messages('not-an-iterable', 'not-a-mapping') + def visit_call(self, node): + for stararg in node.starargs: + self._check_iterable(stararg.value) + for kwarg in node.kwargs: + self._check_mapping(kwarg.value) + + @check_messages('not-an-iterable') + def visit_listcomp(self, node): + for gen in node.generators: + self._check_iterable(gen.iter) + + @check_messages('not-an-iterable') + def visit_dictcomp(self, node): + for gen in node.generators: + self._check_iterable(gen.iter) + + @check_messages('not-an-iterable') + def visit_setcomp(self, node): + for gen in node.generators: + self._check_iterable(gen.iter) + + @check_messages('not-an-iterable') + def visit_generatorexp(self, node): + for gen in node.generators: + self._check_iterable(gen.iter) + + def register(linter): """required method to auto register this checker """ linter.register_checker(TypeChecker(linter)) + linter.register_checker(IterableChecker(linter)) diff --git a/pymode/libs/pylint/checkers/utils.py b/pymode/libs/pylint/checkers/utils.py index 2cb01d55..a8df5cab 100644 --- a/pymode/libs/pylint/checkers/utils.py +++ b/pymode/libs/pylint/checkers/utils.py @@ -17,17 +17,21 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """some functions that may be useful for various checkers """ - +import functools import re import sys import string +import warnings + +import six +from six.moves import map, builtins # pylint: disable=redefined-builtin import astroid from astroid import scoped_nodes -from logilab.common.compat import builtins BUILTINS_NAME = builtins.__name__ -COMP_NODE_TYPES = astroid.ListComp, astroid.SetComp, astroid.DictComp, astroid.GenExpr +COMP_NODE_TYPES = (astroid.ListComp, astroid.SetComp, + astroid.DictComp, astroid.GeneratorExp) PY3K = sys.version_info[0] == 3 if not PY3K: @@ -36,6 +40,60 @@ EXCEPTIONS_MODULE = "builtins" ABC_METHODS = set(('abc.abstractproperty', 'abc.abstractmethod', 'abc.abstractclassmethod', 'abc.abstractstaticmethod')) +ITER_METHOD = '__iter__' +NEXT_METHOD = 'next' if six.PY2 else '__next__' +GETITEM_METHOD = '__getitem__' +CONTAINS_METHOD = '__contains__' +KEYS_METHOD = 'keys' + +# Dictionary which maps the number of expected parameters a +# special method can have to a set of special methods. +# The following keys are used to denote the parameters restrictions: +# +# * None: variable number of parameters +# * number: exactly that number of parameters +# * tuple: this are the odd ones. Basically it means that the function +# can work with any number of arguments from that tuple, +# although it's best to implement it in order to accept +# all of them. +_SPECIAL_METHODS_PARAMS = { + None: ('__new__', '__init__', '__call__'), + + 0: ('__del__', '__repr__', '__str__', '__bytes__', '__hash__', '__bool__', + '__dir__', '__len__', '__length_hint__', '__iter__', '__reversed__', + '__neg__', '__pos__', '__abs__', '__invert__', '__complex__', '__int__', + '__float__', '__neg__', '__pos__', '__abs__', '__complex__', '__int__', + '__float__', '__index__', '__enter__', '__aenter__', '__getnewargs_ex__', + '__getnewargs__', '__getstate__', '__reduce__', '__copy__', + '__unicode__', '__nonzero__', '__await__', '__aiter__', '__anext__'), + + 1: ('__format__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', + '__ge__', '__getattr__', '__getattribute__', '__delattr__', + '__delete__', '__instancecheck__', '__subclasscheck__', + '__getitem__', '__missing__', '__delitem__', '__contains__', + '__add__', '__sub__', '__mul__', '__truediv__', '__floordiv__', + '__mod__', '__divmod__', '__lshift__', '__rshift__', '__and__', + '__xor__', '__or__', '__radd__', '__rsub__', '__rmul__', '__rtruediv__', + '__rmod__', '__rdivmod__', '__rpow__', '__rlshift__', '__rrshift__', + '__rand__', '__rxor__', '__ror__', '__iadd__', '__isub__', '__imul__', + '__itruediv__', '__ifloordiv__', '__imod__', '__ilshift__', + '__irshift__', '__iand__', '__ixor__', '__ior__', '__ipow__', + '__setstate__', '__reduce_ex__', '__deepcopy__', '__cmp__', + '__matmul__', '__rmatmul__'), + + 2: ('__setattr__', '__get__', '__set__', '__setitem__'), + + 3: ('__exit__', '__aexit__'), + + (0, 1): ('__round__', ), +} + +SPECIAL_METHODS_PARAMS = { + name: params + for params, methods in _SPECIAL_METHODS_PARAMS.items() + for name in methods +} +PYMETHODS = set(SPECIAL_METHODS_PARAMS) class NoSuchArgumentError(Exception): @@ -67,39 +125,21 @@ def clobber_in_except(node): Returns (True, args for W0623) if assignment clobbers an existing variable, (False, None) otherwise. """ - if isinstance(node, astroid.AssAttr): + if isinstance(node, astroid.AssignAttr): return (True, (node.attrname, 'object %r' % (node.expr.as_string(),))) - elif isinstance(node, astroid.AssName): + elif isinstance(node, astroid.AssignName): name = node.name if is_builtin(name): return (True, (name, 'builtins')) else: stmts = node.lookup(name)[1] - if (stmts and not isinstance(stmts[0].ass_type(), + if (stmts and not isinstance(stmts[0].assign_type(), (astroid.Assign, astroid.AugAssign, astroid.ExceptHandler))): return (True, (name, 'outer scope (line %s)' % stmts[0].fromlineno)) return (False, None) -def safe_infer(node): - """return the inferred value for the given node. - Return None if inference failed or if there is some ambiguity (more than - one node has been inferred) - """ - try: - inferit = node.infer() - value = next(inferit) - except astroid.InferenceError: - return - try: - next(inferit) - return # None if there is ambiguity on the inferred node - except astroid.InferenceError: - return # there is some kind of ambiguity - except StopIteration: - return value - def is_super(node): """return True if the node is referencing the "super" builtin function """ @@ -122,10 +162,6 @@ def is_raising(body): return True return False -def is_empty(body): - """return true if the given node does nothing but 'pass'""" - return len(body) == 1 and isinstance(body[0], astroid.Pass) - builtins = builtins.__dict__.copy() SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__') @@ -133,14 +169,10 @@ def is_builtin_object(node): """Returns True if the given node is an object from the __builtin__ module.""" return node and node.root().name == BUILTINS_NAME -def is_builtin(name): # was is_native_builtin +def is_builtin(name): """return true if could be considered as a builtin defined by python """ - if name in builtins: - return True - if name in SPECIAL_BUILTINS: - return True - return False + return name in builtins or name in SPECIAL_BUILTINS def is_defined_before(var_node): """return True if the variable node is defined by a parent node (list, @@ -151,11 +183,11 @@ def is_defined_before(var_node): _node = var_node.parent while _node: if isinstance(_node, COMP_NODE_TYPES): - for ass_node in _node.nodes_of_class(astroid.AssName): + for ass_node in _node.nodes_of_class(astroid.AssignName): if ass_node.name == varname: return True elif isinstance(_node, astroid.For): - for ass_node in _node.target.nodes_of_class(astroid.AssName): + for ass_node in _node.target.nodes_of_class(astroid.AssignName): if ass_node.name == varname: return True elif isinstance(_node, astroid.With): @@ -163,17 +195,27 @@ def is_defined_before(var_node): if expr.parent_of(var_node): break if (ids and - isinstance(ids, astroid.AssName) and + isinstance(ids, astroid.AssignName) and ids.name == varname): return True - elif isinstance(_node, (astroid.Lambda, astroid.Function)): + elif isinstance(_node, (astroid.Lambda, astroid.FunctionDef)): if _node.args.is_argument(varname): + # If the name is found inside a default value + # of a function, then let the search continue + # in the parent's tree. + if _node.args.parent_of(var_node): + try: + _node.args.default_value(varname) + _node = _node.parent + continue + except astroid.NoDefault: + pass return True if getattr(_node, 'name', None) == varname: return True break elif isinstance(_node, astroid.ExceptHandler): - if isinstance(_node.name, astroid.AssName): + if isinstance(_node.name, astroid.AssignName): ass_node = _node.name if ass_node.name == varname: return True @@ -183,10 +225,10 @@ def is_defined_before(var_node): _node = stmt.previous_sibling() lineno = stmt.fromlineno while _node and _node.fromlineno == lineno: - for ass_node in _node.nodes_of_class(astroid.AssName): + for ass_node in _node.nodes_of_class(astroid.AssignName): if ass_node.name == varname: return True - for imp_node in _node.nodes_of_class((astroid.From, astroid.Import)): + for imp_node in _node.nodes_of_class((astroid.ImportFrom, astroid.Import)): if varname in [name[1] or name[0] for name in imp_node.names]: return True _node = _node.previous_sibling() @@ -197,7 +239,7 @@ def is_func_default(node): value """ parent = node.scope() - if isinstance(parent, astroid.Function): + if isinstance(parent, astroid.FunctionDef): for default_node in parent.args.defaults: for default_name_node in default_node.nodes_of_class(astroid.Name): if default_name_node is node: @@ -234,49 +276,20 @@ def is_ancestor_name(frame, node): def assign_parent(node): """return the higher parent which is not an AssName, Tuple or List node """ - while node and isinstance(node, (astroid.AssName, + while node and isinstance(node, (astroid.AssignName, astroid.Tuple, astroid.List)): node = node.parent return node -def overrides_an_abstract_method(class_node, name): - """return True if pnode is a parent of node""" - for ancestor in class_node.ancestors(): - if name in ancestor and isinstance(ancestor[name], astroid.Function) and \ - ancestor[name].is_abstract(pass_is_abstract=False): - return True - return False def overrides_a_method(class_node, name): """return True if is a method overridden from an ancestor""" for ancestor in class_node.ancestors(): - if name in ancestor and isinstance(ancestor[name], astroid.Function): + if name in ancestor and isinstance(ancestor[name], astroid.FunctionDef): return True return False -PYMETHODS = set(('__new__', '__init__', '__del__', '__hash__', - '__str__', '__repr__', - '__len__', '__iter__', - '__delete__', '__get__', '__set__', - '__getitem__', '__setitem__', '__delitem__', '__contains__', - '__getattribute__', '__getattr__', '__setattr__', '__delattr__', - '__call__', - '__enter__', '__exit__', - '__cmp__', '__ge__', '__gt__', '__le__', '__lt__', '__eq__', - '__nonzero__', '__neg__', '__invert__', - '__mul__', '__imul__', '__rmul__', - '__div__', '__idiv__', '__rdiv__', - '__add__', '__iadd__', '__radd__', - '__sub__', '__isub__', '__rsub__', - '__pow__', '__ipow__', '__rpow__', - '__mod__', '__imod__', '__rmod__', - '__and__', '__iand__', '__rand__', - '__or__', '__ior__', '__ror__', - '__xor__', '__ixor__', '__rxor__', - # XXX To be continued - )) - def check_messages(*messages): """decorator to store messages that are handled by a checker method""" @@ -370,7 +383,7 @@ def is_attr_protected(attrname): """return True if attribute name is protected (start with _ and some other details), False otherwise. """ - return attrname[0] == '_' and not attrname == '_' and not ( + return attrname[0] == '_' and attrname != '_' and not ( attrname.startswith('__') and attrname.endswith('__')) def node_frame_class(node): @@ -379,7 +392,7 @@ def node_frame_class(node): """ klass = node.frame() - while klass is not None and not isinstance(klass, astroid.Class): + while klass is not None and not isinstance(klass, astroid.ClassDef): if klass.parent is None: klass = None else: @@ -387,13 +400,6 @@ def node_frame_class(node): return klass -def is_super_call(expr): - """return True if expression node is a function call and if function name - is super. Check before that you're in a method. - """ - return (isinstance(expr, astroid.CallFunc) and - isinstance(expr.func, astroid.Name) and - expr.func.name == 'super') def is_attr_private(attrname): """Check that attribute name is private (at least two leading underscores, @@ -416,15 +422,16 @@ def get_argument_from_call(callfunc_node, position=None, keyword=None): """ if position is None and keyword is None: raise ValueError('Must specify at least one of: position or keyword.') - try: - if position is not None and not isinstance(callfunc_node.args[position], astroid.Keyword): + if position is not None: + try: return callfunc_node.args[position] - except IndexError as error: - raise NoSuchArgumentError(error) - if keyword: - for arg in callfunc_node.args: - if isinstance(arg, astroid.Keyword) and arg.arg == keyword: + except IndexError: + pass + if keyword and callfunc_node.keywords: + for arg in callfunc_node.keywords: + if arg.arg == keyword: return arg.value + raise NoSuchArgumentError def inherit_from_std_ex(node): @@ -438,49 +445,33 @@ def inherit_from_std_ex(node): return any(inherit_from_std_ex(parent) for parent in node.ancestors(recurs=False)) -def is_import_error(handler): +def error_of_type(handler, error_type): """ Check if the given exception handler catches - ImportError. + the given error_type. - :param handler: A node, representing an ExceptHandler node. - :returns: True if the handler catches ImportError, False otherwise. + The *handler* parameter is a node, representing an ExceptHandler node. + The *error_type* can be an exception, such as AttributeError, or it + can be a tuple of errors. + The function will return True if the handler catches any of the + given errors. """ - names = None - if isinstance(handler.type, astroid.Tuple): - names = [name for name in handler.type.elts - if isinstance(name, astroid.Name)] - elif isinstance(handler.type, astroid.Name): - names = [handler.type] - else: - # Don't try to infer that. - return - for name in names: - try: - for infered in name.infer(): - if (isinstance(infered, astroid.Class) and - inherit_from_std_ex(infered) and - infered.name == 'ImportError'): - return True - except astroid.InferenceError: - continue + if not isinstance(error_type, tuple): + error_type = (error_type, ) + expected_errors = {error.__name__ for error in error_type} + if not handler.type: + # bare except. While this indeed catches anything, if the desired errors + # aren't specified directly, then we just ignore it. + return False + return handler.catch(expected_errors) + + +def is_import_error(handler): + warnings.warn("This function is deprecated in the favour of " + "error_of_type. It will be removed in Pylint 1.6.", + DeprecationWarning, stacklevel=2) + return error_of_type(handler, ImportError) -def has_known_bases(klass): - """Returns true if all base classes of a class could be inferred.""" - try: - return klass._all_bases_known - except AttributeError: - pass - for base in klass.bases: - result = safe_infer(base) - # TODO: check for A->B->A->B pattern in class structure too? - if (not isinstance(result, astroid.Class) or - result is klass or - not has_known_bases(result)): - klass._all_bases_known = False - return False - klass._all_bases_known = True - return True def decorated_with_property(node): """ Detect if the given function node is decorated with a property. """ @@ -491,7 +482,7 @@ def decorated_with_property(node): continue try: for infered in decorator.infer(): - if isinstance(infered, astroid.Class): + if isinstance(infered, astroid.ClassDef): if (infered.root().name == BUILTINS_NAME and infered.name == 'property'): return True @@ -503,19 +494,16 @@ def decorated_with_property(node): pass -def decorated_with_abc(func): - """Determine if the `func` node is decorated with `abc` decorators.""" - if func.decorators: - for node in func.decorators.nodes: - try: - infered = next(node.infer()) - except astroid.InferenceError: - continue - if infered and infered.qname() in ABC_METHODS: - return True +def decorated_with(func, qnames): + """Determine if the `func` node has a decorator with the qualified name `qname`.""" + decorators = func.decorators.nodes if func.decorators else [] + for decorator_node in decorators: + dec = safe_infer(decorator_node) + if dec and dec.qname() in qnames: + return True -def unimplemented_abstract_methods(node, is_abstract_cb=decorated_with_abc): +def unimplemented_abstract_methods(node, is_abstract_cb=None): """ Get the unimplemented abstract methods for the given *node*. @@ -527,6 +515,9 @@ def unimplemented_abstract_methods(node, is_abstract_cb=decorated_with_abc): For the rest of them, it will return a dictionary of abstract method names and their inferred objects. """ + if is_abstract_cb is None: + is_abstract_cb = functools.partial( + decorated_with, qnames=ABC_METHODS) visited = {} try: mro = reversed(node.mro()) @@ -540,14 +531,20 @@ def unimplemented_abstract_methods(node, is_abstract_cb=decorated_with_abc): for ancestor in mro: for obj in ancestor.values(): infered = obj - if isinstance(obj, astroid.AssName): + if isinstance(obj, astroid.AssignName): infered = safe_infer(obj) if not infered: + # Might be an abstract function, + # but since we don't have enough information + # in order to take this decision, we're taking + # the *safe* decision instead. + if obj.name in visited: + del visited[obj.name] continue - if not isinstance(infered, astroid.Function): + if not isinstance(infered, astroid.FunctionDef): if obj.name in visited: del visited[obj.name] - if isinstance(infered, astroid.Function): + if isinstance(infered, astroid.FunctionDef): # It's critical to use the original name, # since after inferring, an object can be something # else than expected, as in the case of the @@ -562,3 +559,183 @@ def unimplemented_abstract_methods(node, is_abstract_cb=decorated_with_abc): elif not abstract and obj.name in visited: del visited[obj.name] return visited + + +def node_ignores_exception(node, exception): + """Check if the node is in a TryExcept which handles the given exception.""" + current = node + ignores = (astroid.ExceptHandler, astroid.TryExcept) + while current and not isinstance(current.parent, ignores): + current = current.parent + + func = functools.partial(error_of_type, + error_type=(exception, )) + if current and isinstance(current.parent, astroid.TryExcept): + if any(map(func, current.parent.handlers)): + return True + return False + + +def class_is_abstract(node): + """return true if the given class node should be considered as an abstract + class + """ + for method in node.methods(): + if method.parent.frame() is node: + if method.is_abstract(pass_is_abstract=False): + return True + return False + + +def _hasattr(value, attr): + try: + value.getattr(attr) + return True + except astroid.NotFoundError: + return False + + +def is_comprehension(node): + comprehensions = (astroid.ListComp, + astroid.SetComp, + astroid.DictComp, + astroid.GeneratorExp) + return isinstance(node, comprehensions) + + +def _supports_mapping_protocol(value): + return _hasattr(value, GETITEM_METHOD) and _hasattr(value, KEYS_METHOD) + + +def _supports_membership_test_protocol(value): + return _hasattr(value, CONTAINS_METHOD) + + +def _supports_iteration_protocol(value): + return _hasattr(value, ITER_METHOD) or _hasattr(value, GETITEM_METHOD) + + +def _supports_subscript_protocol(value): + return _hasattr(value, GETITEM_METHOD) + + +def _is_abstract_class_name(name): + lname = name.lower() + is_mixin = lname.endswith('mixin') + is_abstract = lname.startswith('abstract') + is_base = lname.startswith('base') or lname.endswith('base') + return is_mixin or is_abstract or is_base + + +def is_inside_abstract_class(node): + while node is not None: + if isinstance(node, astroid.ClassDef): + if class_is_abstract(node): + return True + name = getattr(node, 'name', None) + if name is not None and _is_abstract_class_name(name): + return True + node = node.parent + return False + + +def is_iterable(value): + if isinstance(value, astroid.ClassDef): + if not has_known_bases(value): + return True + # classobj can only be iterable if it has an iterable metaclass + meta = value.metaclass() + if meta is not None: + if _supports_iteration_protocol(meta): + return True + if isinstance(value, astroid.Instance): + if not has_known_bases(value): + return True + if _supports_iteration_protocol(value): + return True + return False + + +def is_mapping(value): + if isinstance(value, astroid.ClassDef): + if not has_known_bases(value): + return True + # classobj can only be a mapping if it has a metaclass is mapping + meta = value.metaclass() + if meta is not None: + if _supports_mapping_protocol(meta): + return True + if isinstance(value, astroid.Instance): + if not has_known_bases(value): + return True + if _supports_mapping_protocol(value): + return True + return False + + +def supports_membership_test(value): + if isinstance(value, astroid.ClassDef): + if not has_known_bases(value): + return True + meta = value.metaclass() + if meta is not None and _supports_membership_test_protocol(meta): + return True + if isinstance(value, astroid.Instance): + if not has_known_bases(value): + return True + if _supports_membership_test_protocol(value): + return True + return is_iterable(value) + + +def supports_subscript(value): + if isinstance(value, astroid.ClassDef): + if not has_known_bases(value): + return True + meta = value.metaclass() + if meta is not None and _supports_subscript_protocol(meta): + return True + if isinstance(value, astroid.Instance): + if not has_known_bases(value): + return True + if _supports_subscript_protocol(value): + return True + return False + +# TODO(cpopa): deprecate these or leave them as aliases? +def safe_infer(node, context=None): + """Return the inferred value for the given node. + + Return None if inference failed or if there is some ambiguity (more than + one node has been inferred). + """ + try: + inferit = node.infer(context=context) + value = next(inferit) + except astroid.InferenceError: + return + try: + next(inferit) + return # None if there is ambiguity on the inferred node + except astroid.InferenceError: + return # there is some kind of ambiguity + except StopIteration: + return value + + +def has_known_bases(klass, context=None): + """Return true if all base classes of a class could be inferred.""" + try: + return klass._all_bases_known + except AttributeError: + pass + for base in klass.bases: + result = safe_infer(base, context=context) + # TODO: check for A->B->A->B pattern in class structure too? + if (not isinstance(result, astroid.ClassDef) or + result is klass or + not has_known_bases(result, context=context)): + klass._all_bases_known = False + return False + klass._all_bases_known = True + return True diff --git a/pymode/libs/pylint/checkers/variables.py b/pymode/libs/pylint/checkers/variables.py index 8f6f9574..209f7f71 100644 --- a/pymode/libs/pylint/checkers/variables.py +++ b/pymode/libs/pylint/checkers/variables.py @@ -20,10 +20,10 @@ import re from copy import copy +import six + import astroid -from astroid import are_exclusive, builtin_lookup from astroid import modutils - from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH from pylint.utils import get_global_option from pylint.checkers import BaseChecker @@ -31,13 +31,28 @@ PYMETHODS, is_ancestor_name, is_builtin, is_defined_before, is_error, is_func_default, is_func_decorator, assign_parent, check_messages, is_inside_except, clobber_in_except, - get_all_elements, has_known_bases) -import six + get_all_elements, has_known_bases, node_ignores_exception, + is_inside_abstract_class, is_comprehension, is_iterable, + safe_infer) SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$") - +FUTURE = '__future__' PY3K = sys.version_info >= (3, 0) + +def _is_from_future_import(stmt, name): + """Check if the name is a future import from another module.""" + try: + module = stmt.do_import_module(stmt.modname) + except astroid.InferenceError: + return + + for local_node in module.locals.get(name, []): + if (isinstance(local_node, astroid.ImportFrom) + and local_node.modname == FUTURE): + return True + + def in_for_else_branch(parent, stmt): """Returns True if stmt in inside the else branch for a parent For stmt.""" return (isinstance(parent, astroid.For) and @@ -55,7 +70,7 @@ def overridden_method(klass, name): # We have found an ancestor defining but it's not in the local # dictionary. This may happen with astroid built from living objects. return None - if isinstance(meth_node, astroid.Function): + if isinstance(meth_node, astroid.FunctionDef): return meth_node return None @@ -96,7 +111,7 @@ class C: ... scope = frame.parent.scope() if defframe and defframe.parent: def_scope = defframe.parent.scope() - if isinstance(frame, astroid.Function): + if isinstance(frame, astroid.FunctionDef): # If the parent of the current node is a # function, then it can be under its scope # (defined in, which doesn't concern us) or @@ -104,9 +119,9 @@ class C: ... # for annotations of function arguments, they'll have # their parent the Arguments node. if not isinstance(node.parent, - (astroid.Function, astroid.Arguments)): + (astroid.FunctionDef, astroid.Arguments)): return False - elif any(not isinstance(f, (astroid.Class, astroid.Module)) + elif any(not isinstance(f, (astroid.ClassDef, astroid.Module)) for f in (frame, defframe)): # Not interested in other frames, since they are already # not in a global scope. @@ -119,7 +134,7 @@ class C: ... # share a global scope. parent_scope = s while parent_scope: - if not isinstance(parent_scope, (astroid.Class, astroid.Module)): + if not isinstance(parent_scope, (astroid.ClassDef, astroid.Module)): break_scopes.append(parent_scope) break if parent_scope.parent: @@ -147,12 +162,12 @@ def _fix_dot_imports(not_consumed): # TODO: this should be improved in issue astroid #46 names = {} for name, stmts in six.iteritems(not_consumed): - if any(isinstance(stmt, astroid.AssName) - and isinstance(stmt.ass_type(), astroid.AugAssign) + if any(isinstance(stmt, astroid.AssignName) + and isinstance(stmt.assign_type(), astroid.AugAssign) for stmt in stmts): continue for stmt in stmts: - if not isinstance(stmt, (astroid.From, astroid.Import)): + if not isinstance(stmt, (astroid.ImportFrom, astroid.Import)): continue for imports in stmt.names: second_name = None @@ -176,7 +191,7 @@ def _find_frame_imports(name, frame): *name*. Such imports can be considered assignments. Returns True if an import for the given name was found. """ - imports = frame.nodes_of_class((astroid.Import, astroid.From)) + imports = frame.nodes_of_class((astroid.Import, astroid.ImportFrom)) for import_node in imports: for import_name, import_alias in import_node.names: # If the import uses an alias, check only that. @@ -235,7 +250,7 @@ def _find_frame_imports(name, frame): 'W0614': ('Unused import %s from wildcard import', 'unused-wildcard-import', 'Used when an imported module or variable is not used from a \ - \'from X import *\' style import.'), + `\'from X import *\'` style import.'), 'W0621': ('Redefining name %r from outer scope (line %s)', 'redefined-outer-name', @@ -255,16 +270,18 @@ def _find_frame_imports(name, frame): a list comprehension or a generator expression) is used outside \ the loop.'), - 'W0632': ('Possible unbalanced tuple unpacking with ' + 'E0632': ('Possible unbalanced tuple unpacking with ' 'sequence%s: ' 'left side has %d label(s), right side has %d value(s)', 'unbalanced-tuple-unpacking', - 'Used when there is an unbalanced tuple unpacking in assignment'), + 'Used when there is an unbalanced tuple unpacking in assignment', + {'old_names': [('W0632', 'unbalanced-tuple-unpacking')]}), - 'W0633': ('Attempting to unpack a non-sequence%s', + 'E0633': ('Attempting to unpack a non-sequence%s', 'unpacking-non-sequence', 'Used when something which is not ' - 'a sequence is used in an unpack assignment'), + 'a sequence is used in an unpack assignment', + {'old_names': [('W0633', 'unpacking-non-sequence')]}), 'W0640': ('Cell variable %s defined in loop', 'cell-var-from-loop', @@ -313,7 +330,7 @@ class VariablesChecker(BaseChecker): ) def __init__(self, linter=None): BaseChecker.__init__(self, linter) - self._to_consume = None + self._to_consume = None # list of tuples: (to_consume:dict, consumed:dict, scope_type:str) self._checking_mod_attr = None def visit_module(self, node): @@ -336,50 +353,61 @@ def leave_module(self, node): not_consumed = self._to_consume.pop()[0] # attempt to check for __all__ if defined if '__all__' in node.locals: - assigned = next(node.igetattr('__all__')) - if assigned is not astroid.YES: - for elt in getattr(assigned, 'elts', ()): - try: - elt_name = next(elt.infer()) - except astroid.InferenceError: - continue - - if not isinstance(elt_name, astroid.Const) \ - or not isinstance(elt_name.value, six.string_types): - self.add_message('invalid-all-object', - args=elt.as_string(), node=elt) - continue - elt_name = elt_name.value - # If elt is in not_consumed, remove it from not_consumed - if elt_name in not_consumed: - del not_consumed[elt_name] - continue - if elt_name not in node.locals: - if not node.package: - self.add_message('undefined-all-variable', - args=elt_name, - node=elt) - else: - basename = os.path.splitext(node.file)[0] - if os.path.basename(basename) == '__init__': - name = node.name + "." + elt_name - try: - modutils.file_from_modpath(name.split(".")) - except ImportError: - self.add_message('undefined-all-variable', - args=elt_name, - node=elt) - except SyntaxError: - # don't yield an syntax-error warning, - # because it will be later yielded - # when the file will be checked - pass + self._check_all(node, not_consumed) # don't check unused imports in __init__ files if not self.config.init_import and node.package: return self._check_imports(not_consumed) + def _check_all(self, node, not_consumed): + assigned = next(node.igetattr('__all__')) + if assigned is astroid.YES: + return + + for elt in getattr(assigned, 'elts', ()): + try: + elt_name = next(elt.infer()) + except astroid.InferenceError: + continue + if elt_name is astroid.YES: + continue + if not elt_name.parent: + continue + + if (not isinstance(elt_name, astroid.Const) + or not isinstance(elt_name.value, six.string_types)): + self.add_message('invalid-all-object', + args=elt.as_string(), node=elt) + continue + + elt_name = elt_name.value + # If elt is in not_consumed, remove it from not_consumed + if elt_name in not_consumed: + del not_consumed[elt_name] + continue + + if elt_name not in node.locals: + if not node.package: + self.add_message('undefined-all-variable', + args=(elt_name, ), + node=elt) + else: + basename = os.path.splitext(node.file)[0] + if os.path.basename(basename) == '__init__': + name = node.name + "." + elt_name + try: + modutils.file_from_modpath(name.split(".")) + except ImportError: + self.add_message('undefined-all-variable', + args=(elt_name, ), + node=elt) + except SyntaxError: + # don't yield an syntax-error warning, + # because it will be later yielded + # when the file will be checked + pass + def _check_imports(self, not_consumed): local_names = _fix_dot_imports(not_consumed) checked = set() @@ -396,9 +424,9 @@ def _check_imports(self, not_consumed): checked.add(real_name) if (isinstance(stmt, astroid.Import) or - (isinstance(stmt, astroid.From) and + (isinstance(stmt, astroid.ImportFrom) and not stmt.modname)): - if (isinstance(stmt, astroid.From) and + if (isinstance(stmt, astroid.ImportFrom) and SPECIAL_OBJ.search(imported_name)): # Filter special objects (__doc__, __all__) etc., # because they can be imported for exporting. @@ -408,11 +436,19 @@ def _check_imports(self, not_consumed): else: msg = "%s imported as %s" % (imported_name, as_name) self.add_message('unused-import', args=msg, node=stmt) - elif isinstance(stmt, astroid.From) and stmt.modname != '__future__': + elif (isinstance(stmt, astroid.ImportFrom) + and stmt.modname != FUTURE): + if SPECIAL_OBJ.search(imported_name): # Filter special objects (__doc__, __all__) etc., # because they can be imported for exporting. continue + + if _is_from_future_import(stmt, name): + # Check if the name is in fact loaded from a + # __future__ import in another module. + continue + if imported_name == '*': self.add_message('unused-wildcard-import', args=name, node=stmt) @@ -425,12 +461,12 @@ def _check_imports(self, not_consumed): self.add_message('unused-import', args=msg, node=stmt) del self._to_consume - def visit_class(self, node): + def visit_classdef(self, node): """visit class: update consumption analysis variable """ self._to_consume.append((copy(node.locals), {}, 'class')) - def leave_class(self, _): + def leave_classdef(self, _): """leave class: update consumption analysis variable """ # do not check for not used locals here (no sense) @@ -447,12 +483,12 @@ def leave_lambda(self, _): # do not check for not used locals here self._to_consume.pop() - def visit_genexpr(self, node): + def visit_generatorexp(self, node): """visit genexpr: update consumption analysis variable """ self._to_consume.append((copy(node.locals), {}, 'comprehension')) - def leave_genexpr(self, _): + def leave_generatorexp(self, _): """leave genexpr: update consumption analysis variable """ # do not check for not used locals here @@ -480,7 +516,7 @@ def leave_setcomp(self, _): # do not check for not used locals here self._to_consume.pop() - def visit_function(self, node): + def visit_functiondef(self, node): """visit function: update consumption analysis variable and check locals """ self._to_consume.append((copy(node.locals), {}, 'function')) @@ -492,15 +528,22 @@ def visit_function(self, node): if is_inside_except(stmt): continue if name in globs and not isinstance(stmt, astroid.Global): - line = globs[name][0].fromlineno + definition = globs[name][0] + if (isinstance(definition, astroid.ImportFrom) + and definition.modname == FUTURE): + # It is a __future__ directive, not a symbol. + continue + + line = definition.fromlineno dummy_rgx = self.config.dummy_variables_rgx if not dummy_rgx.match(name): - self.add_message('redefined-outer-name', args=(name, line), node=stmt) + self.add_message('redefined-outer-name', + args=(name, line), node=stmt) elif is_builtin(name): # do not print Redefining builtin for additional builtins self.add_message('redefined-builtin', args=name, node=stmt) - def leave_function(self, node): + def leave_functiondef(self, node): """leave function: check function's locals are consumed""" not_consumed = self._to_consume.pop()[0] if not (self.linter.is_message_enabled('unused-variable') or @@ -512,9 +555,9 @@ def leave_function(self, node): # don't check arguments of abstract methods or within an interface is_method = node.is_method() klass = node.parent.frame() - if is_method and (klass.type == 'interface' or node.is_abstract()): + if is_method and node.is_abstract(): return - if is_method and isinstance(klass, astroid.Class): + if is_method and isinstance(klass, astroid.ClassDef): confidence = INFERENCE if has_known_bases(klass) else INFERENCE_FAILURE else: confidence = HIGH @@ -537,22 +580,23 @@ def leave_function(self, node): stmt = stmts[0] if isinstance(stmt, astroid.Global): continue - if isinstance(stmt, (astroid.Import, astroid.From)): + if isinstance(stmt, (astroid.Import, astroid.ImportFrom)): # Detect imports, assigned to global statements. - if global_names: - skip = False - for import_name, import_alias in stmt.names: - # If the import uses an alias, check only that. - # Otherwise, check only the import name. - if import_alias: - if import_alias in global_names: - skip = True - break - elif import_name in global_names: + if not global_names: + continue + skip = False + for import_name, import_alias in stmt.names: + # If the import uses an alias, check only that. + # Otherwise, check only the import name. + if import_alias: + if import_alias in global_names: skip = True break - if skip: - continue + elif import_name in global_names: + skip = True + break + if skip: + continue # care about functions with unknown argument (builtins) if name in argnames: @@ -580,8 +624,12 @@ def leave_function(self, node): continue self.add_message('unused-variable', args=name, node=stmt) - @check_messages('global-variable-undefined', 'global-variable-not-assigned', 'global-statement', - 'global-at-module-level', 'redefined-builtin') + visit_asyncfunctiondef = visit_functiondef + leave_asyncfunctiondef = leave_functiondef + + @check_messages('global-variable-undefined', 'global-variable-not-assigned', + 'global-statement', 'global-at-module-level', + 'redefined-builtin') def visit_global(self, node): """check names imported exists in the global scope""" frame = node.frame() @@ -627,11 +675,11 @@ def visit_global(self, node): def _check_late_binding_closure(self, node, assignment_node): def _is_direct_lambda_call(): - return (isinstance(node_scope.parent, astroid.CallFunc) + return (isinstance(node_scope.parent, astroid.Call) and node_scope.parent.func is node_scope) node_scope = node.scope() - if not isinstance(node_scope, (astroid.Lambda, astroid.Function)): + if not isinstance(node_scope, (astroid.Lambda, astroid.FunctionDef)): return if isinstance(node.parent, astroid.Arguments): return @@ -683,9 +731,10 @@ def _loopvar_name(self, node, name): _astmts.append(stmt) astmts = _astmts if len(astmts) == 1: - ass = astmts[0].ass_type() - if isinstance(ass, (astroid.For, astroid.Comprehension, astroid.GenExpr)) \ - and not ass.statement() is node.statement(): + assign = astmts[0].assign_type() + if (isinstance(assign, (astroid.For, astroid.Comprehension, + astroid.GeneratorExp)) + and assign.statement() is not node.statement()): self.add_message('undefined-loop-variable', args=name, node=node) @check_messages('redefine-in-handler') @@ -695,13 +744,134 @@ def visit_excepthandler(self, node): if clobbering: self.add_message('redefine-in-handler', args=args, node=name) - def visit_assname(self, node): - if isinstance(node.ass_type(), astroid.AugAssign): + def visit_assignname(self, node): + if isinstance(node.assign_type(), astroid.AugAssign): self.visit_name(node) def visit_delname(self, node): self.visit_name(node) + @staticmethod + def _defined_in_function_definition(node, frame): + in_annotation_or_default = False + if (isinstance(frame, astroid.FunctionDef) and + node.statement() is frame): + in_annotation_or_default = ( + ( + PY3K and (node in frame.args.annotations + or node is frame.args.varargannotation + or node is frame.args.kwargannotation) + ) + or + frame.args.parent_of(node) + ) + return in_annotation_or_default + + @staticmethod + def _next_to_consume(node, name, to_consume): + # mark the name as consumed if it's defined in this scope + found_node = to_consume.get(name) + if (found_node + and isinstance(node.parent, astroid.Assign) + and node.parent == found_node[0].parent): + lhs = found_node[0].parent.targets[0] + if lhs.name == name: # this name is defined in this very statement + found_node = None + return found_node + + @staticmethod + def _is_variable_violation(node, name, defnode, stmt, defstmt, + frame, defframe, base_scope_type, + recursive_klass): + maybee0601 = True + annotation_return = False + if frame is not defframe: + maybee0601 = _detect_global_scope(node, frame, defframe) + elif defframe.parent is None: + # we are at the module level, check the name is not + # defined in builtins + if name in defframe.scope_attrs or astroid.builtin_lookup(name)[1]: + maybee0601 = False + else: + # we are in a local scope, check the name is not + # defined in global or builtin scope + if defframe.root().lookup(name)[1]: + maybee0601 = False + else: + # check if we have a nonlocal + if name in defframe.locals: + maybee0601 = not any(isinstance(child, astroid.Nonlocal) + and name in child.names + for child in defframe.get_children()) + + if (base_scope_type == 'lambda' and + isinstance(frame, astroid.ClassDef) + and name in frame.locals): + + # This rule verifies that if the definition node of the + # checked name is an Arguments node and if the name + # is used a default value in the arguments defaults + # and the actual definition of the variable label + # is happening before the Arguments definition. + # + # bar = None + # foo = lambda bar=bar: bar + # + # In this case, maybee0601 should be False, otherwise + # it should be True. + maybee0601 = not (isinstance(defnode, astroid.Arguments) and + node in defnode.defaults and + frame.locals[name][0].fromlineno < defstmt.fromlineno) + elif (isinstance(defframe, astroid.ClassDef) and + isinstance(frame, astroid.FunctionDef)): + # Special rule for function return annotations, + # which uses the same name as the class where + # the function lives. + if (PY3K and node is frame.returns and + defframe.parent_of(frame.returns)): + maybee0601 = annotation_return = True + + if (maybee0601 and defframe.name in defframe.locals and + defframe.locals[name][0].lineno < frame.lineno): + # Detect class assignments with the same + # name as the class. In this case, no warning + # should be raised. + maybee0601 = False + if isinstance(node.parent, astroid.Arguments): + maybee0601 = stmt.fromlineno <= defstmt.fromlineno + elif recursive_klass: + maybee0601 = True + else: + maybee0601 = maybee0601 and stmt.fromlineno <= defstmt.fromlineno + return maybee0601, annotation_return + + def _ignore_class_scope(self, node, name, frame): + # Detect if we are in a local class scope, as an assignment. + # For example, the following is fair game. + # + # class A: + # b = 1 + # c = lambda b=b: b * b + # + # class B: + # tp = 1 + # def func(self, arg: tp): + # ... + # class C: + # tp = 2 + # def func(self, arg=tp): + # ... + + in_annotation_or_default = self._defined_in_function_definition( + node, frame) + if in_annotation_or_default: + frame_locals = frame.parent.scope().locals + else: + frame_locals = frame.locals + return not ((isinstance(frame, astroid.ClassDef) or + in_annotation_or_default) and + name in frame_locals) + @check_messages(*(MSGS.keys())) def visit_name(self, node): """check that a name is defined if the current scope and doesn't @@ -724,6 +894,7 @@ def visit_name(self, node): start_index = len(self._to_consume) - 1 # iterates through parent scopes, from the inner to the outer base_scope_type = self._to_consume[start_index][-1] + # pylint: disable=too-many-nested-blocks; refactoring this block is a pain. 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 @@ -733,31 +904,9 @@ def visit_name(self, node): # comprehension and its direct outer scope is a class if scope_type == 'class' and i != start_index and not ( base_scope_type == 'comprehension' and i == start_index-1): - # Detect if we are in a local class scope, as an assignment. - # For example, the following is fair game. - # - # class A: - # b = 1 - # c = lambda b=b: b * b - # - # class B: - # tp = 1 - # def func(self, arg: tp): - # ... - - in_annotation = ( - PY3K and isinstance(frame, astroid.Function) - and node.statement() is frame and - (node in frame.args.annotations - or node is frame.args.varargannotation - or node is frame.args.kwargannotation)) - if in_annotation: - frame_locals = frame.parent.scope().locals - else: - frame_locals = frame.locals - if not ((isinstance(frame, astroid.Class) or in_annotation) - and name in frame_locals): + if self._ignore_class_scope(node, name, frame): continue + # the name has already been consumed, only check it's not a loop # variable used outside the loop if name in consumed: @@ -765,11 +914,10 @@ def visit_name(self, node): self._check_late_binding_closure(node, defnode) self._loopvar_name(node, name) break - # mark the name as consumed if it's defined in this scope - # (i.e. no KeyError is raised by "to_consume[name]") - try: - consumed[name] = to_consume[name] - except KeyError: + found_node = self._next_to_consume(node, name, to_consume) + if found_node: + consumed[name] = found_node + else: continue # checks for use before assignment defnode = assign_parent(to_consume[name][0]) @@ -777,94 +925,62 @@ def visit_name(self, node): self._check_late_binding_closure(node, defnode) defstmt = defnode.statement() defframe = defstmt.frame() - maybee0601 = True - if not frame is defframe: - maybee0601 = _detect_global_scope(node, frame, defframe) - elif defframe.parent is None: - # we are at the module level, check the name is not - # defined in builtins - if name in defframe.scope_attrs or builtin_lookup(name)[1]: - maybee0601 = False - else: - # we are in a local scope, check the name is not - # defined in global or builtin scope - if defframe.root().lookup(name)[1]: - maybee0601 = False - else: - # check if we have a nonlocal - if name in defframe.locals: - maybee0601 = not any(isinstance(child, astroid.Nonlocal) - and name in child.names - for child in defframe.get_children()) - - # Handle a couple of class scoping issues. - annotation_return = False # The class reuses itself in the class scope. recursive_klass = (frame is defframe and defframe.parent_of(node) and - isinstance(defframe, astroid.Class) and + isinstance(defframe, astroid.ClassDef) and node.name == defframe.name) - if (self._to_consume[-1][-1] == 'lambda' and - isinstance(frame, astroid.Class) - and name in frame.locals): - maybee0601 = True - elif (isinstance(defframe, astroid.Class) and - isinstance(frame, astroid.Function)): - # Special rule for function return annotations, - # which uses the same name as the class where - # the function lives. - if (PY3K and node is frame.returns and - defframe.parent_of(frame.returns)): - maybee0601 = annotation_return = True - - if (maybee0601 and defframe.name in defframe.locals and - defframe.locals[name][0].lineno < frame.lineno): - # Detect class assignments with the same - # name as the class. In this case, no warning - # should be raised. - maybee0601 = False - elif recursive_klass: - maybee0601 = True - else: - maybee0601 = maybee0601 and stmt.fromlineno <= defstmt.fromlineno + + maybee0601, annotation_return = self._is_variable_violation( + node, name, defnode, stmt, defstmt, + frame, defframe, + base_scope_type, recursive_klass) if (maybee0601 and not is_defined_before(node) - and not are_exclusive(stmt, defstmt, ('NameError', - 'Exception', - 'BaseException'))): - if recursive_klass or (defstmt is stmt and - isinstance(node, (astroid.DelName, - astroid.AssName))): - self.add_message('undefined-variable', args=name, node=node) - elif annotation_return: - self.add_message('undefined-variable', args=name, node=node) - elif self._to_consume[-1][-1] != 'lambda': + and not astroid.are_exclusive(stmt, defstmt, ('NameError', + 'Exception', + 'BaseException'))): + + # Used and defined in the same place, e.g `x += 1` and `del x` + defined_by_stmt = ( + defstmt is stmt + and isinstance(node, (astroid.DelName, astroid.AssignName)) + ) + + if (recursive_klass + or defined_by_stmt + or annotation_return + or isinstance(defstmt, astroid.Delete)): + if not node_ignores_exception(node, NameError): + self.add_message('undefined-variable', args=name, + node=node) + elif base_scope_type != 'lambda': # E0601 may *not* occurs in lambda scope. self.add_message('used-before-assignment', args=name, node=node) - elif self._to_consume[-1][-1] == 'lambda': + elif base_scope_type == 'lambda': # E0601 can occur in class-level scope in lambdas, as in # the following example: # class A: # x = lambda attr: f + attr # f = 42 - if isinstance(frame, astroid.Class) and name in frame.locals: + if isinstance(frame, astroid.ClassDef) and name in frame.locals: if isinstance(node.parent, astroid.Arguments): - # Doing the following is fine: - # class A: - # x = 42 - # y = lambda attr=x: attr if stmt.fromlineno <= defstmt.fromlineno: + # Doing the following is fine: + # class A: + # x = 42 + # y = lambda attr=x: attr self.add_message('used-before-assignment', args=name, node=node) else: self.add_message('undefined-variable', args=name, node=node) + elif scope_type == 'lambda': + self.add_message('undefined-variable', + node=node, args=name) - if isinstance(node, astroid.AssName): # Aug AssName - del consumed[name] - else: - del to_consume[name] + del to_consume[name] # check it's not a loop variable used outside the loop self._loopvar_name(node, name) break @@ -873,11 +989,17 @@ def visit_name(self, node): # undefined name ! if not (name in astroid.Module.scope_attrs or is_builtin(name) or name in self.config.additional_builtins): - self.add_message('undefined-variable', args=name, node=node) + if not node_ignores_exception(node, NameError): + self.add_message('undefined-variable', args=name, node=node) @check_messages('no-name-in-module') def visit_import(self, node): """check modules attribute accesses""" + if node_ignores_exception(node, ImportError): + # No need to verify this, since ImportError is already + # handled by the client code. + return + for name, _ in node.names: parts = name.split('.') try: @@ -887,13 +1009,17 @@ def visit_import(self, node): self._check_module_attrs(node, module, parts[1:]) @check_messages('no-name-in-module') - def visit_from(self, node): + def visit_importfrom(self, node): """check modules attribute accesses""" + if node_ignores_exception(node, ImportError): + # No need to verify this, since ImportError is already + # handled by the client code. + return + name_parts = node.modname.split('.') - level = getattr(node, 'level', None) try: - module = node.root().import_module(name_parts[0], level=level) - except Exception: # pylint: disable=broad-except + module = node.do_import_module(name_parts[0]) + except Exception: return module = self._check_module_attrs(node, module, name_parts[1:]) if not module: @@ -913,7 +1039,8 @@ def visit_assign(self, node): targets = node.targets[0].itered() try: - for infered in node.value.infer(): + infered = safe_infer(node.value) + if infered is not None: self._check_unpacking(infered, node, targets) except astroid.InferenceError: return @@ -922,6 +1049,10 @@ def _check_unpacking(self, infered, node, targets): """ Check for unbalanced tuple unpacking and unpacking non sequences. """ + if is_inside_abstract_class(node): + return + if is_comprehension(node): + return if infered is astroid.YES: return if (isinstance(infered.parent, astroid.Arguments) and @@ -942,19 +1073,10 @@ def _check_unpacking(self, infered, node, targets): len(targets), len(values))) # attempt to check unpacking may be possible (ie RHS is iterable) - elif isinstance(infered, astroid.Instance): - for meth in ('__iter__', '__getitem__'): - try: - infered.getattr(meth) - break - except astroid.NotFoundError: - continue - else: + else: + if not is_iterable(infered): self.add_message('unpacking-non-sequence', node=node, args=(_get_unpacking_extra_info(node, infered),)) - else: - self.add_message('unpacking-non-sequence', node=node, - args=(_get_unpacking_extra_info(node, infered),)) def _check_module_attrs(self, node, module, module_names): @@ -1017,7 +1139,7 @@ def leave_module(self, node): module_imports = self._to_consume[0][1] consumed = {} - for klass in node.nodes_of_class(astroid.Class): + for klass in node.nodes_of_class(astroid.ClassDef): found = metaclass = name = None if not klass._metaclass: # Skip if this class doesn't use @@ -1043,7 +1165,7 @@ def leave_module(self, node): name = None if isinstance(klass._metaclass, astroid.Name): name = klass._metaclass.name - elif isinstance(klass._metaclass, astroid.Getattr): + elif isinstance(klass._metaclass, astroid.Attribute): name = klass._metaclass.as_string() if name is not None: diff --git a/pymode/libs/pylint/config.py b/pymode/libs/pylint/config.py index ebfe5789..2ef65837 100644 --- a/pymode/libs/pylint/config.py +++ b/pymode/libs/pylint/config.py @@ -16,40 +16,49 @@ * pylintrc * pylint.d (PYLINTHOME) """ -from __future__ import with_statement from __future__ import print_function -import pickle +# TODO(cpopa): this module contains the logic for the +# configuration parser and for the command line parser, +# but it's really coupled to optparse's internals. +# The code was copied almost verbatim from logilab.common, +# in order to not depend on it anymore and it will definitely +# need a cleanup. It could be completely reengineered as well. + +import contextlib +import collections +import copy +import optparse import os +import pickle +import re import sys -from os.path import exists, isfile, join, expanduser, abspath, dirname +import time + +from six.moves import configparser +from six.moves import range -# pylint home is used to save old runs results ################################ +from pylint import utils -USER_HOME = expanduser('~') + +USER_HOME = os.path.expanduser('~') if 'PYLINTHOME' in os.environ: PYLINT_HOME = os.environ['PYLINTHOME'] if USER_HOME == '~': - USER_HOME = dirname(PYLINT_HOME) + USER_HOME = os.path.dirname(PYLINT_HOME) elif USER_HOME == '~': PYLINT_HOME = ".pylint.d" else: - PYLINT_HOME = join(USER_HOME, '.pylint.d') + PYLINT_HOME = os.path.join(USER_HOME, '.pylint.d') -def get_pdata_path(base_name, recurs): - """return the path of the file which should contain old search data for the - given base_name with the given options values - """ + +def _get_pdata_path(base_name, recurs): base_name = base_name.replace(os.sep, '_') - return join(PYLINT_HOME, "%s%s%s"%(base_name, recurs, '.stats')) + return os.path.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) +def load_results(base): + data_file = _get_pdata_path(base, 1) try: with open(data_file, _PICK_LOAD) as stream: return pickle.load(stream) @@ -62,46 +71,47 @@ def load_results(base): _PICK_DUMP, _PICK_LOAD = 'wb', 'rb' def save_results(results, base): - """pickle results""" - if not exists(PYLINT_HOME): + if not os.path.exists(PYLINT_HOME): try: os.mkdir(PYLINT_HOME) except OSError: print('Unable to create directory %s' % PYLINT_HOME, file=sys.stderr) - data_file = get_pdata_path(base, 1) + data_file = _get_pdata_path(base, 1) try: with open(data_file, _PICK_DUMP) as stream: pickle.dump(results, stream) except (IOError, OSError) as ex: print('Unable to create file %s: %s' % (data_file, ex), file=sys.stderr) -# location of the configuration file ########################################## - def find_pylintrc(): """search the pylint rc file and return its path if it find it, else None """ # is there a pylint rc file in the current directory ? - if exists('pylintrc'): - return abspath('pylintrc') - if isfile('__init__.py'): - curdir = abspath(os.getcwd()) - while isfile(join(curdir, '__init__.py')): - curdir = abspath(join(curdir, '..')) - if isfile(join(curdir, 'pylintrc')): - return join(curdir, 'pylintrc') - if 'PYLINTRC' in os.environ and exists(os.environ['PYLINTRC']): + if os.path.exists('pylintrc'): + return os.path.abspath('pylintrc') + if os.path.exists('.pylintrc'): + return os.path.abspath('.pylintrc') + if os.path.isfile('__init__.py'): + curdir = os.path.abspath(os.getcwd()) + while os.path.isfile(os.path.join(curdir, '__init__.py')): + curdir = os.path.abspath(os.path.join(curdir, '..')) + if os.path.isfile(os.path.join(curdir, 'pylintrc')): + return os.path.join(curdir, 'pylintrc') + if os.path.isfile(os.path.join(curdir, '.pylintrc')): + return os.path.join(curdir, '.pylintrc') + if 'PYLINTRC' in os.environ and os.path.exists(os.environ['PYLINTRC']): pylintrc = os.environ['PYLINTRC'] else: - user_home = expanduser('~') + user_home = os.path.expanduser('~') if user_home == '~' or user_home == '/root': pylintrc = ".pylintrc" else: - pylintrc = join(user_home, '.pylintrc') - if not isfile(pylintrc): - pylintrc = join(user_home, '.config', 'pylintrc') - if not isfile(pylintrc): - if isfile('/etc/pylintrc'): + pylintrc = os.path.join(user_home, '.pylintrc') + if not os.path.isfile(pylintrc): + pylintrc = os.path.join(user_home, '.config', 'pylintrc') + if not os.path.isfile(pylintrc): + if os.path.isfile('/etc/pylintrc'): pylintrc = '/etc/pylintrc' else: pylintrc = None @@ -110,48 +120,695 @@ def find_pylintrc(): PYLINTRC = find_pylintrc() ENV_HELP = ''' -The following environment variables are used: - * PYLINTHOME - Path to the directory where the persistent for the run will be stored. If -not found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working -directory). - * PYLINTRC +The following environment variables are used: + * PYLINTHOME + Path to the directory where the persistent for the run will be stored. If +not found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working +directory). + * PYLINTRC Path to the configuration file. See the documentation for the method used to search for configuration file. ''' % globals() -# evaluation messages ######################################################### -def get_note_message(note): - """return a message according to note - note is a float < 10 (10 is the highest note) +class UnsupportedAction(Exception): + """raised by set_option when it doesn't know what to do for an action""" + + +def _multiple_choice_validator(choices, name, value): + values = utils._check_csv(value) + for value in values: + if value not in choices: + msg = "option %s: invalid value: %r, should be in %s" + raise optparse.OptionValueError(msg % (name, value, choices)) + return values + + +def _choice_validator(choices, name, value): + if value not in choices: + msg = "option %s: invalid value: %r, should be in %s" + raise optparse.OptionValueError(msg % (name, value, choices)) + return value + +# pylint: disable=unused-argument +def _csv_validator(_, name, value): + return utils._check_csv(value) + + +# pylint: disable=unused-argument +def _regexp_validator(_, name, value): + if hasattr(value, 'pattern'): + return value + return re.compile(value) + + +def _yn_validator(opt, _, value): + if isinstance(value, int): + return bool(value) + if value in ('y', 'yes'): + return True + if value in ('n', 'no'): + return False + msg = "option %s: invalid yn value %r, should be in (y, yes, n, no)" + raise optparse.OptionValueError(msg % (opt, value)) + + +VALIDATORS = { + 'string': utils._unquote, + 'int': int, + 'regexp': re.compile, + 'csv': _csv_validator, + 'yn': _yn_validator, + 'choice': lambda opt, name, value: _choice_validator(opt['choices'], name, value), + 'multiple_choice': lambda opt, name, value: _multiple_choice_validator(opt['choices'], + name, value), +} + +def _call_validator(opttype, optdict, option, value): + if opttype not in VALIDATORS: + raise Exception('Unsupported type "%s"' % opttype) + try: + return VALIDATORS[opttype](optdict, option, value) + except TypeError: + try: + return VALIDATORS[opttype](value) + except Exception: + raise optparse.OptionValueError('%s value (%r) should be of type %s' % + (option, value, opttype)) + + +def _validate(value, optdict, name=''): + """return a validated value for an option according to its type + + optional argument name is only used for error message formatting """ - 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...' + try: + _type = optdict['type'] + except KeyError: + # FIXME + return value + return _call_validator(_type, optdict, name, value) + + +def _level_options(group, outputlevel): + return [option for option in group.option_list + if (getattr(option, 'level', 0) or 0) <= outputlevel + and option.help is not optparse.SUPPRESS_HELP] + + +def _expand_default(self, option): + """Patch OptionParser.expand_default with custom behaviour + + This will handle defaults to avoid overriding values in the + configuration file. + """ + if self.parser is None or not self.default_tag: + return option.help + optname = option._long_opts[0][2:] + try: + provider = self.parser.options_manager._all_options[optname] + except KeyError: + value = None else: - msg = 'Wow ! Now this deserves our uttermost respect.\nPlease send \ -your code to python-projects@logilab.org' - return msg + optdict = provider.get_option_def(optname) + optname = provider.option_attrname(optname, optdict) + value = getattr(provider.config, optname, optdict) + value = utils._format_option_value(optdict, value) + if value is optparse.NO_DEFAULT or not value: + value = self.NO_DEFAULT_VALUE + return option.help.replace(self.default_tag, str(value)) + + +@contextlib.contextmanager +def _patch_optparse(): + orig_default = optparse.HelpFormatter + try: + optparse.HelpFormatter.expand_default = _expand_default + yield + finally: + optparse.HelpFormatter.expand_default = orig_default + + +def _multiple_choices_validating_option(opt, name, value): + return _multiple_choice_validator(opt.choices, name, value) + + +class Option(optparse.Option): + TYPES = optparse.Option.TYPES + ('regexp', 'csv', 'yn', 'multiple_choice') + ATTRS = optparse.Option.ATTRS + ['hide', 'level'] + TYPE_CHECKER = copy.copy(optparse.Option.TYPE_CHECKER) + TYPE_CHECKER['regexp'] = _regexp_validator + TYPE_CHECKER['csv'] = _csv_validator + TYPE_CHECKER['yn'] = _yn_validator + TYPE_CHECKER['multiple_choice'] = _multiple_choices_validating_option + + def __init__(self, *opts, **attrs): + optparse.Option.__init__(self, *opts, **attrs) + if hasattr(self, "hide") and self.hide: + self.help = optparse.SUPPRESS_HELP + + def _check_choice(self): + if self.type in ("choice", "multiple_choice"): + if self.choices is None: + raise optparse.OptionError( + "must supply a list of choices for type 'choice'", self) + elif not isinstance(self.choices, (tuple, list)): + raise optparse.OptionError( + "choices must be a list of strings ('%s' supplied)" + % str(type(self.choices)).split("'")[1], self) + elif self.choices is not None: + raise optparse.OptionError( + "must not supply choices for type %r" % self.type, self) + optparse.Option.CHECK_METHODS[2] = _check_choice + + def process(self, opt, value, values, parser): + # First, convert the value(s) to the right type. Howl if any + # value(s) are bogus. + value = self.convert_value(opt, value) + if self.type == 'named': + existant = getattr(values, self.dest) + if existant: + existant.update(value) + value = existant + # And then take whatever action is expected of us. + # This is a separate method to make life easier for + # subclasses to add new actions. + return self.take_action( + self.action, self.dest, opt, value, values, parser) + + +class OptionParser(optparse.OptionParser): + + def __init__(self, option_class=Option, *args, **kwargs): + optparse.OptionParser.__init__(self, option_class=Option, *args, **kwargs) + + def format_option_help(self, formatter=None): + if formatter is None: + formatter = self.formatter + outputlevel = getattr(formatter, 'output_level', 0) + formatter.store_option_strings(self) + result = [] + result.append(formatter.format_heading("Options")) + formatter.indent() + if self.option_list: + result.append(optparse.OptionContainer.format_option_help(self, formatter)) + result.append("\n") + for group in self.option_groups: + if group.level <= outputlevel and ( + group.description or _level_options(group, outputlevel)): + result.append(group.format_help(formatter)) + result.append("\n") + formatter.dedent() + # Drop the last "\n", or the header if no options or option groups: + return "".join(result[:-1]) + + def _match_long_opt(self, opt): + """Disable abbreviations.""" + if opt not in self._long_opt: + raise optparse.BadOptionError(opt) + return opt + + +# pylint: disable=abstract-method; by design? +class _ManHelpFormatter(optparse.HelpFormatter): + + def __init__(self, indent_increment=0, max_help_position=24, + width=79, short_first=0): + optparse.HelpFormatter.__init__( + self, indent_increment, max_help_position, width, short_first) + + def format_heading(self, heading): + return '.SH %s\n' % heading.upper() + + def format_description(self, description): + return description + + def format_option(self, option): + try: + optstring = option.option_strings + except AttributeError: + optstring = self.format_option_strings(option) + if option.help: + help_text = self.expand_default(option) + help = ' '.join([l.strip() for l in help_text.splitlines()]) + else: + help = '' + return '''.IP "%s" +%s +''' % (optstring, help) + + def format_head(self, optparser, pkginfo, section=1): + long_desc = "" + try: + pgm = optparser._get_prog_name() + except AttributeError: + # py >= 2.4.X (dunno which X exactly, at least 2) + pgm = optparser.get_prog_name() + short_desc = self.format_short_description(pgm, pkginfo.description) + if hasattr(pkginfo, "long_desc"): + long_desc = self.format_long_description(pgm, pkginfo.long_desc) + return '%s\n%s\n%s\n%s' % (self.format_title(pgm, section), + short_desc, self.format_synopsis(pgm), + long_desc) + + @staticmethod + def format_title(pgm, section): + date = '-'.join(str(num) for num in time.localtime()[:3]) + return '.TH %s %s "%s" %s' % (pgm, section, date, pgm) + + @staticmethod + def format_short_description(pgm, short_desc): + return '''.SH NAME +.B %s +\- %s +''' % (pgm, short_desc.strip()) + + @staticmethod + def format_synopsis(pgm): + return '''.SH SYNOPSIS +.B %s +[ +.I OPTIONS +] [ +.I +] +''' % pgm + + @staticmethod + def format_long_description(pgm, long_desc): + long_desc = '\n'.join(line.lstrip() + for line in long_desc.splitlines()) + long_desc = long_desc.replace('\n.\n', '\n\n') + if long_desc.lower().startswith(pgm): + long_desc = long_desc[len(pgm):] + return '''.SH DESCRIPTION +.B %s +%s +''' % (pgm, long_desc.strip()) + + @staticmethod + def format_tail(pkginfo): + tail = '''.SH SEE ALSO +/usr/share/doc/pythonX.Y-%s/ + +.SH BUGS +Please report bugs on the project\'s mailing list: +%s + +.SH AUTHOR +%s <%s> +''' % (getattr(pkginfo, 'debian_name', pkginfo.modname), + pkginfo.mailinglist, pkginfo.author, pkginfo.author_email) + + if hasattr(pkginfo, "copyright"): + tail += ''' +.SH COPYRIGHT +%s +''' % pkginfo.copyright + + return tail + + +class OptionsManagerMixIn(object): + """Handle configuration from both a configuration file and command line options""" + + def __init__(self, usage, config_file=None, version=None, quiet=0): + self.config_file = config_file + self.reset_parsers(usage, version=version) + # list of registered options providers + self.options_providers = [] + # dictionary associating option name to checker + self._all_options = collections.OrderedDict() + self._short_options = {} + self._nocallback_options = {} + self._mygroups = {} + # verbosity + self.quiet = quiet + self._maxlevel = 0 + + def reset_parsers(self, usage='', version=None): + # configuration file parser + self.cfgfile_parser = configparser.ConfigParser() + # command line parser + self.cmdline_parser = OptionParser(usage=usage, version=version) + self.cmdline_parser.options_manager = self + self._optik_option_attrs = set(self.cmdline_parser.option_class.ATTRS) + + def register_options_provider(self, provider, own_group=True): + """register an options provider""" + assert provider.priority <= 0, "provider's priority can't be >= 0" + for i in range(len(self.options_providers)): + if provider.priority > self.options_providers[i].priority: + self.options_providers.insert(i, provider) + break + else: + self.options_providers.append(provider) + non_group_spec_options = [option for option in provider.options + if 'group' not in option[1]] + groups = getattr(provider, 'option_groups', ()) + if own_group and non_group_spec_options: + self.add_option_group(provider.name.upper(), provider.__doc__, + non_group_spec_options, provider) + else: + for opt, optdict in non_group_spec_options: + self.add_optik_option(provider, self.cmdline_parser, opt, optdict) + for gname, gdoc in groups: + gname = gname.upper() + goptions = [option for option in provider.options + if option[1].get('group', '').upper() == gname] + self.add_option_group(gname, gdoc, goptions, provider) + + def add_option_group(self, group_name, _, options, provider): + # add option group to the command line parser + if group_name in self._mygroups: + group = self._mygroups[group_name] + else: + group = optparse.OptionGroup(self.cmdline_parser, + title=group_name.capitalize()) + self.cmdline_parser.add_option_group(group) + group.level = provider.level + self._mygroups[group_name] = group + # add section to the config file + if group_name != "DEFAULT": + self.cfgfile_parser.add_section(group_name) + # add provider's specific options + for opt, optdict in options: + self.add_optik_option(provider, group, opt, optdict) + + def add_optik_option(self, provider, optikcontainer, opt, optdict): + args, optdict = self.optik_option(provider, opt, optdict) + option = optikcontainer.add_option(*args, **optdict) + self._all_options[opt] = provider + self._maxlevel = max(self._maxlevel, option.level or 0) + + def optik_option(self, provider, opt, optdict): + """get our personal option definition and return a suitable form for + use with optik/optparse + """ + optdict = copy.copy(optdict) + if 'action' in optdict: + self._nocallback_options[provider] = opt + else: + optdict['action'] = 'callback' + optdict['callback'] = self.cb_set_provider_option + # default is handled here and *must not* be given to optik if you + # want the whole machinery to work + if 'default' in optdict: + if ('help' in optdict + and optdict.get('default') is not None + and optdict['action'] not in ('store_true', 'store_false')): + optdict['help'] += ' [current: %default]' + del optdict['default'] + args = ['--' + str(opt)] + if 'short' in optdict: + self._short_options[optdict['short']] = opt + args.append('-' + optdict['short']) + del optdict['short'] + # cleanup option definition dict before giving it to optik + for key in list(optdict.keys()): + if key not in self._optik_option_attrs: + optdict.pop(key) + return args, optdict + + def cb_set_provider_option(self, option, opt, value, parser): + """optik callback for option setting""" + if opt.startswith('--'): + # remove -- on long option + opt = opt[2:] + else: + # short option, get its long equivalent + opt = self._short_options[opt[1:]] + # trick since we can't set action='store_true' on options + if value is None: + value = 1 + self.global_set_option(opt, value) + + def global_set_option(self, opt, value): + """set option on the correct option provider""" + self._all_options[opt].set_option(opt, value) + + def generate_config(self, stream=None, skipsections=(), encoding=None): + """write a configuration file according to the current configuration + into the given stream or stdout + """ + options_by_section = {} + sections = [] + for provider in self.options_providers: + for section, options in provider.options_by_section(): + if section is None: + section = provider.name + if section in skipsections: + continue + options = [(n, d, v) for (n, d, v) in options + if d.get('type') is not None + and not d.get('deprecated')] + if not options: + continue + if section not in sections: + sections.append(section) + alloptions = options_by_section.setdefault(section, []) + alloptions += options + stream = stream or sys.stdout + encoding = utils._get_encoding(encoding, stream) + printed = False + for section in sections: + if printed: + print('\n', file=stream) + utils.format_section(stream, section.upper(), + options_by_section[section], + encoding) + printed = True + + def generate_manpage(self, pkginfo, section=1, stream=None): + with _patch_optparse(): + _generate_manpage(self.cmdline_parser, pkginfo, + section, stream=stream or sys.stdout, + level=self._maxlevel) + + def load_provider_defaults(self): + """initialize configuration using default values""" + for provider in self.options_providers: + provider.load_defaults() + + def read_config_file(self, config_file=None): + """read the configuration file but do not load it (i.e. dispatching + values to each options provider) + """ + helplevel = 1 + while helplevel <= self._maxlevel: + opt = '-'.join(['long'] * helplevel) + '-help' + if opt in self._all_options: + break # already processed + # pylint: disable=unused-argument + def helpfunc(option, opt, val, p, level=helplevel): + print(self.help(level)) + sys.exit(0) + helpmsg = '%s verbose help.' % ' '.join(['more'] * helplevel) + optdict = {'action': 'callback', 'callback': helpfunc, + 'help': helpmsg} + provider = self.options_providers[0] + self.add_optik_option(provider, self.cmdline_parser, opt, optdict) + provider.options += ((opt, optdict),) + helplevel += 1 + if config_file is None: + config_file = self.config_file + if config_file is not None: + config_file = os.path.expanduser(config_file) + if config_file and os.path.exists(config_file): + parser = self.cfgfile_parser + parser.read([config_file]) + # normalize sections'title + for sect, values in list(parser._sections.items()): + if not sect.isupper() and values: + parser._sections[sect.upper()] = values + elif not self.quiet: + msg = 'No config file found, using default configuration' + print(msg, file=sys.stderr) + return + + def load_config_file(self): + """dispatch values previously read from a configuration file to each + options provider) + """ + parser = self.cfgfile_parser + for section in parser.sections(): + for option, value in parser.items(section): + try: + self.global_set_option(option, value) + except (KeyError, optparse.OptionError): + # TODO handle here undeclared options appearing in the config file + continue + + def load_configuration(self, **kwargs): + """override configuration according to given parameters""" + return self.load_configuration_from_config(kwargs) + + def load_configuration_from_config(self, config): + for opt, opt_value in config.items(): + opt = opt.replace('_', '-') + provider = self._all_options[opt] + provider.set_option(opt, opt_value) + + def load_command_line_configuration(self, args=None): + """Override configuration according to command line parameters + + return additional arguments + """ + with _patch_optparse(): + if args is None: + args = sys.argv[1:] + else: + args = list(args) + (options, args) = self.cmdline_parser.parse_args(args=args) + for provider in self._nocallback_options.keys(): + config = provider.config + for attr in config.__dict__.keys(): + value = getattr(options, attr, None) + if value is None: + continue + setattr(config, attr, value) + return args + + def add_help_section(self, title, description, level=0): + """add a dummy option section for help purpose """ + group = optparse.OptionGroup(self.cmdline_parser, + title=title.capitalize(), + description=description) + group.level = level + self._maxlevel = max(self._maxlevel, level) + self.cmdline_parser.add_option_group(group) + + def help(self, level=0): + """return the usage string for available options """ + self.cmdline_parser.formatter.output_level = level + with _patch_optparse(): + return self.cmdline_parser.format_help() + + +class OptionsProviderMixIn(object): + """Mixin to provide options to an OptionsManager""" + + # those attributes should be overridden + priority = -1 + name = 'default' + options = () + level = 0 + + def __init__(self): + self.config = optparse.Values() + self.load_defaults() + + def load_defaults(self): + """initialize the provider using default values""" + for opt, optdict in self.options: + action = optdict.get('action') + if action != 'callback': + # callback action have no default + if optdict is None: + optdict = self.get_option_def(opt) + default = optdict.get('default') + self.set_option(opt, default, action, optdict) + + def option_attrname(self, opt, optdict=None): + """get the config attribute corresponding to opt""" + if optdict is None: + optdict = self.get_option_def(opt) + return optdict.get('dest', opt.replace('-', '_')) + + def option_value(self, opt): + """get the current value for the given option""" + return getattr(self.config, self.option_attrname(opt), None) + + def set_option(self, opt, value, action=None, optdict=None): + """method called to set an option (registered in the options list)""" + if optdict is None: + optdict = self.get_option_def(opt) + if value is not None: + value = _validate(value, optdict, opt) + if action is None: + action = optdict.get('action', 'store') + if action == 'store': + setattr(self.config, self.option_attrname(opt, optdict), value) + elif action in ('store_true', 'count'): + setattr(self.config, self.option_attrname(opt, optdict), 0) + elif action == 'store_false': + setattr(self.config, self.option_attrname(opt, optdict), 1) + elif action == 'append': + opt = self.option_attrname(opt, optdict) + _list = getattr(self.config, opt, None) + if _list is None: + if isinstance(value, (list, tuple)): + _list = value + elif value is not None: + _list = [] + _list.append(value) + setattr(self.config, opt, _list) + elif isinstance(_list, tuple): + setattr(self.config, opt, _list + (value,)) + else: + _list.append(value) + elif action == 'callback': + optdict['callback'](None, opt, value, None) + else: + raise UnsupportedAction(action) + + def get_option_def(self, opt): + """return the dictionary defining an option given its name""" + assert self.options + for option in self.options: + if option[0] == opt: + return option[1] + raise optparse.OptionError('no such option %s in section %r' + % (opt, self.name), opt) + + def options_by_section(self): + """return an iterator on options grouped by section + + (section, [list of (optname, optdict, optvalue)]) + """ + sections = {} + for optname, optdict in self.options: + sections.setdefault(optdict.get('group'), []).append( + (optname, optdict, self.option_value(optname))) + if None in sections: + yield None, sections.pop(None) + for section, options in sorted(sections.items()): + yield section.upper(), options + + def options_and_values(self, options=None): + if options is None: + options = self.options + for optname, optdict in options: + yield (optname, optdict, self.option_value(optname)) + + +class ConfigurationMixIn(OptionsManagerMixIn, OptionsProviderMixIn): + """basic mixin for simple configurations which don't need the + manager / providers model + """ + def __init__(self, *args, **kwargs): + if not args: + kwargs.setdefault('usage', '') + kwargs.setdefault('quiet', 1) + OptionsManagerMixIn.__init__(self, *args, **kwargs) + OptionsProviderMixIn.__init__(self) + if not getattr(self, 'option_groups', None): + self.option_groups = [] + for _, optdict in self.options: + try: + gdef = (optdict['group'].upper(), '') + except KeyError: + continue + if gdef not in self.option_groups: + self.option_groups.append(gdef) + self.register_options_provider(self, own_group=False) + + +def _generate_manpage(optparser, pkginfo, section=1, + stream=sys.stdout, level=0): + formatter = _ManHelpFormatter() + formatter.output_level = level + formatter.parser = optparser + print(formatter.format_head(optparser, pkginfo, section), file=stream) + print(optparser.format_option_help(formatter), file=stream) + print(formatter.format_tail(pkginfo), file=stream) diff --git a/pymode/libs/pylint/epylint.py b/pymode/libs/pylint/epylint.py index 3d73ecd3..12127e0c 100644 --- a/pymode/libs/pylint/epylint.py +++ b/pymode/libs/pylint/epylint.py @@ -48,8 +48,9 @@ """ from __future__ import print_function -import sys, os +import os import os.path as osp +import sys from subprocess import Popen, PIPE def _get_env(): @@ -154,12 +155,12 @@ def py_run(command_options='', return_std=False, stdout=None, stderr=None, else: stderr = sys.stderr # Call pylint in a subprocess - p = Popen(command_line, shell=True, stdout=stdout, stderr=stderr, - env=_get_env(), universal_newlines=True) - p.wait() + process = Popen(command_line, shell=True, stdout=stdout, stderr=stderr, + env=_get_env(), universal_newlines=True) + process.wait() # Return standard output and error if return_std: - return (p.stdout, p.stderr) + return (process.stdout, process.stderr) def Run(): diff --git a/pymode/libs/pylint/extensions/__init__.py b/pymode/libs/pylint/extensions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymode/libs/pylint/extensions/check_docs.py b/pymode/libs/pylint/extensions/check_docs.py new file mode 100644 index 00000000..8f529123 --- /dev/null +++ b/pymode/libs/pylint/extensions/check_docs.py @@ -0,0 +1,311 @@ +"""Pylint plugin for parameter documentation checking in Sphinx, Google or +Numpy style docstrings +""" +from __future__ import print_function, division, absolute_import + +import re + +from pylint.interfaces import IAstroidChecker +from pylint.checkers import BaseChecker +from pylint.checkers.utils import node_frame_class + + +def space_indentation(s): + """The number of leading spaces in a string + + :param str s: input string + + :rtype: int + :return: number of leading spaces + """ + return len(s) - len(s.lstrip(' ')) + + +class ParamDocChecker(BaseChecker): + """Checker for parameter documentation in Sphinx, Google or Numpy style + docstrings + + * Check that all function, method and constructor parameters are mentioned + in the params and types part of the docstring. By convention, + constructor parameters are documented in the class docstring. + * Check that there are no naming inconsistencies between the signature and + the documentation, i.e. also report documented parameters that are missing + in the signature. This is important to find cases where parameters are + renamed only in the code, not in the documentation. + + Activate this checker by adding the line:: + + load-plugins=pylint.extensions.check_docs + + to the ``MASTER`` section of your ``.pylintrc``. + + :param linter: linter object + :type linter: :class:`pylint.lint.PyLinter` + """ + __implements__ = IAstroidChecker + + name = 'param_checks' + msgs = { + 'W9003': ('"%s" missing or differing in parameter documentation', + 'missing-param-doc', + 'Please add parameter declarations for all parameters.'), + 'W9004': ('"%s" missing or differing in parameter type documentation', + 'missing-type-doc', + 'Please add parameter type declarations for all parameters.'), + } + + options = (('accept-no-param-doc', + {'default': True, 'type' : 'yn', 'metavar' : '', + 'help': 'Whether to accept totally missing parameter ' + 'documentation in a docstring of a function that has ' + 'parameters' + }), + ) + + priority = -2 + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + + constructor_names = set(["__init__", "__new__"]) + + def visit_functiondef(self, node): + """Called for function and method definitions (def). + + :param node: Node for a function or method definition in the AST + :type node: :class:`astroid.scoped_nodes.Function` + """ + if node.name in self.constructor_names: + class_node = node_frame_class(node) + if class_node is not None: + self.check_arguments_in_docstring( + class_node.doc, node.args, class_node) + return + self.check_arguments_in_docstring(node.doc, node.args, node) + + re_for_parameters_see = re.compile(r""" + For\s+the\s+(other)?\s*parameters\s*,\s+see + """, re.X | re.S) + + re_sphinx_param_in_docstring = re.compile(r""" + :param # Sphinx keyword + \s+ # whitespace + + (?: # optional type declaration + (\w+) + \s+ + )? + + (\w+) # Parameter name + \s* # whitespace + : # final colon + """, re.X | re.S) + + re_sphinx_type_in_docstring = re.compile(r""" + :type # Sphinx keyword + \s+ # whitespace + (\w+) # Parameter name + \s* # whitespace + : # final colon + """, re.X | re.S) + + re_google_param_section = re.compile(r""" + ^([ ]*) Args \s*: \s*?$ # Google parameter header + ( .* ) # section + """, re.X | re.S | re.M) + + re_google_param_line = re.compile(r""" + \s* (\w+) # identifier + \s* ( [(] .*? [)] )? \s* : # optional type declaration + \s* ( \w+ )? # beginning of optional description + """, re.X) + + re_numpy_param_section = re.compile(r""" + ^([ ]*) Parameters \s*?$ # Numpy parameters header + \s* [-=]+ \s*?$ # underline + ( .* ) # section + """, re.X | re.S | re.M) + + re_numpy_param_line = re.compile(r""" + \s* (\w+) # identifier + \s* : + \s* ( \w+ )? # optional type declaration + """, re.X) + + not_needed_param_in_docstring = set(['self', 'cls']) + + def check_arguments_in_docstring(self, doc, arguments_node, warning_node): + """Check that all parameters in a function, method or class constructor + on the one hand and the parameters mentioned in the parameter + documentation (e.g. the Sphinx tags 'param' and 'type') on the other + hand are consistent with each other. + + * Undocumented parameters except 'self' are noticed. + * Undocumented parameter types except for 'self' and the ``*`` + and ``**`` parameters are noticed. + * Parameters mentioned in the parameter documentation that don't or no + longer exist in the function parameter list are noticed. + * If the text "For the parameters, see" or "For the other parameters, + see" (ignoring additional whitespace) is mentioned in the docstring, + missing parameter documentation is tolerated. + * If there's no Sphinx style, Google style or NumPy style parameter + documentation at all, i.e. ``:param`` is never mentioned etc., the + checker assumes that the parameters are documented in another format + and the absence is tolerated. + + :param doc: Docstring for the function, method or class. + :type doc: str + + :param arguments_node: Arguments node for the function, method or + class constructor. + :type arguments_node: :class:`astroid.scoped_nodes.Arguments` + + :param warning_node: The node to assign the warnings to + :type warning_node: :class:`astroid.scoped_nodes.Node` + """ + # Tolerate missing param or type declarations if there is a link to + # another method carrying the same name. + if doc is None: + return + + doc = doc.expandtabs() + + tolerate_missing_params = self.re_for_parameters_see.search(doc) is not None + + # Collect the function arguments. + expected_argument_names = [arg.name for arg in arguments_node.args] + expected_argument_names += [arg.name for arg in arguments_node.kwonlyargs] + not_needed_type_in_docstring = ( + self.not_needed_param_in_docstring.copy()) + + if arguments_node.vararg is not None: + expected_argument_names.append(arguments_node.vararg) + not_needed_type_in_docstring.add(arguments_node.vararg) + if arguments_node.kwarg is not None: + expected_argument_names.append(arguments_node.kwarg) + not_needed_type_in_docstring.add(arguments_node.kwarg) + params_with_doc, params_with_type = self.match_param_docs(doc) + + # Tolerate no parameter documentation at all. + if (not params_with_doc and not params_with_type + and self.config.accept_no_param_doc): + tolerate_missing_params = True + + def _compare_args(found_argument_names, message_id, not_needed_names): + """Compare the found argument names with the expected ones and + generate a message if there are inconsistencies. + + :param list found_argument_names: argument names found in the + docstring + + :param str message_id: pylint message id + + :param not_needed_names: names that may be omitted + :type not_needed_names: set of str + """ + if not tolerate_missing_params: + missing_or_differing_argument_names = ( + (set(expected_argument_names) ^ set(found_argument_names)) + - not_needed_names) + else: + missing_or_differing_argument_names = ( + (set(found_argument_names) - set(expected_argument_names)) + - not_needed_names) + + if missing_or_differing_argument_names: + self.add_message( + message_id, + args=(', '.join( + sorted(missing_or_differing_argument_names)),), + node=warning_node) + + _compare_args(params_with_doc, 'missing-param-doc', + self.not_needed_param_in_docstring) + _compare_args(params_with_type, 'missing-type-doc', + not_needed_type_in_docstring) + + def match_param_docs(self, doc): + """Match parameter documentation in docstrings written in Sphinx, Google + or NumPy style + + :param str doc: docstring + + :return: tuple of lists of str: params_with_doc, params_with_type + """ + params_with_doc = [] + params_with_type = [] + + if self.re_sphinx_param_in_docstring.search(doc) is not None: + # Sphinx param declarations + for match in re.finditer(self.re_sphinx_param_in_docstring, doc): + name = match.group(2) + params_with_doc.append(name) + if match.group(1) is not None: + params_with_type.append(name) + + # Sphinx type declarations + params_with_type += re.findall( + self.re_sphinx_type_in_docstring, doc) + else: + match = self.re_google_param_section.search(doc) + if match is not None: + is_google = True + re_line = self.re_google_param_line + else: + match = self.re_numpy_param_section.search(doc) + if match is not None: + is_google = False + re_line = self.re_numpy_param_line + else: + # some other documentation style + return [], [] + + min_indentation = len(match.group(1)) + if is_google: + min_indentation += 1 + + prev_param_name = None + is_first = True + for line in match.group(2).splitlines(): + if not line.strip(): + continue + indentation = space_indentation(line) + if indentation < min_indentation: + break + + # The first line after the header defines the minimum + # indentation. + if is_first: + min_indentation = indentation + is_first = False + + if indentation > min_indentation: + # Lines with more than minimum indentation must contain a + # description. + if (not params_with_doc + or params_with_doc[-1] != prev_param_name): + assert prev_param_name is not None + params_with_doc.append(prev_param_name) + else: + # Lines with minimum indentation must contain the beginning + # of a new parameter documentation. + match = re_line.match(line) + if match is None: + break + prev_param_name = match.group(1) + if match.group(2) is not None: + params_with_type.append(prev_param_name) + + if is_google and match.group(3) is not None: + params_with_doc.append(prev_param_name) + + return params_with_doc, params_with_type + + +def register(linter): + """Required method to auto register this checker. + + :param linter: Main interface object for Pylint plugins + :type linter: Pylint object + """ + linter.register_checker(ParamDocChecker(linter)) diff --git a/pymode/libs/pylint/extensions/check_elif.py b/pymode/libs/pylint/extensions/check_elif.py new file mode 100644 index 00000000..11521ec6 --- /dev/null +++ b/pymode/libs/pylint/extensions/check_elif.py @@ -0,0 +1,62 @@ +import astroid +from pylint.checkers import BaseTokenChecker +from pylint.checkers.utils import check_messages +from pylint.interfaces import ITokenChecker, IAstroidChecker + + +class ElseifUsedChecker(BaseTokenChecker): + """Checks for use of "else if" when a "elif" could be used + """ + + __implements__ = (ITokenChecker, IAstroidChecker) + name = 'elseifused' + msgs = {'R5501': ('Consider using "elif" instead of "else if"', + 'else-if-used', + 'Used when an else statement is immediately followed by ' + 'an if statement and does not contain statements that ' + 'would be unrelated to it.'), + } + + def __init__(self, linter=None): + BaseTokenChecker.__init__(self, linter) + self._init() + + def _init(self): + self._elifs = [] + self._if_counter = 0 + + def process_tokens(self, tokens): + # Process tokens and look for 'if' or 'elif' + for _, token, _, _, _ in tokens: + if token == 'elif': + self._elifs.append(True) + elif token == 'if': + self._elifs.append(False) + + def leave_module(self, _): + self._init() + + def visit_ifexp(self, _): + self._if_counter += 1 + + def visit_comprehension(self, node): + self._if_counter += len(node.ifs) + + @check_messages('else-if-used') + def visit_if(self, node): + if isinstance(node.parent, astroid.If): + orelse = node.parent.orelse + # current if node must directly follow a "else" + if orelse and orelse == [node]: + if not self._elifs[self._if_counter]: + self.add_message('else-if-used', node=node) + self._if_counter += 1 + + +def register(linter): + """Required method to auto register this checker. + + :param linter: Main interface object for Pylint plugins + :type linter: Pylint object + """ + linter.register_checker(ElseifUsedChecker(linter)) diff --git a/pymode/libs/pylint/graph.py b/pymode/libs/pylint/graph.py new file mode 100644 index 00000000..17ff75dc --- /dev/null +++ b/pymode/libs/pylint/graph.py @@ -0,0 +1,179 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# This file was copied from logilab-common with the same license: +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +"""Graph manipulation utilities. + +(dot generation adapted from pypy/translator/tool/make_dot.py) +""" + +import os.path as osp +import os +import sys +import tempfile +import codecs + +def target_info_from_filename(filename): + """Transforms /some/path/foo.png into ('/some/path', 'foo.png', 'png').""" + basename = osp.basename(filename) + storedir = osp.dirname(osp.abspath(filename)) + target = filename.split('.')[-1] + return storedir, basename, target + + +class DotBackend(object): + """Dot File backend.""" + def __init__(self, graphname, rankdir=None, size=None, ratio=None, + charset='utf-8', renderer='dot', additional_param=None): + if additional_param is None: + additional_param = {} + self.graphname = graphname + self.renderer = renderer + self.lines = [] + self._source = None + self.emit("digraph %s {" % normalize_node_id(graphname)) + if rankdir: + self.emit('rankdir=%s' % rankdir) + if ratio: + self.emit('ratio=%s' % ratio) + if size: + self.emit('size="%s"' % size) + if charset: + assert charset.lower() in ('utf-8', 'iso-8859-1', 'latin1'), \ + 'unsupported charset %s' % charset + self.emit('charset="%s"' % charset) + for param in additional_param.items(): + self.emit('='.join(param)) + + def get_source(self): + """returns self._source""" + if self._source is None: + self.emit("}\n") + self._source = '\n'.join(self.lines) + del self.lines + return self._source + + source = property(get_source) + + def generate(self, outputfile=None, dotfile=None, mapfile=None): + """Generates a graph file. + + :param outputfile: filename and path [defaults to graphname.png] + :param dotfile: filename and path [defaults to graphname.dot] + + :rtype: str + :return: a path to the generated file + """ + import subprocess # introduced in py 2.4 + name = self.graphname + if not dotfile: + # if 'outputfile' is a dot file use it as 'dotfile' + if outputfile and outputfile.endswith(".dot"): + dotfile = outputfile + else: + dotfile = '%s.dot' % name + if outputfile is not None: + storedir, _, target = target_info_from_filename(outputfile) + if target != "dot": + pdot, dot_sourcepath = tempfile.mkstemp(".dot", name) + os.close(pdot) + else: + dot_sourcepath = osp.join(storedir, dotfile) + else: + target = 'png' + pdot, dot_sourcepath = tempfile.mkstemp(".dot", name) + ppng, outputfile = tempfile.mkstemp(".png", name) + os.close(pdot) + os.close(ppng) + pdot = codecs.open(dot_sourcepath, 'w', encoding='utf8') + pdot.write(self.source) + pdot.close() + if target != 'dot': + use_shell = sys.platform == 'win32' + if mapfile: + subprocess.call([self.renderer, '-Tcmapx', '-o', + mapfile, '-T', target, dot_sourcepath, + '-o', outputfile], + shell=use_shell) + else: + subprocess.call([self.renderer, '-T', target, + dot_sourcepath, '-o', outputfile], + shell=use_shell) + os.unlink(dot_sourcepath) + return outputfile + + def emit(self, line): + """Adds to final output.""" + self.lines.append(line) + + def emit_edge(self, name1, name2, **props): + """emit an edge from to . + edge properties: see http://www.graphviz.org/doc/info/attrs.html + """ + attrs = ['%s="%s"' % (prop, value) for prop, value in props.items()] + n_from, n_to = normalize_node_id(name1), normalize_node_id(name2) + self.emit('%s -> %s [%s];' % (n_from, n_to, ', '.join(sorted(attrs)))) + + def emit_node(self, name, **props): + """emit a node with given properties. + node properties: see http://www.graphviz.org/doc/info/attrs.html + """ + attrs = ['%s="%s"' % (prop, value) for prop, value in props.items()] + self.emit('%s [%s];' % (normalize_node_id(name), ', '.join(sorted(attrs)))) + +def normalize_node_id(nid): + """Returns a suitable DOT node id for `nid`.""" + return '"%s"' % nid + +def get_cycles(graph_dict, vertices=None): + '''given a dictionary representing an ordered graph (i.e. key are vertices + and values is a list of destination vertices representing edges), return a + list of detected cycles + ''' + if not graph_dict: + return () + result = [] + if vertices is None: + vertices = graph_dict.keys() + for vertice in vertices: + _get_cycles(graph_dict, [], set(), result, vertice) + return result + +def _get_cycles(graph_dict, path, visited, result, vertice): + """recursive function doing the real work for get_cycles""" + if vertice in path: + cycle = [vertice] + for node in path[::-1]: + if node == vertice: + break + cycle.insert(0, node) + # make a canonical representation + start_from = min(cycle) + index = cycle.index(start_from) + cycle = cycle[index:] + cycle[0:index] + # append it to result if not already in + if cycle not in result: + result.append(cycle) + return + path.append(vertice) + try: + for node in graph_dict[vertice]: + # don't check already visited nodes again + if node not in visited: + _get_cycles(graph_dict, path, visited, result, node) + visited.add(node) + except KeyError: + pass + path.pop() diff --git a/pymode/libs/pylint/gui.py b/pymode/libs/pylint/gui.py index 8327e0ec..10ee13e9 100644 --- a/pymode/libs/pylint/gui.py +++ b/pymode/libs/pylint/gui.py @@ -27,7 +27,7 @@ Tk, Frame, Listbox, Entry, Label, Button, Scrollbar, Checkbutton, Radiobutton, IntVar, StringVar, PanedWindow, TOP, LEFT, RIGHT, BOTTOM, END, X, Y, BOTH, SUNKEN, W, - HORIZONTAL, DISABLED, NORMAL, W, + HORIZONTAL, DISABLED, NORMAL, ) from six.moves.tkinter_tkfiledialog import ( askopenfilename, askdirectory, @@ -455,7 +455,7 @@ def set_history_window(self): try: view_history = open(HOME+HISTORY, 'r') for hist in view_history.readlines(): - if not hist in self.filenames: + if hist not in self.filenames: self.filenames.append(hist) self.showhistory.insert(END, hist.split('\n')[0]) view_history.close() diff --git a/pymode/libs/pylint/interfaces.py b/pymode/libs/pylint/interfaces.py index 64f5a956..2f6527ac 100644 --- a/pymode/libs/pylint/interfaces.py +++ b/pymode/libs/pylint/interfaces.py @@ -13,8 +13,6 @@ """Interfaces for Pylint objects""" from collections import namedtuple -from logilab.common.interface import Interface - Confidence = namedtuple('Confidence', ['name', 'description']) # Warning Certainties HIGH = Confidence('HIGH', 'No false positive possible.') @@ -27,6 +25,26 @@ CONFIDENCE_LEVELS = [HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED] +class Interface(object): + """Base class for interfaces.""" + @classmethod + def is_implemented_by(cls, instance): + return implements(instance, cls) + + +def implements(obj, interface): + """Return true if the give object (maybe an instance or class) implements + the interface. + """ + kimplements = getattr(obj, '__implements__', ()) + if not isinstance(kimplements, (list, tuple)): + kimplements = (kimplements,) + for implementedinterface in kimplements: + if issubclass(implementedinterface, interface): + return True + return False + + class IChecker(Interface): """This is an base interface, not designed to be used elsewhere than for sub interfaces definition. @@ -76,7 +94,7 @@ def add_message(self, msg_id, location, msg): msg is the actual message """ - def display_results(self, layout): + def display_reports(self, layout): """display results encapsulated in the layout tree """ diff --git a/pymode/libs/pylint/lint.py b/pymode/libs/pylint/lint.py index 01fc2f5d..c5f9328f 100644 --- a/pymode/libs/pylint/lint.py +++ b/pymode/libs/pylint/lint.py @@ -29,7 +29,6 @@ import collections import contextlib -import itertools import operator import os try: @@ -40,23 +39,18 @@ import tokenize import warnings +import six + import astroid from astroid.__pkginfo__ import version as astroid_version from astroid import modutils -from logilab.common import configuration -from logilab.common import optik_ext -from logilab.common import interface -from logilab.common import textutils -from logilab.common import ureports -from logilab.common import __version__ as common_version -import six - from pylint import checkers from pylint import interfaces from pylint import reporters from pylint import utils from pylint import config from pylint.__pkginfo__ import version +from pylint.reporters.ureports import nodes as report_nodes MANAGER = astroid.MANAGER @@ -97,7 +91,11 @@ def _get_python_path(filepath): def _merge_stats(stats): merged = {} + by_msg = collections.Counter() for stat in stats: + message_stats = stat.pop('by_msg', {}) + by_msg.update(message_stats) + for key, item in six.iteritems(stat): if key not in merged: merged[key] = item @@ -106,6 +104,8 @@ def _merge_stats(stats): merged[key].update(item) else: merged[key] = merged[key] + item + + merged['by_msg'] = by_msg return merged @@ -139,10 +139,6 @@ def _patch_sysmodules(): 'Used when an unexpected error occurred while building the ' 'Astroid representation. This is usually accompanied by a ' 'traceback. Please report such errors !'), - 'F0003': ('ignored builtin module %s', - 'ignored-builtin-module', - 'Used to indicate that the user asked to analyze a builtin ' - 'module which has been skipped.'), 'F0010': ('error while code parsing: %s', 'parse-error', 'Used when an exception occured while building the Astroid ' @@ -199,21 +195,16 @@ def _patch_sysmodules(): } -def _deprecated_option(shortname, opt_type, help_msg): - def _warn_deprecated(option, optname, *args): # pylint: disable=unused-argument - sys.stderr.write('Warning: option %s is deprecated and ignored.\n' % (optname,)) - return {'short': shortname, 'help': help_msg, 'hide': True, - 'type': opt_type, 'action': 'callback', 'callback': _warn_deprecated} - - if multiprocessing is not None: - class ChildLinter(multiprocessing.Process): # pylint: disable=no-member + class ChildLinter(multiprocessing.Process): def run(self): - tasks_queue, results_queue, self._config = self._args # pylint: disable=no-member + # pylint: disable=no-member, unbalanced-tuple-unpacking + tasks_queue, results_queue, self._config = self._args self._config["jobs"] = 1 # Child does not parallelize any further. self._python3_porting_mode = self._config.pop( 'python3_porting_mode', None) + self._plugins = self._config.pop('plugins', None) # Run linter for received files/modules. for file_or_module in iter(tasks_queue.get, 'STOP'): @@ -232,9 +223,10 @@ def _run_linter(self, file_or_module): # Register standard checkers. linter.load_default_plugins() # Load command line plugins. - # TODO linter.load_plugin_modules(self._plugins) + if self._plugins: + linter.load_plugin_modules(self._plugins) - linter.load_configuration(**self._config) + linter.load_configuration_from_config(self._config) linter.set_reporter(reporters.CollectingReporter()) # Enable the Python 3 checker mode. This option is @@ -252,7 +244,7 @@ def _run_linter(self, file_or_module): msgs, linter.stats, linter.msg_status) -class PyLinter(configuration.OptionsManagerMixIn, +class PyLinter(config.OptionsManagerMixIn, utils.MessagesHandlerMixIn, utils.ReportsHandlerMixIn, checkers.BaseTokenChecker): @@ -332,11 +324,7 @@ def make_options(): 'statements analyzed. This is used by the global ' 'evaluation report (RP0004).'}), - ('comment', - {'default': 0, 'type' : 'yn', 'metavar' : '', - 'group': 'Reports', 'level': 1, - 'help' : 'Add a comment according to your evaluation note. ' - 'This is used by the global evaluation report (RP0004).'}), + ('comment', utils.deprecated_option(opt_type='yn')), ('confidence', {'type' : 'multiple_choice', 'metavar': '', @@ -353,7 +341,9 @@ def make_options(): 'group': 'Messages control', 'help' : 'Enable the message, report, category or checker with the ' 'given id(s). You can either give multiple identifier ' - 'separated by comma (,) or put this option multiple time. ' + 'separated by comma (,) or put this option multiple time ' + '(only on the command line, not in the configuration file ' + 'where it should appear only once). ' 'See also the "--disable" option for examples. '}), ('disable', @@ -382,9 +372,8 @@ def make_options(): 'See doc for all details') }), - ('include-ids', _deprecated_option('i', 'yn', - INCLUDE_IDS_HELP)), - ('symbols', _deprecated_option('s', 'yn', SYMBOLS_HELP)), + ('include-ids', utils.deprecated_option('i', 'yn', INCLUDE_IDS_HELP)), + ('symbols', utils.deprecated_option('s', 'yn', SYMBOLS_HELP)), ('jobs', {'type' : 'int', 'metavar': '', @@ -400,24 +389,24 @@ def make_options(): ' may run arbitrary code.')}), ('extension-pkg-whitelist', - {'type': 'csv', 'metavar': '', 'default': [], - 'help': ('A comma-separated list of package or module names' - ' from where C extensions may be loaded. Extensions are' - ' loading into the active Python interpreter and may run' - ' arbitrary code')} - ), + {'type': 'csv', 'metavar': '', 'default': [], + 'help': ('A comma-separated list of package or module names' + ' from where C extensions may be loaded. Extensions are' + ' loading into the active Python interpreter and may run' + ' arbitrary code')} + ), ('optimize-ast', - {'type': 'yn', 'metavar': '', 'default': False, - 'help': ('Allow optimization of some AST trees. This will ' - 'activate a peephole AST optimizer, which will ' - 'apply various small optimizations. For instance, ' - 'it can be used to obtain the result of joining ' - 'multiple strings with the addition operator. ' - 'Joining a lot of strings can lead to a maximum ' - 'recursion error in Pylint and this flag can prevent ' - 'that. It has one side effect, the resulting AST ' - 'will be different than the one from reality.')} + {'type': 'yn', 'metavar': '', 'default': False, + 'help': ('Allow optimization of some AST trees. This will ' + 'activate a peephole AST optimizer, which will ' + 'apply various small optimizations. For instance, ' + 'it can be used to obtain the result of joining ' + 'multiple strings with the addition operator. ' + 'Joining a lot of strings can lead to a maximum ' + 'recursion error in Pylint and this flag can prevent ' + 'that. It has one side effect, the resulting AST ' + 'will be different than the one from reality.')} ), ) @@ -452,14 +441,14 @@ def __init__(self, options=(), reporter=None, option_groups=(), 'disable': self.disable} self._bw_options_methods = {'disable-msg': self.disable, 'enable-msg': self.enable} - full_version = '%%prog %s, \nastroid %s, common %s\nPython %s' % ( - version, astroid_version, common_version, sys.version) - configuration.OptionsManagerMixIn.__init__( - self, usage=__doc__, - version=full_version, - config_file=pylintrc or config.PYLINTRC) + full_version = '%%prog %s, \nastroid %s\nPython %s' % ( + version, astroid_version, sys.version) utils.MessagesHandlerMixIn.__init__(self) utils.ReportsHandlerMixIn.__init__(self) + super(PyLinter, self).__init__( + usage=__doc__, + version=full_version, + config_file=pylintrc or config.PYLINTRC) checkers.BaseTokenChecker.__init__(self) # provided reports self.reports = (('RP0001', 'Messages by category', @@ -516,7 +505,7 @@ def set_reporter(self, reporter): reporter.linter = self def set_option(self, optname, value, action=None, optdict=None): - """overridden from configuration.OptionsProviderMixin to handle some + """overridden from config.OptionsProviderMixin to handle some special options """ if optname in self._options_methods or \ @@ -526,10 +515,10 @@ def set_option(self, optname, value, action=None, optdict=None): meth = self._options_methods[optname] except KeyError: meth = self._bw_options_methods[optname] - warnings.warn('%s is deprecated, replace it by %s' % ( - optname, optname.split('-')[0]), + warnings.warn('%s is deprecated, replace it by %s' % (optname, + optname.split('-')[0]), DeprecationWarning) - value = optik_ext.check_csv(None, optname, value) + value = utils._check_csv(value) if isinstance(value, (list, tuple)): for _id in value: meth(_id, ignore_unknown=True) @@ -546,7 +535,7 @@ def set_option(self, optname, value, action=None, optdict=None): try: checkers.BaseTokenChecker.set_option(self, optname, value, action, optdict) - except configuration.UnsupportedAction: + except config.UnsupportedAction: print('option %s can\'t be read from config file' % \ optname, file=sys.stderr) @@ -597,8 +586,8 @@ def disable_noerror_messages(self): def disable_reporters(self): """disable all reporters""" - for reporters in six.itervalues(self._reports): - for report_id, _, _ in reporters: + for _reporters in six.itervalues(self._reports): + for report_id, _, _ in _reporters: self.disable_report(report_id) def error_mode(self): @@ -669,7 +658,7 @@ def process_tokens(self, tokens): # found a "(dis|en)able-msg" pragma deprecated suppresssion self.add_message('deprecated-pragma', line=start[0], args=(opt, opt.replace('-msg', ''))) - for msgid in textutils.splitstrip(value): + for msgid in utils._splitstrip(value): # Add the line where a control pragma was encountered. if opt in control_pragmas: self._pragma_lineno[msgid] = start[0] @@ -692,8 +681,8 @@ def process_tokens(self, tokens): def get_checkers(self): """return all available checkers as a list""" - return [self] + [c for checkers in six.itervalues(self._checkers) - for c in checkers if c is not self] + return [self] + [c for _checkers in six.itervalues(self._checkers) + for c in _checkers if c is not self] def prepare_checkers(self): """return checkers needed for activated messages and reports""" @@ -749,35 +738,46 @@ def check(self, files_or_modules): with _patch_sysmodules(): self._parallel_check(files_or_modules) - - def _parallel_task(self, files_or_modules): - # Prepare configuration for child linters. + def _get_jobs_config(self): + child_config = collections.OrderedDict() filter_options = {'symbols', 'include-ids', 'long-help'} - filter_options.update([opt_name for opt_name, _ in self._external_opts]) - config = {} + filter_options.update((opt_name for opt_name, _ in self._external_opts)) for opt_providers in six.itervalues(self._all_options): for optname, optdict, val in opt_providers.options_and_values(): + if optdict.get('deprecated'): + continue + if optname not in filter_options: - config[optname] = configuration.format_option_value(optdict, val) - config['python3_porting_mode'] = self._python3_porting_mode + child_config[optname] = utils._format_option_value( + optdict, val) + child_config['python3_porting_mode'] = self._python3_porting_mode + child_config['plugins'] = self._dynamic_plugins + return child_config + + def _parallel_task(self, files_or_modules): + # Prepare configuration for child linters. + child_config = self._get_jobs_config() - childs = [] - manager = multiprocessing.Manager() # pylint: disable=no-member - tasks_queue = manager.Queue() # pylint: disable=no-member - results_queue = manager.Queue() # pylint: disable=no-member + children = [] + manager = multiprocessing.Manager() + tasks_queue = manager.Queue() + results_queue = manager.Queue() for _ in range(self.config.jobs): - cl = ChildLinter(args=(tasks_queue, results_queue, config)) - cl.start() # pylint: disable=no-member - childs.append(cl) + child_linter = ChildLinter(args=(tasks_queue, results_queue, + child_config)) + child_linter.start() + children.append(child_linter) - # send files to child linters - for files_or_module in files_or_modules: - tasks_queue.put([files_or_module]) + # Send files to child linters. + expanded_files = self.expand_files(files_or_modules) + for files_or_module in expanded_files: + path = files_or_module['path'] + tasks_queue.put([path]) # collect results from child linters failed = False - for _ in files_or_modules: + for _ in expanded_files: try: result = results_queue.get() except Exception as ex: @@ -791,8 +791,8 @@ def _parallel_task(self, files_or_modules): # Stop child linters and wait for their completion. for _ in range(self.config.jobs): tasks_queue.put('STOP') - for cl in childs: - cl.join() + for child in children: + child.join() if failed: print("Error occured, stopping the linter.", file=sys.stderr) @@ -803,9 +803,10 @@ def _parallel_check(self, files_or_modules): self.open() all_stats = [] + module = None for result in self._parallel_task(files_or_modules): ( - file_or_module, + _, self.file_state.base_name, module, messages, @@ -813,9 +814,6 @@ def _parallel_check(self, files_or_modules): msg_status ) = result - if file_or_module == files_or_modules[-1]: - last_module = module - for msg in messages: msg = utils.Message(*msg) self.set_current_module(module) @@ -824,8 +822,8 @@ def _parallel_check(self, files_or_modules): all_stats.append(stats) self.msg_status |= msg_status - self.stats = _merge_stats(itertools.chain(all_stats, [self.stats])) - self.current_name = last_module + self.stats = _merge_stats(all_stats) + self.current_name = module # Insert stats data to local checkers. for checker in self.get_checkers(): @@ -834,16 +832,16 @@ def _parallel_check(self, files_or_modules): def _do_check(self, files_or_modules): walker = utils.PyLintASTWalker(self) - checkers = self.prepare_checkers() - tokencheckers = [c for c in checkers - if interface.implements(c, interfaces.ITokenChecker) + _checkers = self.prepare_checkers() + tokencheckers = [c for c in _checkers + if interfaces.implements(c, interfaces.ITokenChecker) and c is not self] - rawcheckers = [c for c in checkers - if interface.implements(c, interfaces.IRawChecker)] + rawcheckers = [c for c in _checkers + if interfaces.implements(c, interfaces.IRawChecker)] # notify global begin - for checker in checkers: + for checker in _checkers: checker.open() - if interface.implements(checker, interfaces.IAstroidChecker): + if interfaces.implements(checker, interfaces.IAstroidChecker): walker.add_checker(checker) # build ast and check modules or packages for descr in self.expand_files(files_or_modules): @@ -873,8 +871,7 @@ def _do_check(self, files_or_modules): self.add_message(msgid, line, None, args) # notify global end self.stats['statement'] = walker.nbstatements - checkers.reverse() - for checker in checkers: + for checker in reversed(_checkers): checker.close() def expand_files(self, modules): @@ -908,10 +905,14 @@ def get_ast(self, filepath, modname): """return a ast(roid) representation for a module""" try: return MANAGER.ast_from_file(filepath, modname, source=True) - except SyntaxError as ex: - self.add_message('syntax-error', line=ex.lineno, args=ex.msg) except astroid.AstroidBuildingException as ex: - self.add_message('parse-error', args=ex) + if isinstance(ex.args[0], SyntaxError): + ex = ex.args[0] + self.add_message('syntax-error', + line=ex.lineno or 0, + args=ex.msg) + else: + self.add_message('parse-error', args=ex) except Exception as ex: # pylint: disable=broad-except import traceback traceback.print_exc() @@ -965,6 +966,9 @@ def generate_reports(self): if persistent run, pickle results for later comparison """ + # Display whatever messages are left on the reporter. + self.reporter.display_messages(report_nodes.Section()) + if self.file_state.base_name is not None: # load previous results if any previous_stats = config.load_results(self.file_state.base_name) @@ -976,18 +980,13 @@ def generate_reports(self): filename = 'pylint_global.' + self.reporter.extension self.reporter.set_output(open(filename, 'w')) else: - sect = ureports.Section() - if self.config.reports or self.config.output_format == 'html': - self.reporter.display_results(sect) + sect = report_nodes.Section() + if self.config.reports: + self.reporter.display_reports(sect) # save results if persistent run if self.config.persistent: config.save_results(self.stats, self.file_state.base_name) else: - if self.config.output_format == 'html': - # No output will be emitted for the html - # reporter if the file doesn't exist, so emit - # the results here. - self.reporter.display_results(ureports.Section()) self.reporter.on_close(self.stats, {}) # specific reports ######################################################## @@ -1010,9 +1009,7 @@ def report_evaluation(self, sect, stats, previous_stats): pnote = previous_stats.get('global_note') if pnote is not None: msg += ' (previous run: %.2f/10, %+.2f)' % (pnote, note - pnote) - if self.config.comment: - msg = '%s\n%s' % (msg, config.get_note_message(note)) - sect.append(ureports.Text(msg)) + sect.append(report_nodes.Text(msg)) # some reporting functions #################################################### @@ -1022,7 +1019,7 @@ def report_total_messages_stats(sect, stats, previous_stats): lines += checkers.table_lines_from_stats(stats, previous_stats, ('convention', 'refactor', 'warning', 'error')) - sect.append(ureports.Table(children=lines, cols=4, rheaders=1)) + sect.append(report_nodes.Table(children=lines, cols=4, rheaders=1)) def report_messages_stats(sect, stats, _): """make messages type report""" @@ -1036,7 +1033,7 @@ def report_messages_stats(sect, stats, _): lines = ('message id', 'occurrences') for value, msg_id in in_order: lines += (msg_id, str(value)) - sect.append(ureports.Table(children=lines, cols=2, rheaders=1)) + sect.append(report_nodes.Table(children=lines, cols=2, rheaders=1)) def report_messages_by_module_stats(sect, stats, _): """make errors / warnings by modules report""" @@ -1072,7 +1069,7 @@ def report_messages_by_module_stats(sect, stats, _): lines.append('%.2f' % val) if len(lines) == 5: raise utils.EmptyReport() - sect.append(ureports.Table(children=lines, cols=5, rheaders=1)) + sect.append(report_nodes.Table(children=lines, cols=5, rheaders=1)) # utilities ################################################################### @@ -1230,10 +1227,7 @@ def __init__(self, args, reporter=None, exit=True): 'disabled and only messages emitted by the porting ' 'checker will be displayed'}), - ('profile', - {'type' : 'yn', 'metavar' : '', - 'default': False, 'hide': True, - 'help' : 'Profiled execution.'}), + ('profile', utils.deprecated_option(opt_type='yn')), ), option_groups=self.option_groups, pylintrc=self._rcfile) # register standard checkers @@ -1270,7 +1264,6 @@ def __init__(self, args, reporter=None, exit=True): 'been issued by analysing pylint output status code\n', level=1) # read configuration - linter.disable('pointless-except') linter.disable('suppressed-message') linter.disable('useless-suppression') linter.read_config_file() @@ -1278,11 +1271,11 @@ def __init__(self, args, reporter=None, exit=True): # run init hook, if present, before loading plugins if config_parser.has_option('MASTER', 'init-hook'): cb_init_hook('init-hook', - textutils.unquote(config_parser.get('MASTER', - 'init-hook'))) + utils._unquote(config_parser.get('MASTER', + 'init-hook'))) # is there some additional plugins in the file configuration, in if config_parser.has_option('MASTER', 'load-plugins'): - plugins = textutils.splitstrip( + plugins = utils._splitstrip( config_parser.get('MASTER', 'load-plugins')) linter.load_plugin_modules(plugins) # now we can load file config and command line, plugins (which can @@ -1319,17 +1312,7 @@ def __init__(self, args, reporter=None, exit=True): # insert current working directory to the python path to have a correct # behaviour with fix_import_path(args): - if self.linter.config.profile: - print('** profiled run', file=sys.stderr) - import cProfile, pstats - cProfile.runctx('linter.check(%r)' % args, globals(), locals(), - 'stones.prof') - data = pstats.Stats('stones.prof') - data.strip_dirs() - data.sort_stats('time', 'calls') - data.print_stats(30) - else: - linter.check(args) + linter.check(args) linter.generate_reports() if exit: sys.exit(self.linter.msg_status) @@ -1340,7 +1323,7 @@ def cb_set_rcfile(self, name, value): def cb_add_plugins(self, name, value): """callback for option preprocessing (i.e. before option parsing)""" - self._plugins.extend(textutils.splitstrip(value)) + self._plugins.extend(utils._splitstrip(value)) def cb_error_mode(self, *args, **kwargs): """error mode: @@ -1365,7 +1348,7 @@ def cb_generate_manpage(self, *args, **kwargs): def cb_help_message(self, option, optname, value, parser): """optik callback for printing some help about a particular message""" - self.linter.msgs_store.help_message(textutils.splitstrip(value)) + self.linter.msgs_store.help_message(utils._splitstrip(value)) sys.exit(0) def cb_full_documentation(self, option, optname, value, parser): diff --git a/pymode/libs/pylint/pyreverse/diadefslib.py b/pymode/libs/pylint/pyreverse/diadefslib.py index e0dc8cfc..0d3a963b 100644 --- a/pymode/libs/pylint/pyreverse/diadefslib.py +++ b/pymode/libs/pylint/pyreverse/diadefslib.py @@ -16,12 +16,12 @@ """handle diagram generation options for class diagram or default diagrams """ -from logilab.common.compat import builtins +from six.moves import builtins import astroid -from astroid.utils import LocalsVisitor from pylint.pyreverse.diagrams import PackageDiagram, ClassDiagram +from pylint.pyreverse.utils import LocalsVisitor BUILTINS_NAME = builtins.__name__ @@ -49,10 +49,7 @@ def _set_option(self, option): # if we have a class diagram, we want more information by default; # so if the option is None, we return True if option is None: - if self.config.classes: - return True - else: - return False + return bool(self.config.classes) return option def _set_default_options(self): @@ -104,7 +101,7 @@ def get_associated(self, klass_node, level): for ass_node in ass_nodes: if isinstance(ass_node, astroid.Instance): ass_node = ass_node._proxied - if not (isinstance(ass_node, astroid.Class) + if not (isinstance(ass_node, astroid.ClassDef) and self.show_node(ass_node)): continue yield ass_node @@ -134,7 +131,7 @@ def __init__(self, linker, handler): LocalsVisitor.__init__(self) def visit_project(self, node): - """visit an astroid.Project node + """visit an pyreverse.utils.Project node create a diagram definition for packages """ @@ -146,7 +143,7 @@ def visit_project(self, node): self.classdiagram = ClassDiagram('classes %s' % node.name, mode) def leave_project(self, node): # pylint: disable=unused-argument - """leave the astroid.Project node + """leave the pyreverse.utils.Project node return the generated diagram definition """ @@ -163,7 +160,7 @@ def visit_module(self, node): self.linker.visit(node) self.pkgdiagram.add_object(node.name, node) - def visit_class(self, node): + def visit_classdef(self, node): """visit an astroid.Class node add this class to the class diagram definition @@ -171,7 +168,7 @@ def visit_class(self, node): anc_level, ass_level = self._get_levels() self.extract_classes(node, anc_level, ass_level) - def visit_from(self, node): + def visit_importfrom(self, node): """visit astroid.From and catch modules for package diagram """ if self.pkgdiagram: @@ -217,8 +214,8 @@ def __init__(self, config): def get_diadefs(self, project, linker): """get the diagrams configuration data - :param linker: astroid.inspector.Linker(IdGeneratorMixIn, LocalsVisitor) - :param project: astroid.manager.Project + :param linker: pyreverse.inspector.Linker(IdGeneratorMixIn, LocalsVisitor) + :param project: pyreverse.utils.Project """ # read and interpret diagram definitions (Diadefs) diff --git a/pymode/libs/pylint/pyreverse/diagrams.py b/pymode/libs/pylint/pyreverse/diagrams.py index f0d7a92c..ef7fe902 100644 --- a/pymode/libs/pylint/pyreverse/diagrams.py +++ b/pymode/libs/pylint/pyreverse/diagrams.py @@ -18,10 +18,13 @@ import astroid from pylint.pyreverse.utils import is_interface, FilterMixIn +from pylint.checkers.utils import decorated_with_property + class Figure(object): """base class for counter handling""" + class Relationship(Figure): """a relation ship from an object in the diagram to another """ @@ -41,6 +44,7 @@ def __init__(self, title='No name', node=None): self.title = title self.node = node + class ClassDiagram(Figure, FilterMixIn): """main class diagram handling """ @@ -77,8 +81,13 @@ def get_relationship(self, from_object, relation_type): def get_attrs(self, node): """return visible attributes, possibly with class name""" attrs = [] + properties = [ + (n, m) for n, m in node.items() + if isinstance(m, astroid.FunctionDef) + and decorated_with_property(m) + ] for node_name, ass_nodes in list(node.instance_attrs_type.items()) + \ - list(node.locals_type.items()): + list(node.locals_type.items()) + properties: if not self.show_attr(node_name): continue names = self.class_names(ass_nodes) @@ -91,7 +100,9 @@ def get_methods(self, node): """return visible methods""" methods = [ m for m in node.values() - if isinstance(m, astroid.Function) and self.show_attr(m.name) + if isinstance(m, astroid.FunctionDef) + and not decorated_with_property(m) + and self.show_attr(m.name) ] return sorted(methods, key=lambda n: n.name) @@ -109,7 +120,7 @@ def class_names(self, nodes): for ass_node in nodes: if isinstance(ass_node, astroid.Instance): ass_node = ass_node._proxied - if isinstance(ass_node, astroid.Class) \ + if isinstance(ass_node, astroid.ClassDef) \ and hasattr(ass_node, "name") and not self.has_node(ass_node): if ass_node.name not in names: ass_name = ass_node.name @@ -133,7 +144,7 @@ def object_from_node(self, node): def classes(self): """return all class nodes in the diagram""" - return [o for o in self.objects if isinstance(o.node, astroid.Class)] + return [o for o in self.objects if isinstance(o.node, astroid.ClassDef)] def classe(self, name): """return a class by its name, raise KeyError if not found diff --git a/pymode/libs/pylint/pyreverse/inspector.py b/pymode/libs/pylint/pyreverse/inspector.py new file mode 100644 index 00000000..4fd86b8a --- /dev/null +++ b/pymode/libs/pylint/pyreverse/inspector.py @@ -0,0 +1,372 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# Copyright (c) 2009-2010 Arista Networks, Inc. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" +Visitor doing some postprocessing on the astroid tree. +Try to resolve definitions (namespace) dictionary, relationship... +""" +from __future__ import print_function + +import collections +import os +import traceback + +import astroid +from astroid import bases +from astroid import exceptions +from astroid import manager +from astroid import modutils +from astroid import node_classes + + +from pylint.pyreverse import utils + + +def _iface_hdlr(_): + """Handler used by interfaces to handle suspicious interface nodes.""" + return True + + +def _astroid_wrapper(func, modname): + print('parsing %s...' % modname) + try: + return func(modname) + except exceptions.AstroidBuildingException as exc: + print(exc) + except Exception as exc: # pylint: disable=broad-except + traceback.print_exc() + + +def interfaces(node, herited=True, handler_func=_iface_hdlr): + """Return an iterator on interfaces implemented by the given class node.""" + # FIXME: what if __implements__ = (MyIFace, MyParent.__implements__)... + try: + implements = bases.Instance(node).getattr('__implements__')[0] + except exceptions.NotFoundError: + return + if not herited and implements.frame() is not node: + return + found = set() + missing = False + for iface in node_classes.unpack_infer(implements): + if iface is astroid.YES: + missing = True + continue + if iface not in found and handler_func(iface): + found.add(iface) + yield iface + if missing: + raise exceptions.InferenceError() + + +class IdGeneratorMixIn(object): + """Mixin adding the ability to generate integer uid.""" + + def __init__(self, start_value=0): + self.id_count = start_value + + def init_counter(self, start_value=0): + """init the id counter + """ + self.id_count = start_value + + def generate_id(self): + """generate a new identifier + """ + self.id_count += 1 + return self.id_count + + +class Linker(IdGeneratorMixIn, utils.LocalsVisitor): + """Walk on the project tree and resolve relationships. + + According to options the following attributes may be + added to visited nodes: + + * uid, + a unique identifier for the node (on astroid.Project, astroid.Module, + astroid.Class and astroid.locals_type). Only if the linker + has been instantiated with tag=True parameter (False by default). + + * Function + a mapping from locals names to their bounded value, which may be a + constant like a string or an integer, or an astroid node + (on astroid.Module, astroid.Class and astroid.Function). + + * instance_attrs_type + as locals_type but for klass member attributes (only on astroid.Class) + + * implements, + list of implemented interface _objects_ (only on astroid.Class nodes) + """ + + def __init__(self, project, inherited_interfaces=0, tag=False): + IdGeneratorMixIn.__init__(self) + utils.LocalsVisitor.__init__(self) + # take inherited interface in consideration or not + self.inherited_interfaces = inherited_interfaces + # tag nodes or not + self.tag = tag + # visited project + self.project = project + + def visit_project(self, node): + """visit an pyreverse.utils.Project node + + * optionally tag the node with a unique id + """ + if self.tag: + node.uid = self.generate_id() + for module in node.modules: + self.visit(module) + + def visit_package(self, node): + """visit an astroid.Package node + + * optionally tag the node with a unique id + """ + if self.tag: + node.uid = self.generate_id() + for subelmt in node.values(): + self.visit(subelmt) + + def visit_module(self, node): + """visit an astroid.Module node + + * set the locals_type mapping + * set the depends mapping + * optionally tag the node with a unique id + """ + if hasattr(node, 'locals_type'): + return + node.locals_type = collections.defaultdict(list) + node.depends = [] + if self.tag: + node.uid = self.generate_id() + + def visit_classdef(self, node): + """visit an astroid.Class node + + * set the locals_type and instance_attrs_type mappings + * set the implements list and build it + * optionally tag the node with a unique id + """ + if hasattr(node, 'locals_type'): + return + node.locals_type = collections.defaultdict(list) + if self.tag: + node.uid = self.generate_id() + # resolve ancestors + for baseobj in node.ancestors(recurs=False): + specializations = getattr(baseobj, 'specializations', []) + specializations.append(node) + baseobj.specializations = specializations + # resolve instance attributes + node.instance_attrs_type = collections.defaultdict(list) + for assattrs in node.instance_attrs.values(): + for assattr in assattrs: + self.handle_assattr_type(assattr, node) + # resolve implemented interface + try: + node.implements = list(interfaces(node, self.inherited_interfaces)) + except astroid.InferenceError: + node.implements = () + + def visit_functiondef(self, node): + """visit an astroid.Function node + + * set the locals_type mapping + * optionally tag the node with a unique id + """ + if hasattr(node, 'locals_type'): + return + node.locals_type = collections.defaultdict(list) + if self.tag: + node.uid = self.generate_id() + + link_project = visit_project + link_module = visit_module + link_class = visit_classdef + link_function = visit_functiondef + + def visit_assignname(self, node): + """visit an astroid.AssName node + + handle locals_type + """ + # avoid double parsing done by different Linkers.visit + # running over the same project: + if hasattr(node, '_handled'): + return + node._handled = True + if node.name in node.frame(): + frame = node.frame() + else: + # the name has been defined as 'global' in the frame and belongs + # there. + frame = node.root() + try: + if not hasattr(frame, 'locals_type'): + # If the frame doesn't have a locals_type yet, + # it means it wasn't yet visited. Visit it now + # to add what's missing from it. + if isinstance(frame, astroid.ClassDef): + self.visit_classdef(frame) + elif isinstance(frame, astroid.FunctionDef): + self.visit_functiondef(frame) + else: + self.visit_module(frame) + + current = frame.locals_type[node.name] + values = set(node.infer()) + frame.locals_type[node.name] = list(set(current) | values) + except astroid.InferenceError: + pass + + @staticmethod + def handle_assattr_type(node, parent): + """handle an astroid.AssAttr node + + handle instance_attrs_type + """ + try: + values = set(node.infer()) + current = set(parent.instance_attrs_type[node.attrname]) + parent.instance_attrs_type[node.attrname] = list(current | values) + except astroid.InferenceError: + pass + + def visit_import(self, node): + """visit an astroid.Import node + + resolve module dependencies + """ + context_file = node.root().file + for name in node.names: + relative = modutils.is_relative(name[0], context_file) + self._imported_module(node, name[0], relative) + + def visit_importfrom(self, node): + """visit an astroid.From node + + resolve module dependencies + """ + basename = node.modname + context_file = node.root().file + if context_file is not None: + relative = modutils.is_relative(basename, context_file) + else: + relative = False + for name in node.names: + if name[0] == '*': + continue + # analyze dependencies + fullname = '%s.%s' % (basename, name[0]) + if fullname.find('.') > -1: + try: + # TODO: don't use get_module_part, + # missing package precedence + fullname = modutils.get_module_part(fullname, + context_file) + except ImportError: + continue + if fullname != basename: + self._imported_module(node, fullname, relative) + + def compute_module(self, context_name, mod_path): + """return true if the module should be added to dependencies""" + package_dir = os.path.dirname(self.project.path) + if context_name == mod_path: + return 0 + elif modutils.is_standard_module(mod_path, (package_dir,)): + return 1 + return 0 + + def _imported_module(self, node, mod_path, relative): + """Notify an imported module, used to analyze dependencies""" + module = node.root() + context_name = module.name + if relative: + mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]), + mod_path) + if self.compute_module(context_name, mod_path): + # handle dependencies + if not hasattr(module, 'depends'): + module.depends = [] + mod_paths = module.depends + if mod_path not in mod_paths: + mod_paths.append(mod_path) + + +class Project(object): + """a project handle a set of modules / packages""" + def __init__(self, name=''): + self.name = name + self.path = None + self.modules = [] + self.locals = {} + self.__getitem__ = self.locals.__getitem__ + self.__iter__ = self.locals.__iter__ + self.values = self.locals.values + self.keys = self.locals.keys + self.items = self.locals.items + + def add_module(self, node): + self.locals[node.name] = node + self.modules.append(node) + + def get_module(self, name): + return self.locals[name] + + def get_children(self): + return self.modules + + def __repr__(self): + return '' % (self.name, id(self), + len(self.modules)) + + +def project_from_files(files, func_wrapper=_astroid_wrapper, + project_name="no name", + black_list=('CVS',)): + """return a Project from a list of files or modules""" + # build the project representation + astroid_manager = manager.AstroidManager() + project = Project(project_name) + for something in files: + if not os.path.exists(something): + fpath = modutils.file_from_modpath(something.split('.')) + elif os.path.isdir(something): + fpath = os.path.join(something, '__init__.py') + else: + fpath = something + ast = func_wrapper(astroid_manager.ast_from_file, fpath) + if ast is None: + continue + # XXX why is first file defining the project.path ? + project.path = project.path or ast.file + project.add_module(ast) + base_name = ast.name + # recurse in package except if __init__ was explicitly given + if ast.package and something.find('__init__') == -1: + # recurse on others packages / modules if this is a package + for fpath in modutils.get_module_files(os.path.dirname(ast.file), + black_list): + ast = func_wrapper(astroid_manager.ast_from_file, fpath) + if ast is None or ast.name == base_name: + continue + project.add_module(ast) + return project diff --git a/pymode/libs/pylint/pyreverse/main.py b/pymode/libs/pylint/pyreverse/main.py index 408c1722..e56c9dd9 100644 --- a/pymode/libs/pylint/pyreverse/main.py +++ b/pymode/libs/pylint/pyreverse/main.py @@ -20,11 +20,12 @@ """ from __future__ import print_function -import sys, os -from logilab.common.configuration import ConfigurationMixIn -from astroid.manager import AstroidManager -from astroid.inspector import Linker +import os +import subprocess +import sys +from pylint.config import ConfigurationMixIn +from pylint.pyreverse.inspector import Linker, project_from_files from pylint.pyreverse.diadefslib import DiadefsHandler from pylint.pyreverse import writer from pylint.pyreverse.utils import insert_default_options @@ -79,11 +80,31 @@ ("output", dict(short="o", dest="output_format", action="store", default="dot", metavar="", help="create a *. output file if format available.")), + ("ignore", {'type' : "csv", 'metavar' : "", + 'dest' : "black_list", "default" : ('CVS',), + 'help' : "add (may be a directory) to the black list. " + "It should be a base name, not a path. You may set " + "this option multiple times."}), + ("project", {'default': "No Name", 'type' : 'string', 'short': 'p', + 'metavar': '', 'help': 'set the project name.'}), ) # FIXME : quiet mode #( ('quiet', #dict(help='run quietly', action='store_true', short='q')), ) +def _check_graphviz_available(output_format): + """check if we need graphviz for different output format""" + try: + subprocess.call(['dot', '-V'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except OSError: + print("The output format '%s' is currently not available.\n" + "Please install 'Graphviz' to have other output formats " + "than 'dot' or 'vcg'." % output_format) + sys.exit(32) + + + class Run(ConfigurationMixIn): """base class providing common behaviour for pyreverse commands""" @@ -92,9 +113,10 @@ class Run(ConfigurationMixIn): def __init__(self, args): ConfigurationMixIn.__init__(self, usage=__doc__) insert_default_options() - self.manager = AstroidManager() - self.register_options_provider(self.manager) args = self.load_command_line_configuration() + if self.config.output_format not in ('dot', 'vcg'): + _check_graphviz_available(self.config.output_format) + sys.exit(self.run(args)) def run(self, args): @@ -106,7 +128,8 @@ def run(self, args): # dependencies to local modules even if cwd is not in the PYTHONPATH sys.path.insert(0, os.getcwd()) try: - project = self.manager.project_from_files(args) + project = project_from_files(args, project_name=self.config.project, + black_list=self.config.black_list) linker = Linker(project, tag=True) handler = DiadefsHandler(self.config) diadefs = handler.get_diadefs(project, linker) diff --git a/pymode/libs/pylint/pyreverse/utils.py b/pymode/libs/pylint/pyreverse/utils.py index 5d6d1335..90e973d9 100644 --- a/pymode/libs/pylint/pyreverse/utils.py +++ b/pymode/libs/pylint/pyreverse/utils.py @@ -18,9 +18,9 @@ """ from __future__ import print_function -import sys -import re import os +import re +import sys ########### pyreverse option utils ############################## @@ -110,6 +110,7 @@ def is_exception(node): VIS_MOD = {'special': _SPECIAL, 'protected': _PROTECTED, 'private': _PRIVATE, 'public': 0} + class FilterMixIn(object): """filter nodes according to a mode and nodes' visibility """ @@ -130,3 +131,80 @@ def show_attr(self, node): visibility = get_visibility(getattr(node, 'name', node)) return not self.__mode & VIS_MOD[visibility] + +class ASTWalker(object): + """a walker visiting a tree in preorder, calling on the handler: + + * visit_ on entering a node, where class name is the class of + the node in lower case + + * leave_ on leaving a node, where class name is the class of + the node in lower case + """ + + def __init__(self, handler): + self.handler = handler + self._cache = {} + + def walk(self, node, _done=None): + """walk on the tree from , getting callbacks from handler""" + if _done is None: + _done = set() + if node in _done: + raise AssertionError((id(node), node, node.parent)) + _done.add(node) + self.visit(node) + for child_node in node.get_children(): + assert child_node is not node + self.walk(child_node, _done) + self.leave(node) + assert node.parent is not node + + def get_callbacks(self, node): + """get callbacks from handler for the visited node""" + klass = node.__class__ + methods = self._cache.get(klass) + if methods is None: + handler = self.handler + kid = klass.__name__.lower() + e_method = getattr(handler, 'visit_%s' % kid, + getattr(handler, 'visit_default', None)) + l_method = getattr(handler, 'leave_%s' % kid, + getattr(handler, 'leave_default', None)) + self._cache[klass] = (e_method, l_method) + else: + e_method, l_method = methods + return e_method, l_method + + def visit(self, node): + """walk on the tree from , getting callbacks from handler""" + method = self.get_callbacks(node)[0] + if method is not None: + method(node) + + def leave(self, node): + """walk on the tree from , getting callbacks from handler""" + method = self.get_callbacks(node)[1] + if method is not None: + method(node) + + +class LocalsVisitor(ASTWalker): + """visit a project by traversing the locals dictionary""" + def __init__(self): + ASTWalker.__init__(self, self) + self._visited = {} + + def visit(self, node): + """launch the visit starting from the given node""" + if node in self._visited: + return + self._visited[node] = 1 # FIXME: use set ? + methods = self.get_callbacks(node) + if methods[0] is not None: + methods[0](node) + if hasattr(node, 'locals'): # skip Instance and other proxy + for local_node in node.values(): + self.visit(local_node) + if methods[1] is not None: + return methods[1](node) diff --git a/pymode/libs/pylint/pyreverse/vcgutils.py b/pymode/libs/pylint/pyreverse/vcgutils.py new file mode 100644 index 00000000..d135c128 --- /dev/null +++ b/pymode/libs/pylint/pyreverse/vcgutils.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2008-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# This file was copied from logilab-common with the same license: +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. + +"""Functions to generate files readable with Georg Sander's vcg +(Visualization of Compiler Graphs). + +You can download vcg at http://rw4.cs.uni-sb.de/~sander/html/gshome.html +Note that vcg exists as a debian package. + +See vcg's documentation for explanation about the different values that +maybe used for the functions parameters. +""" + +ATTRS_VAL = { + 'algos': ('dfs', 'tree', 'minbackward', + 'left_to_right', 'right_to_left', + 'top_to_bottom', 'bottom_to_top', + 'maxdepth', 'maxdepthslow', 'mindepth', 'mindepthslow', + 'mindegree', 'minindegree', 'minoutdegree', + 'maxdegree', 'maxindegree', 'maxoutdegree'), + 'booleans': ('yes', 'no'), + 'colors': ('black', 'white', 'blue', 'red', 'green', 'yellow', + 'magenta', 'lightgrey', + 'cyan', 'darkgrey', 'darkblue', 'darkred', 'darkgreen', + 'darkyellow', 'darkmagenta', 'darkcyan', 'gold', + 'lightblue', 'lightred', 'lightgreen', 'lightyellow', + 'lightmagenta', 'lightcyan', 'lilac', 'turquoise', + 'aquamarine', 'khaki', 'purple', 'yellowgreen', 'pink', + 'orange', 'orchid'), + 'shapes': ('box', 'ellipse', 'rhomb', 'triangle'), + 'textmodes': ('center', 'left_justify', 'right_justify'), + 'arrowstyles': ('solid', 'line', 'none'), + 'linestyles': ('continuous', 'dashed', 'dotted', 'invisible'), + } + +# meaning of possible values: +# O -> string +# 1 -> int +# list -> value in list +GRAPH_ATTRS = { + 'title': 0, + 'label': 0, + 'color': ATTRS_VAL['colors'], + 'textcolor': ATTRS_VAL['colors'], + 'bordercolor': ATTRS_VAL['colors'], + 'width': 1, + 'height': 1, + 'borderwidth': 1, + 'textmode': ATTRS_VAL['textmodes'], + 'shape': ATTRS_VAL['shapes'], + 'shrink': 1, + 'stretch': 1, + 'orientation': ATTRS_VAL['algos'], + 'vertical_order': 1, + 'horizontal_order': 1, + 'xspace': 1, + 'yspace': 1, + 'layoutalgorithm': ATTRS_VAL['algos'], + 'late_edge_labels': ATTRS_VAL['booleans'], + 'display_edge_labels': ATTRS_VAL['booleans'], + 'dirty_edge_labels': ATTRS_VAL['booleans'], + 'finetuning': ATTRS_VAL['booleans'], + 'manhattan_edges': ATTRS_VAL['booleans'], + 'smanhattan_edges': ATTRS_VAL['booleans'], + 'port_sharing': ATTRS_VAL['booleans'], + 'edges': ATTRS_VAL['booleans'], + 'nodes': ATTRS_VAL['booleans'], + 'splines': ATTRS_VAL['booleans'], + } +NODE_ATTRS = { + 'title': 0, + 'label': 0, + 'color': ATTRS_VAL['colors'], + 'textcolor': ATTRS_VAL['colors'], + 'bordercolor': ATTRS_VAL['colors'], + 'width': 1, + 'height': 1, + 'borderwidth': 1, + 'textmode': ATTRS_VAL['textmodes'], + 'shape': ATTRS_VAL['shapes'], + 'shrink': 1, + 'stretch': 1, + 'vertical_order': 1, + 'horizontal_order': 1, + } +EDGE_ATTRS = { + 'sourcename': 0, + 'targetname': 0, + 'label': 0, + 'linestyle': ATTRS_VAL['linestyles'], + 'class': 1, + 'thickness': 0, + 'color': ATTRS_VAL['colors'], + 'textcolor': ATTRS_VAL['colors'], + 'arrowcolor': ATTRS_VAL['colors'], + 'backarrowcolor': ATTRS_VAL['colors'], + 'arrowsize': 1, + 'backarrowsize': 1, + 'arrowstyle': ATTRS_VAL['arrowstyles'], + 'backarrowstyle': ATTRS_VAL['arrowstyles'], + 'textmode': ATTRS_VAL['textmodes'], + 'priority': 1, + 'anchor': 1, + 'horizontal_order': 1, + } + + +# Misc utilities ############################################################### + +class VCGPrinter(object): + """A vcg graph writer. + """ + + def __init__(self, output_stream): + self._stream = output_stream + self._indent = '' + + def open_graph(self, **args): + """open a vcg graph + """ + self._stream.write('%sgraph:{\n'%self._indent) + self._inc_indent() + self._write_attributes(GRAPH_ATTRS, **args) + + def close_graph(self): + """close a vcg graph + """ + self._dec_indent() + self._stream.write('%s}\n'%self._indent) + + + def node(self, title, **args): + """draw a node + """ + self._stream.write('%snode: {title:"%s"' % (self._indent, title)) + self._write_attributes(NODE_ATTRS, **args) + self._stream.write('}\n') + + + def edge(self, from_node, to_node, edge_type='', **args): + """draw an edge from a node to another. + """ + self._stream.write( + '%s%sedge: {sourcename:"%s" targetname:"%s"' % ( + self._indent, edge_type, from_node, to_node)) + self._write_attributes(EDGE_ATTRS, **args) + self._stream.write('}\n') + + + # private ################################################################## + + def _write_attributes(self, attributes_dict, **args): + """write graph, node or edge attributes + """ + for key, value in args.items(): + try: + _type = attributes_dict[key] + except KeyError: + raise Exception('''no such attribute %s +possible attributes are %s''' % (key, attributes_dict.keys())) + + if not _type: + self._stream.write('%s%s:"%s"\n' % (self._indent, key, value)) + elif _type == 1: + self._stream.write('%s%s:%s\n' % (self._indent, key, + int(value))) + elif value in _type: + self._stream.write('%s%s:%s\n' % (self._indent, key, value)) + else: + raise Exception('''value %s isn\'t correct for attribute %s +correct values are %s''' % (value, key, _type)) + + def _inc_indent(self): + """increment indentation + """ + self._indent = ' %s' % self._indent + + def _dec_indent(self): + """decrement indentation + """ + self._indent = self._indent[:-2] diff --git a/pymode/libs/pylint/pyreverse/writer.py b/pymode/libs/pylint/pyreverse/writer.py index 8628a8cc..4c7d69d6 100644 --- a/pymode/libs/pylint/pyreverse/writer.py +++ b/pymode/libs/pylint/pyreverse/writer.py @@ -16,10 +16,9 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """Utilities for creating VCG and Dot diagrams""" -from logilab.common.vcgutils import VCGPrinter -from logilab.common.graph import DotBackend - from pylint.pyreverse.utils import is_exception +from pylint.pyreverse.vcgutils import VCGPrinter +from pylint.graph import DotBackend class DiagramWriter(object): """base class for writing project diagrams @@ -106,7 +105,7 @@ def set_printer(self, file_name, basename): """initialize DotWriter and add options for layout. """ layout = dict(rankdir="BT") - self.printer = DotBackend(basename, additionnal_param=layout) + self.printer = DotBackend(basename, additional_param=layout) self.file_name = file_name def get_title(self, obj): diff --git a/pymode/libs/pylint/reporters/__init__.py b/pymode/libs/pylint/reporters/__init__.py index ea3281ff..f5f26f93 100644 --- a/pymode/libs/pylint/reporters/__init__.py +++ b/pymode/libs/pylint/reporters/__init__.py @@ -17,9 +17,9 @@ import sys import locale import os +import warnings - -from pylint import utils +import six CMPS = ['=', '-', '+'] @@ -47,12 +47,9 @@ class BaseReporter(object): def __init__(self, output=None): self.linter = None - # self.include_ids = None # Deprecated - # self.symbols = None # Deprecated self.section = 0 self.out = None self.out_encoding = None - self.encode = None self.set_output(output) # Build the path prefix to strip to get relative paths self.path_strip_prefix = os.getcwd() + os.sep @@ -67,18 +64,20 @@ def handle_message(self, msg): def add_message(self, msg_id, location, msg): """Deprecated, do not use.""" - raise NotImplementedError + # pylint: disable=no-self-use,unused-argument + msg = ("This method is deprecated, use handle_message instead. " + "It will be removed in Pylint 1.6.") + warnings.warn(msg, DeprecationWarning, stacklevel=2) def set_output(self, output=None): """set output stream""" self.out = output or sys.stdout - # py3k streams handle their encoding : - if sys.version_info >= (3, 0): - self.encode = lambda x: x - return - def encode(string): - if not isinstance(string, unicode): + if six.PY3: + encode = lambda self, string: string + else: + def encode(self, string): + if not isinstance(string, six.text_type): return string encoding = (getattr(self.out, 'encoding', None) or locale.getdefaultlocale()[1] or @@ -87,32 +86,46 @@ def encode(string): # source code line that can't be encoded with the current locale # settings return string.encode(encoding, 'replace') - self.encode = encode def writeln(self, string=''): """write a line in the output buffer""" print(self.encode(string), file=self.out) - def display_results(self, layout): + def display_reports(self, layout): """display results encapsulated in the layout tree""" self.section = 0 if hasattr(layout, 'report_id'): layout.children[0].children[0].data += ' (%s)' % layout.report_id self._display(layout) + def display_results(self, layout): + warnings.warn("display_results is deprecated, use display_reports instead. " + "The former will be removed in Pylint 2.0.", + DeprecationWarning) + self.display_reports(layout) + def _display(self, layout): """display the layout""" raise NotImplementedError() + def display_messages(self, layout): + """Hook for displaying the messages of the reporter + + This will be called whenever the underlying messages + needs to be displayed. For some reporters, it probably + doesn't make sense to display messages as soon as they + are available, so some mechanism of storing them could be used. + This method can be implemented to display them after they've + been aggregated. + """ + # Event callbacks def on_set_current_module(self, module, filepath): - """starting analyzis of a module""" - pass + """Hook called when a module starts to be analysed.""" def on_close(self, stats, previous_stats): - """global end of analyzis""" - pass + """Hook called when a module finished analyzing.""" class CollectingReporter(BaseReporter): @@ -127,7 +140,10 @@ def __init__(self): def handle_message(self, msg): self.messages.append(msg) + _display = None + def initialize(linter): """initialize linter with reporters in this package """ + from pylint import utils utils.register_plugins(linter, __path__[0]) diff --git a/pymode/libs/pylint/reporters/guireporter.py b/pymode/libs/pylint/reporters/guireporter.py index 4ad4ebbf..32f5d224 100644 --- a/pymode/libs/pylint/reporters/guireporter.py +++ b/pymode/libs/pylint/reporters/guireporter.py @@ -4,7 +4,7 @@ from pylint.interfaces import IReporter from pylint.reporters import BaseReporter -from logilab.common.ureports import TextWriter +from pylint.reporters.ureports.text_writer import TextWriter class GUIReporter(BaseReporter): diff --git a/pymode/libs/pylint/reporters/html.py b/pymode/libs/pylint/reporters/html.py index 1e050d30..3aed847b 100644 --- a/pymode/libs/pylint/reporters/html.py +++ b/pymode/libs/pylint/reporters/html.py @@ -17,10 +17,12 @@ import string import sys -from logilab.common.ureports import HTMLWriter, Section, Table +import six from pylint.interfaces import IReporter from pylint.reporters import BaseReporter +from pylint.reporters.ureports.html_writer import HTMLWriter +from pylint.reporters.ureports.nodes import Section, Table class HTMLReporter(BaseReporter): @@ -67,7 +69,9 @@ def handle_message(self, msg): self._parse_template() # We want to add the lines given by the template - self.msgs += [str(getattr(msg, field)) for field in self.msgargs] + values = [getattr(msg, field) for field in self.msgargs] + self.msgs += [value if isinstance(value, six.text_type) else str(value) + for value in values] def set_output(self, output=None): """set output stream @@ -84,6 +88,9 @@ def _display(self, layout): (in add_message, message is not displayed, just collected so it can be displayed in an html table) """ + HTMLWriter().format(layout, self.out) + + def display_messages(self, layout): if self.msgs: # add stored messages to the layout msgs = self.header @@ -93,7 +100,7 @@ def _display(self, layout): layout.append(sect) sect.append(Table(cols=cols, children=msgs, rheaders=1)) self.msgs = [] - HTMLWriter().format(layout, self.out) + self._display(layout) def register(linter): diff --git a/pymode/libs/pylint/reporters/json.py b/pymode/libs/pylint/reporters/json.py index 7dba52b8..4065c470 100644 --- a/pymode/libs/pylint/reporters/json.py +++ b/pymode/libs/pylint/reporters/json.py @@ -14,9 +14,9 @@ """JSON reporter""" from __future__ import absolute_import, print_function +import cgi import json import sys -from cgi import escape from pylint.interfaces import IReporter from pylint.reporters import BaseReporter @@ -35,7 +35,6 @@ def __init__(self, output=sys.stdout): def handle_message(self, message): """Manage message of different type and in the context of path.""" - self.messages.append({ 'type': message.category, 'module': message.module, @@ -44,14 +43,21 @@ def handle_message(self, message): 'column': message.column, 'path': message.path, 'symbol': message.symbol, - 'message': escape(message.msg or ''), + # pylint: disable=deprecated-method; deprecated since 3.2. + 'message': cgi.escape(message.msg or ''), }) - def _display(self, layout): + def display_messages(self, layout): """Launch layouts display""" if self.messages: print(json.dumps(self.messages, indent=4), file=self.out) + def display_reports(self, _): + """Don't do nothing in this reporter.""" + + def _display(self, layout): + """Don't do nothing.""" + def register(linter): """Register the reporter classes with the linter.""" diff --git a/pymode/libs/pylint/reporters/text.py b/pymode/libs/pylint/reporters/text.py index 53c4a8da..78b10999 100644 --- a/pymode/libs/pylint/reporters/text.py +++ b/pymode/libs/pylint/reporters/text.py @@ -19,16 +19,104 @@ from __future__ import print_function import warnings +import sys -from logilab.common.ureports import TextWriter -from logilab.common.textutils import colorize_ansi +import six from pylint.interfaces import IReporter from pylint.reporters import BaseReporter -import six +from pylint import utils +from pylint.reporters.ureports.text_writer import TextWriter + TITLE_UNDERLINES = ['', '=', '-', '.'] +ANSI_PREFIX = '\033[' +ANSI_END = 'm' +ANSI_RESET = '\033[0m' +ANSI_STYLES = { + 'reset': "0", + 'bold': "1", + 'italic': "3", + 'underline': "4", + 'blink': "5", + 'inverse': "7", + 'strike': "9", +} +ANSI_COLORS = { + 'reset': "0", + 'black': "30", + 'red': "31", + 'green': "32", + 'yellow': "33", + 'blue': "34", + 'magenta': "35", + 'cyan': "36", + 'white': "37", +} + +def _get_ansi_code(color=None, style=None): + """return ansi escape code corresponding to color and style + + :type color: str or None + :param color: + the color name (see `ANSI_COLORS` for available values) + or the color number when 256 colors are available + + :type style: str or None + :param style: + style string (see `ANSI_COLORS` for available values). To get + several style effects at the same time, use a coma as separator. + + :raise KeyError: if an unexistent color or style identifier is given + + :rtype: str + :return: the built escape code + """ + ansi_code = [] + if style: + style_attrs = utils._splitstrip(style) + for effect in style_attrs: + ansi_code.append(ANSI_STYLES[effect]) + if color: + if color.isdigit(): + ansi_code.extend(['38', '5']) + ansi_code.append(color) + else: + ansi_code.append(ANSI_COLORS[color]) + if ansi_code: + return ANSI_PREFIX + ';'.join(ansi_code) + ANSI_END + return '' + +def colorize_ansi(msg, color=None, style=None): + """colorize message by wrapping it with ansi escape codes + + :type msg: str or unicode + :param msg: the message string to colorize + + :type color: str or None + :param color: + the color identifier (see `ANSI_COLORS` for available values) + + :type style: str or None + :param style: + style string (see `ANSI_COLORS` for available values). To get + several style effects at the same time, use a coma as separator. + + :raise KeyError: if an unexistent color or style identifier is given + + :rtype: str or unicode + :return: the ansi escaped string + """ + # If both color and style are not defined, then leave the text as is + if color is None and style is None: + return msg + escape_code = _get_ansi_code(color, style) + # If invalid (or unknown) color, don't wrap msg with ansi codes + if escape_code: + return '%s%s%s' % (escape_code, msg, ANSI_RESET) + return msg + class TextReporter(BaseReporter): """reports messages and layouts in plain text""" @@ -106,6 +194,9 @@ def __init__(self, output=None, color_mapping=None): TextReporter.__init__(self, output) self.color_mapping = color_mapping or \ dict(ColorizedTextReporter.COLOR_MAPPING) + if sys.platform == 'win32': + import colorama + self.out = colorama.AnsiToWin32(self.out) def _get_decoration(self, msg_id): """Returns the tuple color, style associated with msg_id as defined diff --git a/pymode/libs/pylint/reporters/ureports/__init__.py b/pymode/libs/pylint/reporters/ureports/__init__.py new file mode 100644 index 00000000..02322db3 --- /dev/null +++ b/pymode/libs/pylint/reporters/ureports/__init__.py @@ -0,0 +1,106 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of pylint. +# +# pylint is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) any +# later version. +# +# pylint 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 Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with pylint. If not, see . +"""Universal report objects and some formatting drivers. + +A way to create simple reports using python objects, primarily designed to be +formatted as text and html. +""" +import os +import sys + +import six + + +class BaseWriter(object): + """base class for ureport writers""" + + def format(self, layout, stream=None, encoding=None): + """format and write the given layout into the stream object + + unicode policy: unicode strings may be found in the layout; + try to call stream.write with it, but give it back encoded using + the given encoding if it fails + """ + if stream is None: + stream = sys.stdout + if not encoding: + encoding = getattr(stream, 'encoding', 'UTF-8') + self.encoding = encoding or 'UTF-8' + self.out = stream + self.begin_format() + layout.accept(self) + self.end_format() + + def format_children(self, layout): + """recurse on the layout children and call their accept method + (see the Visitor pattern) + """ + for child in getattr(layout, 'children', ()): + child.accept(self) + + def writeln(self, string=u''): + """write a line in the output buffer""" + self.write(string + os.linesep) + + def write(self, string): + """write a string in the output buffer""" + self.out.write(string) + + def begin_format(self): + """begin to format a layout""" + self.section = 0 + + def end_format(self): + """finished to format a layout""" + + def get_table_content(self, table): + """trick to get table content without actually writing it + + return an aligned list of lists containing table cells values as string + """ + result = [[]] + cols = table.cols + for cell in self.compute_content(table): + if cols == 0: + result.append([]) + cols = table.cols + cols -= 1 + result[-1].append(cell) + # fill missing cells + while len(result[-1]) < cols: + result[-1].append(u'') + return result + + def compute_content(self, layout): + """trick to compute the formatting of children layout before actually + writing it + + return an iterator on strings (one for each child element) + """ + # Patch the underlying output stream with a fresh-generated stream, + # which is used to store a temporary representation of a child + # node. + out = self.out + try: + for child in layout.children: + stream = six.StringIO() + self.out = stream + child.accept(self) + yield stream.getvalue() + finally: + self.out = out diff --git a/pymode/libs/pylint/reporters/ureports/html_writer.py b/pymode/libs/pylint/reporters/ureports/html_writer.py new file mode 100644 index 00000000..b3db2707 --- /dev/null +++ b/pymode/libs/pylint/reporters/ureports/html_writer.py @@ -0,0 +1,93 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of pylint. +# +# pylint is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) any +# later version. +# +# pylint 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 Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with pylint. If not, see . +"""HTML formatting drivers for ureports""" + +from pylint.reporters.ureports import BaseWriter + + +class HTMLWriter(BaseWriter): + """format layouts as HTML""" + + def __init__(self, snippet=None): + super(HTMLWriter, self).__init__() + self.snippet = snippet + + def begin_format(self): + """begin to format a layout""" + super(HTMLWriter, self).begin_format() + if self.snippet is None: + self.writeln(u'') + self.writeln(u'') + + def end_format(self): + """finished to format a layout""" + if self.snippet is None: + self.writeln(u'') + self.writeln(u'') + + def visit_section(self, layout): + """display a section as html, using div + h[section level]""" + self.section += 1 + self.writeln(u'
') + self.format_children(layout) + self.writeln(u'
') + self.section -= 1 + + def visit_title(self, layout): + """display a title using """ + self.write(u'' % self.section) + self.format_children(layout) + self.writeln(u'' % self.section) + + def visit_table(self, layout): + """display a table as html""" + self.writeln(u'') + table_content = self.get_table_content(layout) + for i, row in enumerate(table_content): + if i == 0 and layout.rheaders: + self.writeln(u'') + else: + self.writeln(u'' % (u'even' if i % 2 else u'odd')) + for j, cell in enumerate(row): + cell = cell or u' ' + if (layout.rheaders and i == 0) or \ + (layout.cheaders and j == 0): + self.writeln(u'' % cell) + else: + self.writeln(u'' % cell) + self.writeln(u'') + self.writeln(u'
%s%s
') + + def visit_paragraph(self, layout): + """display links (using

)""" + self.write(u'

') + self.format_children(layout) + self.write(u'

') + + def visit_verbatimtext(self, layout): + """display verbatim text (using
)"""
+        self.write(u'
')
+        self.write(layout.data.replace(u'&', u'&').replace(u'<', u'<'))
+        self.write(u'
') + + def visit_text(self, layout): + """add some text""" + data = layout.data + if layout.escaped: + data = data.replace(u'&', u'&').replace(u'<', u'<') + self.write(data) diff --git a/pymode/libs/pylint/reporters/ureports/nodes.py b/pymode/libs/pylint/reporters/ureports/nodes.py new file mode 100644 index 00000000..9bd9c10a --- /dev/null +++ b/pymode/libs/pylint/reporters/ureports/nodes.py @@ -0,0 +1,181 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of pylint. +# +# pylint is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) any +# later version. +# +# pylint 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 Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with pylint. If not, see . +"""Micro reports objects. + +A micro report is a tree of layout and content objects. +""" + +from six import string_types + + +class VNode(object): + + def __init__(self, nid=None): + self.id = nid + # navigation + self.parent = None + self.children = [] + + def __iter__(self): + return iter(self.children) + + def append(self, child): + """add a node to children""" + self.children.append(child) + child.parent = self + + def insert(self, index, child): + """insert a child node""" + self.children.insert(index, child) + child.parent = self + + def _get_visit_name(self): + """ + return the visit name for the mixed class. When calling 'accept', the + method <'visit_' + name returned by this method> will be called on the + visitor + """ + try: + return self.TYPE.replace('-', '_') + except Exception: + return self.__class__.__name__.lower() + + def accept(self, visitor, *args, **kwargs): + func = getattr(visitor, 'visit_%s' % self._get_visit_name()) + return func(self, *args, **kwargs) + + def leave(self, visitor, *args, **kwargs): + func = getattr(visitor, 'leave_%s' % self._get_visit_name()) + return func(self, *args, **kwargs) + + +class BaseLayout(VNode): + """base container node + + attributes + * children : components in this table (i.e. the table's cells) + """ + def __init__(self, children=(), **kwargs): + super(BaseLayout, self).__init__(**kwargs) + for child in children: + if isinstance(child, VNode): + self.append(child) + else: + self.add_text(child) + + def append(self, child): + """overridden to detect problems easily""" + assert child not in self.parents() + VNode.append(self, child) + + def parents(self): + """return the ancestor nodes""" + assert self.parent is not self + if self.parent is None: + return [] + return [self.parent] + self.parent.parents() + + def add_text(self, text): + """shortcut to add text data""" + self.children.append(Text(text)) + + +# non container nodes ######################################################### + +class Text(VNode): + """a text portion + + attributes : + * data : the text value as an encoded or unicode string + """ + def __init__(self, data, escaped=True, **kwargs): + super(Text, self).__init__(**kwargs) + #if isinstance(data, unicode): + # data = data.encode('ascii') + assert isinstance(data, string_types), data.__class__ + self.escaped = escaped + self.data = data + + +class VerbatimText(Text): + """a verbatim text, display the raw data + + attributes : + * data : the text value as an encoded or unicode string + """ + +# container nodes ############################################################# + +class Section(BaseLayout): + """a section + + attributes : + * BaseLayout attributes + + a title may also be given to the constructor, it'll be added + as a first element + a description may also be given to the constructor, it'll be added + as a first paragraph + """ + def __init__(self, title=None, description=None, **kwargs): + super(Section, self).__init__(**kwargs) + if description: + self.insert(0, Paragraph([Text(description)])) + if title: + self.insert(0, Title(children=(title,))) + + +class Title(BaseLayout): + """a title + + attributes : + * BaseLayout attributes + + A title must not contains a section nor a paragraph! + """ + + +class Paragraph(BaseLayout): + """a simple text paragraph + + attributes : + * BaseLayout attributes + + A paragraph must not contains a section ! + """ + + +class Table(BaseLayout): + """some tabular data + + attributes : + * BaseLayout attributes + * cols : the number of columns of the table (REQUIRED) + * rheaders : the first row's elements are table's header + * cheaders : the first col's elements are table's header + * title : the table's optional title + """ + def __init__(self, cols, title=None, + rheaders=0, cheaders=0, + **kwargs): + super(Table, self).__init__(**kwargs) + assert isinstance(cols, int) + self.cols = cols + self.title = title + self.rheaders = rheaders + self.cheaders = cheaders diff --git a/pymode/libs/pylint/reporters/ureports/text_writer.py b/pymode/libs/pylint/reporters/ureports/text_writer.py new file mode 100644 index 00000000..6109b959 --- /dev/null +++ b/pymode/libs/pylint/reporters/ureports/text_writer.py @@ -0,0 +1,99 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of logilab-common. +# +# pylint is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) any +# later version. +# +# pylint 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 Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with pylint. If not, see . +"""Text formatting drivers for ureports""" + +from __future__ import print_function + +from pylint.reporters.ureports import BaseWriter + + +TITLE_UNDERLINES = [u'', u'=', u'-', u'`', u'.', u'~', u'^'] +BULLETS = [u'*', u'-'] + +class TextWriter(BaseWriter): + """format layouts as text + (ReStructured inspiration but not totally handled yet) + """ + def begin_format(self): + super(TextWriter, self).begin_format() + self.list_level = 0 + + def visit_section(self, layout): + """display a section as text + """ + self.section += 1 + self.writeln() + self.format_children(layout) + self.section -= 1 + self.writeln() + + def visit_title(self, layout): + title = u''.join(list(self.compute_content(layout))) + self.writeln(title) + try: + self.writeln(TITLE_UNDERLINES[self.section] * len(title)) + except IndexError: + print("FIXME TITLE TOO DEEP. TURNING TITLE INTO TEXT") + + def visit_paragraph(self, layout): + """enter a paragraph""" + self.format_children(layout) + self.writeln() + + def visit_table(self, layout): + """display a table as text""" + table_content = self.get_table_content(layout) + # get columns width + cols_width = [0]*len(table_content[0]) + for row in table_content: + for index, col in enumerate(row): + cols_width[index] = max(cols_width[index], len(col)) + self.default_table(layout, table_content, cols_width) + self.writeln() + + def default_table(self, layout, table_content, cols_width): + """format a table""" + cols_width = [size+1 for size in cols_width] + format_strings = u' '.join([u'%%-%ss'] * len(cols_width)) + format_strings = format_strings % tuple(cols_width) + format_strings = format_strings.split(u' ') + table_linesep = u'\n+' + u'+'.join([u'-'*w for w in cols_width]) + u'+\n' + headsep = u'\n+' + u'+'.join([u'='*w for w in cols_width]) + u'+\n' + # FIXME: layout.cheaders + self.write(table_linesep) + for index, line in enumerate(table_content): + self.write(u'|') + for line_index, at_index in enumerate(line): + self.write(format_strings[line_index] % at_index) + self.write(u'|') + if index == 0 and layout.rheaders: + self.write(headsep) + else: + self.write(table_linesep) + + def visit_verbatimtext(self, layout): + """display a verbatim layout as text (so difficult ;) + """ + self.writeln(u'::\n') + for line in layout.data.splitlines(): + self.writeln(u' ' + line) + self.writeln() + + def visit_text(self, layout): + """add some text""" + self.write(u'%s' % layout.data) diff --git a/pymode/libs/pylint/test/data/__init__.py b/pymode/libs/pylint/test/data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymode/libs/pylint/test/data/ascript b/pymode/libs/pylint/test/data/ascript new file mode 100644 index 00000000..f401ebc4 --- /dev/null +++ b/pymode/libs/pylint/test/data/ascript @@ -0,0 +1,2 @@ +#!/usr/bin/python +"""ttttttttttttttttttttoooooooooooooooooooooooooooooooooooooooooooooooooooooo lllllllllllooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg""" diff --git a/pymode/libs/pylint/test/data/classes_No_Name.dot b/pymode/libs/pylint/test/data/classes_No_Name.dot new file mode 100644 index 00000000..3a9df796 --- /dev/null +++ b/pymode/libs/pylint/test/data/classes_No_Name.dot @@ -0,0 +1,12 @@ +digraph "classes_No_Name" { +charset="utf-8" +rankdir=BT +"0" [label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value()\l}", shape="record"]; +"1" [label="{DoNothing|\l|}", shape="record"]; +"2" [label="{Interface|\l|get_value()\lset_value()\l}", shape="record"]; +"3" [label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record"]; +"3" -> "0" [arrowhead="empty", arrowtail="none"]; +"0" -> "2" [arrowhead="empty", arrowtail="node", style="dashed"]; +"1" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; +"1" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"]; +} diff --git a/pymode/libs/pylint/test/data/clientmodule_test.py b/pymode/libs/pylint/test/data/clientmodule_test.py new file mode 100644 index 00000000..e520e88e --- /dev/null +++ b/pymode/libs/pylint/test/data/clientmodule_test.py @@ -0,0 +1,29 @@ +""" docstring for file clientmodule.py """ +from data.suppliermodule_test import Interface, DoNothing + +class Ancestor(object): + """ Ancestor method """ + __implements__ = (Interface,) + cls_member = DoNothing() + + def __init__(self, value): + local_variable = 0 + self.attr = 'this method shouldn\'t have a docstring' + self.__value = value + + def get_value(self): + """ nice docstring ;-) """ + return self.__value + + def set_value(self, value): + self.__value = value + return 'this method shouldn\'t have a docstring' + +class Specialization(Ancestor): + TYPE = 'final class' + top = 'class' + + def __init__(self, value, _id): + Ancestor.__init__(self, value) + self._id = _id + self.relation = DoNothing() diff --git a/pymode/libs/pylint/test/data/packages_No_Name.dot b/pymode/libs/pylint/test/data/packages_No_Name.dot new file mode 100644 index 00000000..1ceeb724 --- /dev/null +++ b/pymode/libs/pylint/test/data/packages_No_Name.dot @@ -0,0 +1,8 @@ +digraph "packages_No_Name" { +charset="utf-8" +rankdir=BT +"0" [label="data", shape="box"]; +"1" [label="data.clientmodule_test", shape="box"]; +"2" [label="data.suppliermodule_test", shape="box"]; +"1" -> "2" [arrowhead="open", arrowtail="none"]; +} diff --git a/pymode/libs/pylint/test/data/suppliermodule_test.py b/pymode/libs/pylint/test/data/suppliermodule_test.py new file mode 100644 index 00000000..24dc9a02 --- /dev/null +++ b/pymode/libs/pylint/test/data/suppliermodule_test.py @@ -0,0 +1,10 @@ +""" file suppliermodule.py """ + +class Interface: + def get_value(self): + raise NotImplementedError + + def set_value(self, value): + raise NotImplementedError + +class DoNothing: pass diff --git a/pymode/libs/pylint/test/extensions/__init__.py b/pymode/libs/pylint/test/extensions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymode/libs/pylint/test/extensions/data/elif.py b/pymode/libs/pylint/test/extensions/data/elif.py new file mode 100644 index 00000000..22e79c1d --- /dev/null +++ b/pymode/libs/pylint/test/extensions/data/elif.py @@ -0,0 +1,26 @@ +"""Checks use of "else if" triggers a refactor message""" + +def my_function(): + """docstring""" + myint = 2 + if myint > 5: + pass + else: + if myint <= 5: + pass + else: + myint = 3 + if myint > 2: + if myint > 3: + pass + elif myint == 3: + pass + elif myint < 3: + pass + else: + if myint: + pass + else: + if myint: + pass + myint = 4 diff --git a/pymode/libs/pylint/test/extensions/test_check_docs.py b/pymode/libs/pylint/test/extensions/test_check_docs.py new file mode 100644 index 00000000..a8ae5fd7 --- /dev/null +++ b/pymode/libs/pylint/test/extensions/test_check_docs.py @@ -0,0 +1,666 @@ +"""Unit tests for the pylint checkers in :mod:`pylint.extensions.check_docs`, +in particular the parameter documentation checker `ParamDocChecker` +""" +from __future__ import division, print_function, absolute_import + +import unittest +import sys + +import astroid +from astroid import test_utils +from pylint.testutils import CheckerTestCase, Message, set_config + +from pylint.extensions.check_docs import ParamDocChecker, space_indentation + + +class ParamDocCheckerTest(CheckerTestCase): + """Tests for pylint_plugin.ParamDocChecker""" + CHECKER_CLASS = ParamDocChecker + + def test_space_indentation(self): + self.assertEqual(space_indentation('abc'), 0) + self.assertEqual(space_indentation(''), 0) + self.assertEqual(space_indentation(' abc'), 2) + self.assertEqual(space_indentation('\n abc'), 0) + self.assertEqual(space_indentation(' \n abc'), 3) + + def test_missing_func_params_in_sphinx_docstring(self): + """Example of a function with missing Sphinx parameter documentation in + the docstring + """ + node = test_utils.extract_node(""" + def function_foo(x, y, z): + '''docstring ... + + :param x: bla + + :param int z: bar + ''' + pass + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('y',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('x, y',)) + ): + self.checker.visit_functiondef(node) + + def test_missing_func_params_in_google_docstring(self): + """Example of a function with missing Google style parameter + documentation in the docstring + """ + node = test_utils.extract_node(""" + def function_foo(x, y, z): + '''docstring ... + + Args: + x: bla + z (int): bar + + some other stuff + ''' + pass + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('y',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('x, y',)) + ): + self.checker.visit_functiondef(node) + + def test_missing_func_params_in_numpy_docstring(self): + """Example of a function with missing NumPy style parameter + documentation in the docstring + """ + node = test_utils.extract_node(""" + def function_foo(x, y, z): + '''docstring ... + + Parameters + ---------- + x: + bla + z: int + bar + + some other stuff + ''' + pass + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('y',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('x, y',)) + ): + self.checker.visit_functiondef(node) + + def test_tolerate_no_param_documentation_at_all(self): + """Example of a function with no parameter documentation at all + + No error message is emitted. + """ + node = test_utils.extract_node(""" + def function_foo(x, y): + '''docstring ... + + missing parameter documentation''' + pass + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + + @set_config(accept_no_param_doc=False) + def test_don_t_tolerate_no_param_documentation_at_all(self): + """Example of a function with no parameter documentation at all + + No error message is emitted. + """ + node = test_utils.extract_node(""" + def function_foo(x, y): + '''docstring ... + + missing parameter documentation''' + pass + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('x, y',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('x, y',)) + ): + self.checker.visit_functiondef(node) + + def _visit_methods_of_class(self, node): + """Visit all methods of a class node + + :param node: class node + :type node: :class:`astroid.scoped_nodes.Class` + """ + for body_item in node.body: + if (isinstance(body_item, astroid.FunctionDef) + and hasattr(body_item, 'name')): + self.checker.visit_functiondef(body_item) + + def test_missing_method_params_in_sphinx_docstring(self): + """Example of a class method with missing parameter documentation in + the Sphinx style docstring + """ + node = test_utils.extract_node(""" + class Foo(object): + def method_foo(self, x, y): + '''docstring ... + + missing parameter documentation + + :param x: bla + ''' + pass + """) + method_node = node.body[0] + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=method_node, + args=('y',)), + Message( + msg_id='missing-type-doc', + node=method_node, + args=('x, y',)) + ): + self._visit_methods_of_class(node) + + def test_missing_method_params_in_google_docstring(self): + """Example of a class method with missing parameter documentation in + the Google style docstring + """ + node = test_utils.extract_node(""" + class Foo(object): + def method_foo(self, x, y): + '''docstring ... + + missing parameter documentation + + Args: + x: bla + ''' + pass + """) + method_node = node.body[0] + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=method_node, + args=('y',)), + Message( + msg_id='missing-type-doc', + node=method_node, + args=('x, y',)) + ): + self._visit_methods_of_class(node) + + def test_missing_method_params_in_numpy_docstring(self): + """Example of a class method with missing parameter documentation in + the Numpy style docstring + """ + node = test_utils.extract_node(""" + class Foo(object): + def method_foo(self, x, y): + '''docstring ... + + missing parameter documentation + + Parameters + ---------- + x: + bla + ''' + pass + """) + method_node = node.body[0] + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=method_node, + args=('y',)), + Message( + msg_id='missing-type-doc', + node=method_node, + args=('x, y',)) + ): + self._visit_methods_of_class(node) + + def test_existing_func_params_in_sphinx_docstring(self): + """Example of a function with correctly documented parameters and + return values (Sphinx style) + """ + node = test_utils.extract_node(""" + def function_foo(xarg, yarg, zarg): + '''function foo ... + + :param xarg: bla xarg + :type xarg: int + + :param yarg: bla yarg + :type yarg: float + + :param int zarg: bla zarg + + :return: sum + :rtype: float + ''' + return xarg + yarg + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + + def test_existing_func_params_in_google_docstring(self): + """Example of a function with correctly documented parameters and + return values (Google style) + """ + node = test_utils.extract_node(""" + def function_foo(xarg, yarg, zarg): + '''function foo ... + + Args: + xarg (int): bla xarg + yarg (float): bla + bla yarg + + zarg (int): bla zarg + + Returns: + float: sum + ''' + return xarg + yarg + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + + def test_existing_func_params_in_numpy_docstring(self): + """Example of a function with correctly documented parameters and + return values (Numpy style) + """ + node = test_utils.extract_node(""" + def function_foo(xarg, yarg, zarg): + '''function foo ... + + Parameters + ---------- + xarg: int + bla xarg + yarg: float + bla yarg + + zarg: int + bla zarg + + Returns + ------- + float + sum + ''' + return xarg + yarg + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + + def test_wrong_name_of_func_params_in_sphinx_docstring(self): + """Example of functions with inconsistent parameter names in the + signature and in the Sphinx style documentation + """ + node = test_utils.extract_node(""" + def function_foo(xarg, yarg, zarg): + '''function foo ... + + :param xarg1: bla xarg + :type xarg: int + + :param yarg: bla yarg + :type yarg1: float + + :param str zarg1: bla zarg + ''' + return xarg + yarg + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('xarg, xarg1, zarg, zarg1',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('yarg, yarg1, zarg, zarg1',)), + ): + self.checker.visit_functiondef(node) + + node = test_utils.extract_node(""" + def function_foo(xarg, yarg): + '''function foo ... + + :param yarg1: bla yarg + :type yarg1: float + + For the other parameters, see bla. + ''' + return xarg + yarg + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('yarg1',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('yarg1',)) + ): + self.checker.visit_functiondef(node) + + def test_wrong_name_of_func_params_in_google_docstring(self): + """Example of functions with inconsistent parameter names in the + signature and in the Google style documentation + """ + node = test_utils.extract_node(""" + def function_foo(xarg, yarg, zarg): + '''function foo ... + + Args: + xarg1 (int): bla xarg + yarg (float): bla yarg + + zarg1 (str): bla zarg + ''' + return xarg + yarg + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('xarg, xarg1, zarg, zarg1',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('xarg, xarg1, zarg, zarg1',)), + ): + self.checker.visit_functiondef(node) + + node = test_utils.extract_node(""" + def function_foo(xarg, yarg): + '''function foo ... + + Args: + yarg1 (float): bla yarg + + For the other parameters, see bla. + ''' + return xarg + yarg + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('yarg1',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('yarg1',)) + ): + self.checker.visit_functiondef(node) + + def test_wrong_name_of_func_params_in_numpy_docstring(self): + """Example of functions with inconsistent parameter names in the + signature and in the Numpy style documentation + """ + node = test_utils.extract_node(""" + def function_foo(xarg, yarg, zarg): + '''function foo ... + + Parameters + ---------- + xarg1: int + bla xarg + yarg: float + bla yarg + + zarg1: str + bla zarg + ''' + return xarg + yarg + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('xarg, xarg1, zarg, zarg1',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('xarg, xarg1, zarg, zarg1',)), + ): + self.checker.visit_functiondef(node) + + node = test_utils.extract_node(""" + def function_foo(xarg, yarg): + '''function foo ... + + Parameters + ---------- + yarg1: float + bla yarg + + For the other parameters, see bla. + ''' + return xarg + yarg + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('yarg1',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('yarg1',)) + ): + self.checker.visit_functiondef(node) + + def test_see_sentence_for_func_params_in_sphinx_docstring(self): + """Example for the usage of "For the other parameters, see" to avoid + too many repetitions, e.g. in functions or methods adhering to a + given interface (Sphinx style) + """ + node = test_utils.extract_node(""" + def function_foo(xarg, yarg): + '''function foo ... + + :param yarg: bla yarg + :type yarg: float + + For the other parameters, see :func:`bla` + ''' + return xarg + yarg + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + + def test_see_sentence_for_func_params_in_google_docstring(self): + """Example for the usage of "For the other parameters, see" to avoid + too many repetitions, e.g. in functions or methods adhering to a + given interface (Google style) + """ + node = test_utils.extract_node(""" + def function_foo(xarg, yarg): + '''function foo ... + + Args: + yarg (float): bla yarg + + For the other parameters, see :func:`bla` + ''' + return xarg + yarg + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + + def test_see_sentence_for_func_params_in_numpy_docstring(self): + """Example for the usage of "For the other parameters, see" to avoid + too many repetitions, e.g. in functions or methods adhering to a + given interface (Numpy style) + """ + node = test_utils.extract_node(""" + def function_foo(xarg, yarg): + '''function foo ... + + Parameters + ---------- + yarg: float + bla yarg + + For the other parameters, see :func:`bla` + ''' + return xarg + yarg + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + + def test_constr_params_in_class_sphinx(self): + """Example of a class with missing constructor parameter documentation + (Sphinx style) + + Everything is completely analogous to functions. + """ + node = test_utils.extract_node(""" + class ClassFoo(object): + '''docstring foo + + :param y: bla + + missing constructor parameter documentation + ''' + + def __init__(self, x, y): + pass + + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('x',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('x, y',)) + ): + self._visit_methods_of_class(node) + + def test_constr_params_in_class_google(self): + """Example of a class with missing constructor parameter documentation + (Google style) + + Everything is completely analogous to functions. + """ + node = test_utils.extract_node(""" + class ClassFoo(object): + '''docstring foo + + Args: + y: bla + + missing constructor parameter documentation + ''' + + def __init__(self, x, y): + pass + + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('x',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('x, y',)) + ): + self._visit_methods_of_class(node) + + def test_constr_params_in_class_numpy(self): + """Example of a class with missing constructor parameter documentation + (Numpy style) + + Everything is completely analogous to functions. + """ + node = test_utils.extract_node(""" + class ClassFoo(object): + '''docstring foo + + Parameters + ---------- + y: + bla + + missing constructor parameter documentation + ''' + + def __init__(self, x, y): + pass + + """) + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('x',)), + Message( + msg_id='missing-type-doc', + node=node, + args=('x, y',)) + ): + self._visit_methods_of_class(node) + + @unittest.skipIf(sys.version_info[0] != 3, "Enabled on Python 3") + def test_kwonlyargs_are_taken_in_account(self): + node = test_utils.extract_node(''' + def my_func(arg, *, kwonly, missing_kwonly): + """The docstring + + :param int arg: The argument. + :param bool kwonly: A keyword-arg. + """ + ''') + with self.assertAddsMessages( + Message( + msg_id='missing-param-doc', + node=node, + args=('missing_kwonly', )), + Message( + msg_id='missing-type-doc', + node=node, + args=('missing_kwonly', ))): + self.checker.visit_functiondef(node) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/extensions/test_elseif_used.py b/pymode/libs/pylint/test/extensions/test_elseif_used.py new file mode 100644 index 00000000..991193ce --- /dev/null +++ b/pymode/libs/pylint/test/extensions/test_elseif_used.py @@ -0,0 +1,47 @@ +"""Tests for the pylint checker in :mod:`pylint.extensions.check_elif +""" + +import os +import os.path as osp +import unittest + +from pylint import checkers +from pylint.extensions.check_elif import ElseifUsedChecker +from pylint.lint import PyLinter +from pylint.reporters import BaseReporter + + +class TestReporter(BaseReporter): + + def handle_message(self, msg): + self.messages.append(msg) + + def on_set_current_module(self, module, filepath): + self.messages = [] + + +class CheckElseIfUsedTC(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls._linter = PyLinter() + cls._linter.set_reporter(TestReporter()) + checkers.initialize(cls._linter) + cls._linter.register_checker(ElseifUsedChecker(cls._linter)) + + def test_elseif_message(self): + elif_test = osp.join(osp.dirname(osp.abspath(__file__)), 'data', + 'elif.py') + self._linter.check([elif_test]) + msgs = self._linter.reporter.messages + self.assertEqual(len(msgs), 2) + for msg in msgs: + self.assertEqual(msg.symbol, 'else-if-used') + self.assertEqual(msg.msg, + 'Consider using "elif" instead of "else if"') + self.assertEqual(msgs[0].line, 9) + self.assertEqual(msgs[1].line, 21) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/functional/__init__.py b/pymode/libs/pylint/test/functional/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymode/libs/pylint/test/functional/abstract_abc_methods.py b/pymode/libs/pylint/test/functional/abstract_abc_methods.py new file mode 100644 index 00000000..31ac2ae0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_abc_methods.py @@ -0,0 +1,17 @@ +""" This should not warn about `prop` being abstract in Child """ +# pylint: disable=too-few-public-methods, no-absolute-import,metaclass-assignment + +import abc + +class Parent(object): + """Abstract Base Class """ + __metaclass__ = abc.ABCMeta + + @property + @abc.abstractmethod + def prop(self): + """ Abstract """ + +class Child(Parent): + """ No warning for the following. """ + prop = property(lambda self: 1) diff --git a/pymode/libs/pylint/test/functional/abstract_class_instantiated_in_class.py b/pymode/libs/pylint/test/functional/abstract_class_instantiated_in_class.py new file mode 100644 index 00000000..9402c127 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_class_instantiated_in_class.py @@ -0,0 +1,20 @@ +"""Don't warn if the class is instantiated in its own body.""" +# pylint: disable=missing-docstring + + +import abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +class Ala(object): + + @abc.abstractmethod + def bala(self): + pass + + @classmethod + def portocala(cls): + instance = cls() + return instance diff --git a/pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.py b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.py new file mode 100644 index 00000000..d30c1a5f --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.py @@ -0,0 +1,83 @@ +"""Check that instantiating a class with +`abc.ABCMeta` as metaclass fails if it defines +abstract methods. +""" + +# pylint: disable=too-few-public-methods, missing-docstring +# pylint: disable=no-absolute-import, metaclass-assignment +# pylint: disable=abstract-method, import-error, wildcard-import + +import abc +from abc import ABCMeta +from lala import Bala + + +class GoodClass(object): + __metaclass__ = abc.ABCMeta + +class SecondGoodClass(object): + __metaclass__ = abc.ABCMeta + + def test(self): + """ do nothing. """ + +class ThirdGoodClass(object): + __metaclass__ = abc.ABCMeta + + def test(self): + raise NotImplementedError() + +class FourthGoodClass(object): + __metaclass__ = ABCMeta + +class BadClass(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def test(self): + """ do nothing. """ + +class SecondBadClass(object): + __metaclass__ = abc.ABCMeta + + @property + @abc.abstractmethod + def test(self): + """ do nothing. """ + +class ThirdBadClass(object): + __metaclass__ = ABCMeta + + @abc.abstractmethod + def test(self): + pass + +class FourthBadClass(ThirdBadClass): + pass + + +class SomeMetaclass(object): + __metaclass__ = ABCMeta + + @abc.abstractmethod + def prop(self): + pass + +class FifthGoodClass(SomeMetaclass): + """Don't consider this abstract if some attributes are + there, but can't be inferred. + """ + prop = Bala # missing + + +def main(): + """ do nothing """ + GoodClass() + SecondGoodClass() + ThirdGoodClass() + FourthGoodClass() + BadClass() # [abstract-class-instantiated] + SecondBadClass() # [abstract-class-instantiated] + ThirdBadClass() # [abstract-class-instantiated] + FourthBadClass() # [abstract-class-instantiated] + diff --git a/pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.rc b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.rc new file mode 100644 index 00000000..b11e16d5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.txt b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.txt new file mode 100644 index 00000000..1e4a72d0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py2.txt @@ -0,0 +1,4 @@ +abstract-class-instantiated:79:main:Abstract class 'BadClass' with abstract methods instantiated +abstract-class-instantiated:80:main:Abstract class 'SecondBadClass' with abstract methods instantiated +abstract-class-instantiated:81:main:Abstract class 'ThirdBadClass' with abstract methods instantiated +abstract-class-instantiated:82:main:Abstract class 'FourthBadClass' with abstract methods instantiated diff --git a/pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.py b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.py new file mode 100644 index 00000000..f3731899 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.py @@ -0,0 +1,111 @@ +"""Check that instantiating a class with +`abc.ABCMeta` as metaclass fails if it defines +abstract methods. +""" + +# pylint: disable=too-few-public-methods, missing-docstring +# pylint: disable=abstract-method, import-error + +import abc +import weakref +from lala import Bala + + +class GoodClass(object, metaclass=abc.ABCMeta): + pass + +class SecondGoodClass(object, metaclass=abc.ABCMeta): + def test(self): + """ do nothing. """ + +class ThirdGoodClass(object, metaclass=abc.ABCMeta): + """ This should not raise the warning. """ + def test(self): + raise NotImplementedError() + +class BadClass(object, metaclass=abc.ABCMeta): + @abc.abstractmethod + def test(self): + """ do nothing. """ + +class SecondBadClass(object, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def test(self): + """ do nothing. """ + +class ThirdBadClass(SecondBadClass): + pass + + +class Structure(object, metaclass=abc.ABCMeta): + @abc.abstractmethod + def __iter__(self): + pass + @abc.abstractmethod + def __len__(self): + pass + @abc.abstractmethod + def __contains__(self, _): + pass + @abc.abstractmethod + def __hash__(self): + pass + +class Container(Structure): + def __contains__(self, _): + pass + +class Sizable(Structure): + def __len__(self): + pass + +class Hashable(Structure): + __hash__ = 42 + + +class Iterator(Structure): + def keys(self): # pylint: disable=no-self-use + return iter([1, 2, 3]) + + __iter__ = keys + +class AbstractSizable(Structure): + @abc.abstractmethod + def length(self): + pass + __len__ = length + +class NoMroAbstractMethods(Container, Iterator, Sizable, Hashable): + pass + +class BadMroAbstractMethods(Container, Iterator, AbstractSizable): + pass + +class SomeMetaclass(metaclass=abc.ABCMeta): + + @abc.abstractmethod + def prop(self): + pass + +class FourthGoodClass(SomeMetaclass): + """Don't consider this abstract if some attributes are + there, but can't be inferred. + """ + prop = Bala # missing + + +def main(): + """ do nothing """ + GoodClass() + SecondGoodClass() + ThirdGoodClass() + FourthGoodClass() + weakref.WeakKeyDictionary() + weakref.WeakValueDictionary() + NoMroAbstractMethods() + + BadMroAbstractMethods() # [abstract-class-instantiated] + BadClass() # [abstract-class-instantiated] + SecondBadClass() # [abstract-class-instantiated] + ThirdBadClass() # [abstract-class-instantiated] diff --git a/pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.rc b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.rc new file mode 100644 index 00000000..a2ab06c5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.txt b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.txt new file mode 100644 index 00000000..629eec75 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py3.txt @@ -0,0 +1,4 @@ +abstract-class-instantiated:108:main:Abstract class 'BadMroAbstractMethods' with abstract methods instantiated +abstract-class-instantiated:109:main:Abstract class 'BadClass' with abstract methods instantiated +abstract-class-instantiated:110:main:Abstract class 'SecondBadClass' with abstract methods instantiated +abstract-class-instantiated:111:main:Abstract class 'ThirdBadClass' with abstract methods instantiated diff --git a/pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.py b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.py new file mode 100644 index 00000000..e24ad28f --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.py @@ -0,0 +1,19 @@ +""" +Check that instantiating a class with `abc.ABCMeta` as ancestor fails if it +defines abstract methods. +""" + +# pylint: disable=too-few-public-methods, missing-docstring, no-init + +import abc + + + +class BadClass(abc.ABC): + @abc.abstractmethod + def test(self): + pass + +def main(): + """ do nothing """ + BadClass() # [abstract-class-instantiated] diff --git a/pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.rc b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.rc new file mode 100644 index 00000000..1fb7b871 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.4 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.txt b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.txt new file mode 100644 index 00000000..afee9756 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_class_instantiated_py34.txt @@ -0,0 +1 @@ +abstract-class-instantiated:19:main:Abstract class 'BadClass' with abstract methods instantiated diff --git a/pymode/libs/pylint/test/functional/abstract_method_py2.py b/pymode/libs/pylint/test/functional/abstract_method_py2.py new file mode 100644 index 00000000..7238d442 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_method_py2.py @@ -0,0 +1,90 @@ +"""Test abstract-method warning.""" +from __future__ import print_function + +# pylint: disable=missing-docstring, no-init, no-self-use +# pylint: disable=too-few-public-methods +import abc + +class Abstract(object): + def aaaa(self): + """should be overridden in concrete class""" + raise NotImplementedError() + + def bbbb(self): + """should be overridden in concrete class""" + raise NotImplementedError() + + +class AbstractB(Abstract): + """Abstract class. + + this class is checking that it does not output an error msg for + unimplemeted methods in abstract classes + """ + def cccc(self): + """should be overridden in concrete class""" + raise NotImplementedError() + +class Concret(Abstract): # [abstract-method] + """Concrete class""" + + def aaaa(self): + """overidden form Abstract""" + + +class Structure(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def __iter__(self): + pass + @abc.abstractmethod + def __len__(self): + pass + @abc.abstractmethod + def __contains__(self, _): + pass + @abc.abstractmethod + def __hash__(self): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Container(Structure): + def __contains__(self, _): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Sizable(Structure): + def __len__(self): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Hashable(Structure): + __hash__ = 42 + + +# +1: [abstract-method, abstract-method, abstract-method] +class Iterator(Structure): + def keys(self): + return iter([1, 2, 3]) + + __iter__ = keys + + +class AbstractSizable(Structure): + @abc.abstractmethod + def length(self): + pass + __len__ = length + + +class GoodComplexMRO(Container, Iterator, Sizable, Hashable): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class BadComplexMro(Container, Iterator, AbstractSizable): + pass diff --git a/pymode/libs/pylint/test/functional/abstract_method_py2.rc b/pymode/libs/pylint/test/functional/abstract_method_py2.rc new file mode 100644 index 00000000..b11e16d5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_method_py2.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/abstract_method_py2.txt b/pymode/libs/pylint/test/functional/abstract_method_py2.txt new file mode 100644 index 00000000..ce0c6176 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_method_py2.txt @@ -0,0 +1,16 @@ +abstract-method:28:Concret:"Method 'bbbb' is abstract in class 'Abstract' but is not overridden" +abstract-method:53:Container:"Method '__hash__' is abstract in class 'Structure' but is not overridden" +abstract-method:53:Container:"Method '__iter__' is abstract in class 'Structure' but is not overridden" +abstract-method:53:Container:"Method '__len__' is abstract in class 'Structure' but is not overridden" +abstract-method:59:Sizable:"Method '__contains__' is abstract in class 'Structure' but is not overridden" +abstract-method:59:Sizable:"Method '__hash__' is abstract in class 'Structure' but is not overridden" +abstract-method:59:Sizable:"Method '__iter__' is abstract in class 'Structure' but is not overridden" +abstract-method:65:Hashable:"Method '__contains__' is abstract in class 'Structure' but is not overridden" +abstract-method:65:Hashable:"Method '__iter__' is abstract in class 'Structure' but is not overridden" +abstract-method:65:Hashable:"Method '__len__' is abstract in class 'Structure' but is not overridden" +abstract-method:70:Iterator:"Method '__contains__' is abstract in class 'Structure' but is not overridden" +abstract-method:70:Iterator:"Method '__hash__' is abstract in class 'Structure' but is not overridden" +abstract-method:70:Iterator:"Method '__len__' is abstract in class 'Structure' but is not overridden" +abstract-method:89:BadComplexMro:"Method '__hash__' is abstract in class 'Structure' but is not overridden" +abstract-method:89:BadComplexMro:"Method '__len__' is abstract in class 'AbstractSizable' but is not overridden" +abstract-method:89:BadComplexMro:"Method 'length' is abstract in class 'AbstractSizable' but is not overridden" diff --git a/pymode/libs/pylint/test/functional/abstract_method_py3.py b/pymode/libs/pylint/test/functional/abstract_method_py3.py new file mode 100644 index 00000000..daacf65c --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_method_py3.py @@ -0,0 +1,88 @@ +"""Test abstract-method warning.""" +from __future__ import print_function + +# pylint: disable=missing-docstring, no-init, no-self-use +# pylint: disable=too-few-public-methods +import abc + +class Abstract(object): + def aaaa(self): + """should be overridden in concrete class""" + raise NotImplementedError() + + def bbbb(self): + """should be overridden in concrete class""" + raise NotImplementedError() + + +class AbstractB(Abstract): + """Abstract class. + + this class is checking that it does not output an error msg for + unimplemeted methods in abstract classes + """ + def cccc(self): + """should be overridden in concrete class""" + raise NotImplementedError() + +class Concret(Abstract): # [abstract-method] + """Concrete class""" + + def aaaa(self): + """overidden form Abstract""" + + +class Structure(object, metaclass=abc.ABCMeta): + @abc.abstractmethod + def __iter__(self): + pass + @abc.abstractmethod + def __len__(self): + pass + @abc.abstractmethod + def __contains__(self, _): + pass + @abc.abstractmethod + def __hash__(self): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Container(Structure): + def __contains__(self, _): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Sizable(Structure): + def __len__(self): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Hashable(Structure): + __hash__ = 42 + + +# +1: [abstract-method, abstract-method, abstract-method] +class Iterator(Structure): + def keys(self): + return iter([1, 2, 3]) + + __iter__ = keys + + +class AbstractSizable(Structure): + @abc.abstractmethod + def length(self): + pass + __len__ = length + + +class GoodComplexMRO(Container, Iterator, Sizable, Hashable): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class BadComplexMro(Container, Iterator, AbstractSizable): + pass diff --git a/pymode/libs/pylint/test/functional/abstract_method_py3.rc b/pymode/libs/pylint/test/functional/abstract_method_py3.rc new file mode 100644 index 00000000..a2ab06c5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_method_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/abstract_method_py3.txt b/pymode/libs/pylint/test/functional/abstract_method_py3.txt new file mode 100644 index 00000000..7d83f380 --- /dev/null +++ b/pymode/libs/pylint/test/functional/abstract_method_py3.txt @@ -0,0 +1,16 @@ +abstract-method:28:Concret:"Method 'bbbb' is abstract in class 'Abstract' but is not overridden" +abstract-method:51:Container:"Method '__hash__' is abstract in class 'Structure' but is not overridden" +abstract-method:51:Container:"Method '__iter__' is abstract in class 'Structure' but is not overridden" +abstract-method:51:Container:"Method '__len__' is abstract in class 'Structure' but is not overridden" +abstract-method:57:Sizable:"Method '__contains__' is abstract in class 'Structure' but is not overridden" +abstract-method:57:Sizable:"Method '__hash__' is abstract in class 'Structure' but is not overridden" +abstract-method:57:Sizable:"Method '__iter__' is abstract in class 'Structure' but is not overridden" +abstract-method:63:Hashable:"Method '__contains__' is abstract in class 'Structure' but is not overridden" +abstract-method:63:Hashable:"Method '__iter__' is abstract in class 'Structure' but is not overridden" +abstract-method:63:Hashable:"Method '__len__' is abstract in class 'Structure' but is not overridden" +abstract-method:68:Iterator:"Method '__contains__' is abstract in class 'Structure' but is not overridden" +abstract-method:68:Iterator:"Method '__hash__' is abstract in class 'Structure' but is not overridden" +abstract-method:68:Iterator:"Method '__len__' is abstract in class 'Structure' but is not overridden" +abstract-method:87:BadComplexMro:"Method '__hash__' is abstract in class 'Structure' but is not overridden" +abstract-method:87:BadComplexMro:"Method '__len__' is abstract in class 'AbstractSizable' but is not overridden" +abstract-method:87:BadComplexMro:"Method 'length' is abstract in class 'AbstractSizable' but is not overridden" diff --git a/pymode/libs/pylint/test/functional/access_member_before_definition.py b/pymode/libs/pylint/test/functional/access_member_before_definition.py new file mode 100644 index 00000000..53294fbc --- /dev/null +++ b/pymode/libs/pylint/test/functional/access_member_before_definition.py @@ -0,0 +1,40 @@ +# pylint: disable=missing-docstring,too-few-public-methods,invalid-name +# pylint: disable=attribute-defined-outside-init + +class Aaaa(object): + """class with attributes defined in wrong order""" + + def __init__(self): + var1 = self._var2 # [access-member-before-definition] + self._var2 = 3 + self._var3 = var1 + + +class Bbbb(object): + A = 23 + B = A + + def __getattr__(self, attr): + try: + return self.__repo + except AttributeError: + self.__repo = attr + return attr + + + def catchme(self, attr): + """no AttributeError catched""" + try: + return self._repo # [access-member-before-definition] + except ValueError: + self._repo = attr + return attr + + +class Mixin(object): + + def test_mixin(self): + """Don't emit access-member-before-definition for mixin classes.""" + if self.already_defined: + # pylint: disable=attribute-defined-outside-init + self.already_defined = None diff --git a/pymode/libs/pylint/test/functional/access_member_before_definition.txt b/pymode/libs/pylint/test/functional/access_member_before_definition.txt new file mode 100644 index 00000000..9b3ae697 --- /dev/null +++ b/pymode/libs/pylint/test/functional/access_member_before_definition.txt @@ -0,0 +1,2 @@ +access-member-before-definition:8:Aaaa.__init__:Access to member '_var2' before its definition line 9 +access-member-before-definition:28:Bbbb.catchme:Access to member '_repo' before its definition line 30 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/access_to__name__.py b/pymode/libs/pylint/test/functional/access_to__name__.py new file mode 100644 index 00000000..6f5a39ec --- /dev/null +++ b/pymode/libs/pylint/test/functional/access_to__name__.py @@ -0,0 +1,21 @@ +# pylint: disable=too-few-public-methods, print-statement +"""test access to __name__ gives undefined member on new/old class instances +but not on new/old class object +""" +from __future__ import print_function + +class Aaaa: # <3.0:[old-style-class] + """old class""" + def __init__(self): + print(self.__name__) # [no-member] + print(self.__class__.__name__) + +class NewClass(object): + """new class""" + + def __new__(cls, *args, **kwargs): + print('new', cls.__name__) + return object.__new__(cls, *args, **kwargs) + + def __init__(self): + print('init', self.__name__) # [no-member] diff --git a/pymode/libs/pylint/test/functional/access_to__name__.txt b/pymode/libs/pylint/test/functional/access_to__name__.txt new file mode 100644 index 00000000..ecf5ffd6 --- /dev/null +++ b/pymode/libs/pylint/test/functional/access_to__name__.txt @@ -0,0 +1,3 @@ +old-style-class:7:Aaaa:Old-style class defined. +no-member:10:Aaaa.__init__:Instance of 'Aaaa' has no '__name__' member:INFERENCE +no-member:21:NewClass.__init__:Instance of 'NewClass' has no '__name__' member:INFERENCE diff --git a/pymode/libs/pylint/test/functional/access_to_protected_members.py b/pymode/libs/pylint/test/functional/access_to_protected_members.py new file mode 100644 index 00000000..fd96bafe --- /dev/null +++ b/pymode/libs/pylint/test/functional/access_to_protected_members.py @@ -0,0 +1,44 @@ +# pylint: disable=too-few-public-methods, W0231, print-statement +# pylint: disable=no-classmethod-decorator +"""Test external access to protected class members.""" +from __future__ import print_function + +class MyClass(object): + """Class with protected members.""" + _cls_protected = 5 + + def __init__(self, other): + MyClass._cls_protected = 6 + self._protected = 1 + self.public = other + self.attr = 0 + + def test(self): + """Docstring.""" + self._protected += self._cls_protected + print(self.public._haha) # [protected-access] + + def clsmeth(cls): + """Docstring.""" + cls._cls_protected += 1 + print(cls._cls_protected) + clsmeth = classmethod(clsmeth) + + def _private_method(self): + """Doing nothing.""" + + +class Subclass(MyClass): + """Subclass with protected members.""" + + def __init__(self): + MyClass._protected = 5 + super(Subclass, self)._private_method() + +INST = Subclass() +INST.attr = 1 +print(INST.attr) +INST._protected = 2 # [protected-access] +print(INST._protected) # [protected-access] +INST._cls_protected = 3 # [protected-access] +print(INST._cls_protected) # [protected-access] diff --git a/pymode/libs/pylint/test/functional/access_to_protected_members.txt b/pymode/libs/pylint/test/functional/access_to_protected_members.txt new file mode 100644 index 00000000..7ba601b7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/access_to_protected_members.txt @@ -0,0 +1,5 @@ +protected-access:19:MyClass.test:Access to a protected member _haha of a client class +protected-access:41::Access to a protected member _protected of a client class +protected-access:42::Access to a protected member _protected of a client class +protected-access:43::Access to a protected member _cls_protected of a client class +protected-access:44::Access to a protected member _cls_protected of a client class diff --git a/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.py b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.py new file mode 100644 index 00000000..8f304fa4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.py @@ -0,0 +1,20 @@ +# pylint:disable=W0105, W0511 +"""Test for backslash escapes in byte vs unicode strings""" + +# Would be valid in Unicode, but probably not what you want otherwise +BAD_UNICODE = b'\u0042' # [anomalous-unicode-escape-in-string] +BAD_LONG_UNICODE = b'\U00000042' # [anomalous-unicode-escape-in-string] +# +1:[anomalous-unicode-escape-in-string] +BAD_NAMED_UNICODE = b'\N{GREEK SMALL LETTER ALPHA}' + +GOOD_UNICODE = u'\u0042' +GOOD_LONG_UNICODE = u'\U00000042' +GOOD_NAMED_UNICODE = u'\N{GREEK SMALL LETTER ALPHA}' + + +# Valid raw strings +RAW_BACKSLASHES = r'raw' +RAW_UNICODE = ur"\u0062\n" + +# In a comment you can have whatever you want: \ \\ \n \m +# even things that look like bad strings: "C:\Program Files" diff --git a/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.rc b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.rc new file mode 100644 index 00000000..a2328edb --- /dev/null +++ b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.txt b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.txt new file mode 100644 index 00000000..c242cb98 --- /dev/null +++ b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py2.txt @@ -0,0 +1,3 @@ +anomalous-unicode-escape-in-string:5::"Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix." +anomalous-unicode-escape-in-string:6::"Anomalous Unicode escape in byte string: '\U'. String constant might be missing an r or u prefix." +anomalous-unicode-escape-in-string:8::"Anomalous Unicode escape in byte string: '\N'. String constant might be missing an r or u prefix." diff --git a/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.py b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.py new file mode 100644 index 00000000..b8bfd0a9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.py @@ -0,0 +1,19 @@ +# pylint:disable=W0105, W0511 +"""Test for backslash escapes in byte vs unicode strings""" + +# Would be valid in Unicode, but probably not what you want otherwise +BAD_UNICODE = b'\u0042' # [anomalous-unicode-escape-in-string] +BAD_LONG_UNICODE = b'\U00000042' # [anomalous-unicode-escape-in-string] +# +1:[anomalous-unicode-escape-in-string] +BAD_NAMED_UNICODE = b'\N{GREEK SMALL LETTER ALPHA}' + +GOOD_UNICODE = u'\u0042' +GOOD_LONG_UNICODE = u'\U00000042' +GOOD_NAMED_UNICODE = u'\N{GREEK SMALL LETTER ALPHA}' + + +# Valid raw strings +RAW_BACKSLASHES = r'raw' + +# In a comment you can have whatever you want: \ \\ \n \m +# even things that look like bad strings: "C:\Program Files" diff --git a/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.rc b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.rc new file mode 100644 index 00000000..8c6eb56c --- /dev/null +++ b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.txt b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.txt new file mode 100644 index 00000000..d2c5d10e --- /dev/null +++ b/pymode/libs/pylint/test/functional/anomalous_unicode_escape_py3.txt @@ -0,0 +1,3 @@ +anomalous-unicode-escape-in-string:5::"Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix." +anomalous-unicode-escape-in-string:6::"Anomalous Unicode escape in byte string: '\U'. String constant might be missing an r or u prefix." +anomalous-unicode-escape-in-string:8::"Anomalous Unicode escape in byte string: '\N'. String constant might be missing an r or u prefix." \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/arguments.py b/pymode/libs/pylint/test/functional/arguments.py new file mode 100644 index 00000000..037a3f93 --- /dev/null +++ b/pymode/libs/pylint/test/functional/arguments.py @@ -0,0 +1,167 @@ +# pylint: disable=too-few-public-methods, no-absolute-import,missing-docstring,import-error,wrong-import-position +"""Test function argument checker""" + +def decorator(fun): + """Decorator""" + return fun + + +class DemoClass(object): + """Test class for method invocations.""" + + @staticmethod + def static_method(arg): + """static method.""" + return arg + arg + + @classmethod + def class_method(cls, arg): + """class method""" + return arg + arg + + def method(self, arg): + """method.""" + return (self, arg) + + @decorator + def decorated_method(self, arg): + """decorated method.""" + return (self, arg) + + +def function_1_arg(first_argument): + """one argument function""" + return first_argument + +def function_3_args(first_argument, second_argument, third_argument): + """three arguments function""" + return first_argument, second_argument, third_argument + +def function_default_arg(one=1, two=2): + """fonction with default value""" + return two, one + + +function_1_arg(420) +function_1_arg() # [no-value-for-parameter] +function_1_arg(1337, 347) # [too-many-function-args] + +function_3_args(420, 789) # [no-value-for-parameter] +# +1:[no-value-for-parameter,no-value-for-parameter,no-value-for-parameter] +function_3_args() +function_3_args(1337, 347, 456) +function_3_args('bab', 'bebe', None, 5.6) # [too-many-function-args] + +function_default_arg(1, two=5) +function_default_arg(two=5) + +function_1_arg(bob=4) # [unexpected-keyword-arg,no-value-for-parameter] +function_default_arg(1, 4, coin="hello") # [unexpected-keyword-arg] + +function_default_arg(1, one=5) # [redundant-keyword-arg] + +# Remaining tests are for coverage of correct names in messages. +LAMBDA = lambda arg: 1 + +LAMBDA() # [no-value-for-parameter] + +def method_tests(): + """Method invocations.""" + demo = DemoClass() + demo.static_method() # [no-value-for-parameter] + DemoClass.static_method() # [no-value-for-parameter] + + demo.class_method() # [no-value-for-parameter] + DemoClass.class_method() # [no-value-for-parameter] + + demo.method() # [no-value-for-parameter] + DemoClass.method(demo) # [no-value-for-parameter] + + demo.decorated_method() # [no-value-for-parameter] + DemoClass.decorated_method(demo) # [no-value-for-parameter] + +# Test a regression (issue #234) +import sys + +class Text(object): + """ Regression """ + + if sys.version_info > (3,): + def __new__(cls): + """ empty """ + return object.__new__(cls) + else: + def __new__(cls): + """ empty """ + return object.__new__(cls) + +Text() + +class TestStaticMethod(object): + + @staticmethod + def test(first, second=None, **kwargs): + return first, second, kwargs + + def func(self): + self.test(42) + self.test(42, second=34) + self.test(42, 42) + self.test() # [no-value-for-parameter] + self.test(42, 42, 42) # [too-many-function-args] + +# Should be enabled on a more capable astroid (> 1.4.0) +# class TypeCheckConstructor(object): +# def __init__(self, first, second): +# self.first = first +# self.second = second +# def test(self): +# type(self)(1, 2, 3) +# # +# type(self)() +# type(self)(1, lala=2) +# type(self)(1, 2) +# type(self)(first=1, second=2) + + +class Test(object): + """ lambda needs Test instance as first argument """ + lam = lambda self, icon: (self, icon) + + def test(self): + self.lam(42) + self.lam() # [no-value-for-parameter] + self.lam(1, 2, 3) # [too-many-function-args] + +Test().lam() # [no-value-for-parameter] + +# Don't emit a redundant-keyword-arg for this example, +# it's perfectly valid + +class Issue642(object): + attr = 0 + def __str__(self): + return "{self.attr}".format(self=self) + +# These should not emit anything regarding the number of arguments, +# since they have something invalid. +from ala_bala_portocola import unknown + +# pylint: disable=not-a-mapping,not-an-iterable + +function_1_arg(*unknown) +function_1_arg(1, *2) +function_1_arg(1, 2, 3, **unknown) +function_1_arg(4, 5, **1) +function_1_arg(5, 6, **{unknown: 1}) +function_1_arg(**{object: 1}) +function_1_arg(**{1: 2}) + +# Don't emit no-value-for-parameter for this, since we +# don't have the context at our disposal. +def expect_three(one, two, three): + return one + two + three + + +def no_context(*args): + expect_three(*args) diff --git a/pymode/libs/pylint/test/functional/arguments.txt b/pymode/libs/pylint/test/functional/arguments.txt new file mode 100644 index 00000000..c34f2051 --- /dev/null +++ b/pymode/libs/pylint/test/functional/arguments.txt @@ -0,0 +1,26 @@ +no-value-for-parameter:46::No value for argument 'first_argument' in function call +too-many-function-args:47::Too many positional arguments for function call +no-value-for-parameter:49::No value for argument 'third_argument' in function call +no-value-for-parameter:51::No value for argument 'first_argument' in function call +no-value-for-parameter:51::No value for argument 'second_argument' in function call +no-value-for-parameter:51::No value for argument 'third_argument' in function call +too-many-function-args:53::Too many positional arguments for function call +no-value-for-parameter:58::No value for argument 'first_argument' in function call +unexpected-keyword-arg:58::Unexpected keyword argument 'bob' in function call +unexpected-keyword-arg:59::Unexpected keyword argument 'coin' in function call +redundant-keyword-arg:61::Argument 'one' passed by position and keyword in function call +no-value-for-parameter:66::No value for argument 'arg' in lambda call +no-value-for-parameter:71:method_tests:No value for argument 'arg' in staticmethod call +no-value-for-parameter:72:method_tests:No value for argument 'arg' in staticmethod call +no-value-for-parameter:74:method_tests:No value for argument 'arg' in classmethod call +no-value-for-parameter:75:method_tests:No value for argument 'arg' in classmethod call +no-value-for-parameter:77:method_tests:No value for argument 'arg' in method call +no-value-for-parameter:78:method_tests:No value for argument 'arg' in unbound method call +no-value-for-parameter:80:method_tests:No value for argument 'arg' in method call +no-value-for-parameter:81:method_tests:No value for argument 'arg' in unbound method call +no-value-for-parameter:110:TestStaticMethod.func:No value for argument 'first' in staticmethod call +too-many-function-args:111:TestStaticMethod.func:Too many positional arguments for staticmethod call +unexpected-keyword-arg:122:TypeCheckConstructor.test:Unexpected keyword argument 'lala' in constructor call +no-value-for-parameter:133:Test.test:No value for argument 'icon' in function call +too-many-function-args:134:Test.test:Too many positional arguments for function call +no-value-for-parameter:136::No value for argument 'icon' in function call \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/arguments_differ.py b/pymode/libs/pylint/test/functional/arguments_differ.py new file mode 100644 index 00000000..d4819043 --- /dev/null +++ b/pymode/libs/pylint/test/functional/arguments_differ.py @@ -0,0 +1,138 @@ +"""Test that we are emitting arguments-differ when the arguments are different.""" +# pylint: disable=missing-docstring, too-few-public-methods + +class Parent(object): + + def test(self): + pass + + +class Child(Parent): + + def test(self, arg): # [arguments-differ] + pass + + +class ParentDefaults(object): + + def test(self, arg=None, barg=None): + pass + +class ChildDefaults(ParentDefaults): + + def test(self, arg=None): # [arguments-differ] + pass + + +class Classmethod(object): + + @classmethod + def func(cls, data): + return data + + @classmethod + def func1(cls): + return cls + + +class ClassmethodChild(Classmethod): + + @staticmethod + def func(): # [arguments-differ] + pass + + @classmethod + def func1(cls): + return cls() + + +class Builtins(dict): + """Ignore for builtins, for which we don't know the number of required args.""" + + @classmethod + def fromkeys(cls, arg, arg1): + pass + + +class Varargs(object): + + def test(self, arg, **kwargs): + pass + +class VarargsChild(Varargs): + + def test(self, arg): + pass + + +class Super(object): + def __init__(self): + pass + + def __private(self): + pass + + def __private2_(self): + pass + + def ___private3(self): + pass + + def method(self, param): + raise NotImplementedError + + +class Sub(Super): + + # pylint: disable=unused-argument + def __init__(self, arg): + super(Sub, self).__init__() + + def __private(self, arg): + pass + + def __private2_(self, arg): + pass + + def ___private3(self, arg): + pass + + def method(self, param='abc'): + pass + + +class Staticmethod(object): + + @staticmethod + def func(data): + return data + + +class StaticmethodChild(Staticmethod): + + @classmethod + def func(cls, data): + return data + + +class Property(object): + + @property + def close(self): + pass + +class PropertySetter(Property): + + @property + def close(self): + pass + + @close.setter + def close(self, attr): + return attr + + +class StaticmethodChild2(Staticmethod): + + def func(self, arg): + super(StaticmethodChild2, self).func(arg) diff --git a/pymode/libs/pylint/test/functional/arguments_differ.txt b/pymode/libs/pylint/test/functional/arguments_differ.txt new file mode 100644 index 00000000..cec3359b --- /dev/null +++ b/pymode/libs/pylint/test/functional/arguments_differ.txt @@ -0,0 +1,3 @@ +arguments-differ:12:Child.test:Arguments number differs from overridden 'test' method +arguments-differ:23:ChildDefaults.test:Arguments number differs from overridden 'test' method +arguments-differ:41:ClassmethodChild.func:Arguments number differs from overridden 'func' method \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/assert_on_tuple.py b/pymode/libs/pylint/test/functional/assert_on_tuple.py new file mode 100644 index 00000000..a612aa47 --- /dev/null +++ b/pymode/libs/pylint/test/functional/assert_on_tuple.py @@ -0,0 +1,11 @@ +'''Assert check example''' + +# pylint: disable=misplaced-comparison-constant +assert (1 == 1, 2 == 2), "no error" +assert (1 == 1, 2 == 2) # [assert-on-tuple] +assert 1 == 1, "no error" +assert (1 == 1, ), "no error" +assert (1 == 1, ) +assert (1 == 1, 2 == 2, 3 == 5), "no error" +assert () +assert (True, 'error msg') # [assert-on-tuple] diff --git a/pymode/libs/pylint/test/functional/assert_on_tuple.txt b/pymode/libs/pylint/test/functional/assert_on_tuple.txt new file mode 100644 index 00000000..52d1ca5d --- /dev/null +++ b/pymode/libs/pylint/test/functional/assert_on_tuple.txt @@ -0,0 +1,2 @@ +assert-on-tuple:5::Assert called on a 2-uple. Did you mean 'assert x,y'? +assert-on-tuple:11::Assert called on a 2-uple. Did you mean 'assert x,y'? diff --git a/pymode/libs/pylint/test/functional/assigning_non_slot.py b/pymode/libs/pylint/test/functional/assigning_non_slot.py new file mode 100644 index 00000000..7163ac9a --- /dev/null +++ b/pymode/libs/pylint/test/functional/assigning_non_slot.py @@ -0,0 +1,133 @@ +""" Checks assigning attributes not found in class slots +will trigger assigning-non-slot warning. +""" +# pylint: disable=too-few-public-methods, no-init, missing-docstring, no-absolute-import, import-error +from collections import deque + +from missing import Unknown + +class Empty(object): + """ empty """ + +class Bad(object): + """ missing not in slots. """ + + __slots__ = ['member'] + + def __init__(self): + self.missing = 42 # [assigning-non-slot] + +class Bad2(object): + """ missing not in slots """ + __slots__ = [deque.__name__, 'member'] + + def __init__(self): + self.deque = 42 + self.missing = 42 # [assigning-non-slot] + +class Bad3(Bad): + """ missing not found in slots """ + + __slots__ = ['component'] + + def __init__(self): + self.component = 42 + self.member = 24 + self.missing = 42 # [assigning-non-slot] + super(Bad3, self).__init__() + +class Good(Empty): + """ missing not in slots, but Empty doesn't + specify __slots__. + """ + __slots__ = ['a'] + + def __init__(self): + self.missing = 42 + +class Good2(object): + """ Using __dict__ in slots will be safe. """ + + __slots__ = ['__dict__', 'comp'] + + def __init__(self): + self.comp = 4 + self.missing = 5 + +class PropertyGood(object): + """ Using properties is safe. """ + + __slots__ = ['tmp', '_value'] + + @property + def test(self): + return self._value + + @test.setter + def test(self, value): + # pylint: disable=attribute-defined-outside-init + self._value = value + + def __init__(self): + self.test = 42 + +class PropertyGood2(object): + """ Using properties in the body of the class is safe. """ + __slots__ = ['_value'] + + def _getter(self): + return self._value + + def _setter(self, value): + # pylint: disable=attribute-defined-outside-init + self._value = value + + test = property(_getter, _setter) + + def __init__(self): + self.test = 24 + +class UnicodeSlots(object): + """Using unicode objects in __slots__ is okay. + + On Python 3.3 onward, u'' is equivalent to '', + so this test should be safe for both versions. + """ + __slots__ = (u'first', u'second') + + def __init__(self): + self.first = 42 + self.second = 24 + + +class DataDescriptor(object): + def __init__(self, name, default=''): + self.__name = name + self.__default = default + + def __get__(self, inst, cls): + return getattr(inst, self.__name, self.__default) + + def __set__(self, inst, value): + setattr(inst, self.__name, value) + + +class NonDataDescriptor(object): + def __get__(self, inst, cls): + return 42 + + +class SlotsWithDescriptor(object): + __slots__ = ['_err'] + data_descriptor = DataDescriptor('_err') + non_data_descriptor = NonDataDescriptor() + missing_descriptor = Unknown() + + +def dont_emit_for_descriptors(): + inst = SlotsWithDescriptor() + # This should not emit, because attr is + # a data descriptor + inst.data_descriptor = 'foo' + inst.non_data_descriptor = 'lala' # [assigning-non-slot] + return diff --git a/pymode/libs/pylint/test/functional/assigning_non_slot.txt b/pymode/libs/pylint/test/functional/assigning_non_slot.txt new file mode 100644 index 00000000..e1b20a66 --- /dev/null +++ b/pymode/libs/pylint/test/functional/assigning_non_slot.txt @@ -0,0 +1,4 @@ +assigning-non-slot:18:Bad.__init__:Assigning to attribute 'missing' not defined in class slots +assigning-non-slot:26:Bad2.__init__:Assigning to attribute 'missing' not defined in class slots +assigning-non-slot:36:Bad3.__init__:Assigning to attribute 'missing' not defined in class slots +assigning-non-slot:132:dont_emit_for_descriptors:Assigning to attribute 'non_data_descriptor' not defined in class slots \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/async_functions.py b/pymode/libs/pylint/test/functional/async_functions.py new file mode 100644 index 00000000..4819cb23 --- /dev/null +++ b/pymode/libs/pylint/test/functional/async_functions.py @@ -0,0 +1,65 @@ +"""Check that Python 3.5's async functions are properly analyzed by Pylint.""" +# pylint: disable=missing-docstring,invalid-name,too-few-public-methods +# pylint: disable=using-constant-test + +async def next(): # [redefined-builtin] + pass + +async def some_function(arg1, arg2): # [unused-argument] + await arg1 + + +class OtherClass(object): + + @staticmethod + def test(): + return 42 + + +class Class(object): + + async def some_method(self): + super(OtherClass, self).test() # [bad-super-call] + + +# +1: [too-many-arguments,too-many-return-statements, too-many-branches] +async def complex_function(this, function, has, more, arguments, than, + one, _, should, have): + if 1: + return this + elif 1: + return function + elif 1: + return has + elif 1: + return more + elif 1: + return arguments + elif 1: + return than + try: + return one + finally: + pass + if 2: + return should + while True: + pass + if 1: + return have + elif 2: + return function + elif 3: + pass + + +# +1: [duplicate-argument-name,dangerous-default-value] +async def func(a, a, b=[]): + return a, b + + +# +1: [empty-docstring, blacklisted-name] +async def foo(): + "" + + \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/async_functions.rc b/pymode/libs/pylint/test/functional/async_functions.rc new file mode 100644 index 00000000..03004f2c --- /dev/null +++ b/pymode/libs/pylint/test/functional/async_functions.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.5 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/async_functions.txt b/pymode/libs/pylint/test/functional/async_functions.txt new file mode 100644 index 00000000..94a66783 --- /dev/null +++ b/pymode/libs/pylint/test/functional/async_functions.txt @@ -0,0 +1,10 @@ +redefined-builtin:5:next:"Redefining built-in 'next'" +unused-argument:8:some_function:"Unused argument 'arg2'" +bad-super-call:22:Class.some_method:"Bad first argument 'OtherClass' given to super()" +too-many-arguments:26:complex_function:Too many arguments (10/5) +too-many-branches:26:complex_function:Too many branches (13/12) +too-many-return-statements:26:complex_function:Too many return statements (10/6) +dangerous-default-value:57:func:Dangerous default value [] as argument +duplicate-argument-name:57:func:Duplicate argument name a in function definition +blacklisted-name:62:foo:Black listed name "foo" +empty-docstring:62:foo:Empty function docstring \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/attribute_defined_outside_init.py b/pymode/libs/pylint/test/functional/attribute_defined_outside_init.py new file mode 100644 index 00000000..912bc459 --- /dev/null +++ b/pymode/libs/pylint/test/functional/attribute_defined_outside_init.py @@ -0,0 +1,62 @@ +# pylint: disable=missing-docstring,too-few-public-methods,invalid-name + +class A(object): + + def __init__(self): + self.x = 0 + self.setUp() + + def set_y(self, y): + self.y = y + + def set_x(self, x): + self.x = x + + def set_z(self, z): + self.z = z # [attribute-defined-outside-init] + + def setUp(self): + self.x = 0 + self.y = 0 + + +class B(A): + + def test(self): + self.z = 44 # [attribute-defined-outside-init] + + +class C(object): + + def __init__(self): + self._init() + + def _init(self): + self.z = 44 + + +class D(object): + + def setUp(self): + self.set_z() + + def set_z(self): + self.z = 42 + + +class E(object): + + def __init__(self): + i = self._init + i() + + def _init(self): + self.z = 44 + + +class Mixin(object): + + def test_mixin(self): + """Don't emit attribute-defined-outside-init for mixin classes.""" + if self.defined_already: # pylint: disable=access-member-before-definition + self.defined_already = None diff --git a/pymode/libs/pylint/test/functional/attribute_defined_outside_init.txt b/pymode/libs/pylint/test/functional/attribute_defined_outside_init.txt new file mode 100644 index 00000000..51456e30 --- /dev/null +++ b/pymode/libs/pylint/test/functional/attribute_defined_outside_init.txt @@ -0,0 +1,2 @@ +attribute-defined-outside-init:16:A.set_z:Attribute 'z' defined outside __init__ +attribute-defined-outside-init:26:B.test:Attribute 'z' defined outside __init__ diff --git a/pymode/libs/pylint/test/functional/bad_builtin.py b/pymode/libs/pylint/test/functional/bad_builtin.py new file mode 100644 index 00000000..1ca9790f --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_builtin.py @@ -0,0 +1,4 @@ +# pylint: disable=missing-docstring + +TEST = map(str, (1, 2, 3)) # [bad-builtin] +TEST1 = filter(str, (1, 2, 3)) # [bad-builtin] diff --git a/pymode/libs/pylint/test/functional/bad_builtin.txt b/pymode/libs/pylint/test/functional/bad_builtin.txt new file mode 100644 index 00000000..552f1962 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_builtin.txt @@ -0,0 +1,2 @@ +bad-builtin:3::Used builtin function 'map'. Using a list comprehension can be clearer. +bad-builtin:4::Used builtin function 'filter'. Using a list comprehension can be clearer. diff --git a/pymode/libs/pylint/test/functional/bad_continuation.py b/pymode/libs/pylint/test/functional/bad_continuation.py new file mode 100644 index 00000000..c0918a64 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_continuation.py @@ -0,0 +1,191 @@ +"""Regression test case for bad-continuation.""" +# pylint: disable=print-statement,using-constant-test, redefined-variable-type +# Various alignment for brackets +from __future__ import print_function + +LIST0 = [ + 1, 2, 3 +] +LIST1 = [ + 1, 2, 3 + ] +LIST2 = [ + 1, 2, 3 + ] # [bad-continuation] + +# Alignment inside literals +W0 = [1, 2, 3, + 4, 5, 6, + 7, # [bad-continuation] + 8, 9, 10, + 11, 12, 13, + # and a comment + 14, 15, 16] + +W1 = { + 'a': 1, + 'b': 2, # [bad-continuation] + 'c': 3, + } + +W2 = { + 'a': 1, + 'b': 2, # [bad-continuation] + 'c': 3, + } + +W2 = ['some', 'contents' # with a continued comment that may be aligned + # under the previous comment (optionally) + 'and', + 'more', # but this + # [bad-continuation] is not accepted + 'contents', # [bad-continuation] nor this. + ] + +# Values in dictionaries should be indented 4 spaces further if they are on a +# different line than their key +W4 = { + 'key1': + 'value1', # Grandfather in the old style + 'key2': + 'value2', # [bad-continuation] + 'key3': + 'value3', # Comma here + } + +# And should follow the same rules as continuations within parens +W5 = { + 'key1': 'long value' + 'long continuation', + 'key2': 'breaking' + 'wrong', # [bad-continuation] + 'key3': 2*( + 2+2), + 'key4': ('parenthesis', + 'continuation') # No comma here + } + +# Allow values to line up with their keys when the key is next to the brace +W6 = {'key1': + 'value1', + 'key2': + 'value2', + } + +# Or allow them to be indented +W7 = {'key1': + 'value1', + 'key2': + 'value2' + } + +# Bug that caused a warning on the previous two cases permitted these odd +# incorrect indentations +W8 = {'key1': +'value1', # [bad-continuation] + } + +W9 = {'key1': + 'value1', # [bad-continuation] + } + +# Alignment of arguments in function definitions +def continue1(some_arg, + some_other_arg): + """A function with well-aligned arguments.""" + print(some_arg, some_other_arg) + + +def continue2( + some_arg, + some_other_arg): + """A function with well-aligned arguments.""" + print(some_arg, some_other_arg) + +def continue3( + some_arg, # [bad-continuation] + some_other_arg): # [bad-continuation] + """A function with misaligned arguments""" + print(some_arg, some_other_arg) + +def continue4( # pylint:disable=missing-docstring + arg1, + arg2): print(arg1, arg2) + + +def callee(*args): + """noop""" + print(args) + + +callee( + "a", + "b" + ) + +callee("a", + "b") # [bad-continuation] + +callee(5, {'a': 'b', + 'c': 'd'}) + +if ( + 1 + ): pass + +if ( + 1 +): pass +if ( + 1 + ): pass # [bad-continuation] + +if (1 and + 2): # [bad-continuation] + pass + +while (1 and + 2): + pass + +while (1 and + 2 and # [bad-continuation] + 3): + pass + +if ( + 2): pass # [bad-continuation] + +if (1 or + 2 or + 3): pass + +if (1 or + 2 or # [bad-continuation] + 3): print(1, 2) + +if (1 and + 2): pass # [bad-continuation] + +if ( + 2): pass + +if ( + 2): # [bad-continuation] + pass + +L1 = (lambda a, + b: a + b) + +if not (1 and + 2): + print(3) + +if not (1 and + 2): # [bad-continuation] + print(3) + +continue2("foo", + some_other_arg="this " + "is " + "fine") diff --git a/pymode/libs/pylint/test/functional/bad_continuation.txt b/pymode/libs/pylint/test/functional/bad_continuation.txt new file mode 100644 index 00000000..672f0afd --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_continuation.txt @@ -0,0 +1,63 @@ +bad-continuation:14::"Wrong hanging indentation. + ] # [bad-continuation] +| ^|" +bad-continuation:19::"Wrong continued indentation (remove 3 spaces). + 7, # [bad-continuation] + | ^" +bad-continuation:27::"Wrong hanging indentation (add 1 space). + 'b': 2, # [bad-continuation] + ^|" +bad-continuation:33::"Wrong hanging indentation (add 1 space). + 'b': 2, # [bad-continuation] + ^|" +bad-continuation:41::"Wrong continued indentation. + # [bad-continuation] is not accepted + | | ^" +bad-continuation:42::"Wrong continued indentation (remove 20 spaces). + 'contents', # [bad-continuation] nor this. + | ^" +bad-continuation:51::"Wrong hanging indentation in dict value. + 'value2', # [bad-continuation] + | ^ |" +bad-continuation:61::"Wrong continued indentation (add 4 spaces). + 'wrong', # [bad-continuation] + ^ |" +bad-continuation:85::"Wrong hanging indentation in dict value. +'value1', # [bad-continuation] +^ | |" +bad-continuation:89::"Wrong hanging indentation in dict value. + 'value1', # [bad-continuation] + ^ | |" +bad-continuation:106::"Wrong hanging indentation before block (add 4 spaces). + some_arg, # [bad-continuation] + ^ |" +bad-continuation:107::"Wrong hanging indentation before block (add 4 spaces). + some_other_arg): # [bad-continuation] + ^ |" +bad-continuation:127::"Wrong continued indentation (add 3 spaces). + ""b"") # [bad-continuation] + ^ |" +bad-continuation:141::"Wrong hanging indentation before block. + ): pass # [bad-continuation] +| ^|" +bad-continuation:144::"Wrong continued indentation before block (add 4 spaces). + 2): # [bad-continuation] + ^ |" +bad-continuation:152::"Wrong continued indentation (remove 2 spaces). + 2 and # [bad-continuation] + | ^" +bad-continuation:157::"Wrong hanging indentation before block. + 2): pass # [bad-continuation] + ^ | |" +bad-continuation:164::"Wrong continued indentation before block. + 2 or # [bad-continuation] + |^ |" +bad-continuation:168::"Wrong continued indentation before block. + 2): pass # [bad-continuation] + ^ | |" +bad-continuation:174::"Wrong hanging indentation before block. + 2): # [bad-continuation] + ^ | |" +bad-continuation:185::"Wrong continued indentation (add 4 spaces). + 2): # [bad-continuation] + ^ |" diff --git a/pymode/libs/pylint/test/functional/bad_exception_context.py b/pymode/libs/pylint/test/functional/bad_exception_context.py new file mode 100644 index 00000000..af3e4fb9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_exception_context.py @@ -0,0 +1,24 @@ +"""Check that raise ... from .. uses a proper exception context """ + +# pylint: disable=unreachable, import-error, multiple-imports + +import socket, unknown + +__revision__ = 0 + +class ExceptionSubclass(Exception): + """ subclass """ + +def test(): + """ docstring """ + raise IndexError from 1 # [bad-exception-context] + raise IndexError from None + raise IndexError from ZeroDivisionError + raise IndexError from object() # [bad-exception-context] + raise IndexError from ExceptionSubclass + raise IndexError from socket.error + raise IndexError() from None + raise IndexError() from ZeroDivisionError + raise IndexError() from ZeroDivisionError() + raise IndexError() from object() # [bad-exception-context] + raise IndexError() from unknown diff --git a/pymode/libs/pylint/test/functional/bad_exception_context.rc b/pymode/libs/pylint/test/functional/bad_exception_context.rc new file mode 100644 index 00000000..8c6eb56c --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_exception_context.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/bad_exception_context.txt b/pymode/libs/pylint/test/functional/bad_exception_context.txt new file mode 100644 index 00000000..f403a39b --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_exception_context.txt @@ -0,0 +1,3 @@ +bad-exception-context:14:test:Exception context set to something which is not an exception, nor None +bad-exception-context:17:test:Exception context set to something which is not an exception, nor None +bad-exception-context:23:test:Exception context set to something which is not an exception, nor None \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/bad_indentation.py b/pymode/libs/pylint/test/functional/bad_indentation.py new file mode 100644 index 00000000..c868aedc --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_indentation.py @@ -0,0 +1,20 @@ +# pylint: disable=missing-docstring, pointless-statement +from __future__ import print_function + + +def totoo(): + print('malindented') # [bad-indentation] + +def tutuu(): + print('good indentation') + +def titii(): + 1 # and this. # [bad-indentation] + +def tataa(kdict): + for key in ['1', '2', '3']: + key = key.lower() + + if key in kdict: + del kdict[key] + diff --git a/pymode/libs/pylint/test/functional/bad_indentation.txt b/pymode/libs/pylint/test/functional/bad_indentation.txt new file mode 100644 index 00000000..c5c00bb1 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_indentation.txt @@ -0,0 +1,2 @@ +bad-indentation:6::Bad indentation. Found 1 spaces, expected 4 +bad-indentation:12::Bad indentation. Found 5 spaces, expected 4 diff --git a/pymode/libs/pylint/test/functional/bad_inline_option.py b/pymode/libs/pylint/test/functional/bad_inline_option.py new file mode 100644 index 00000000..be6e2408 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_inline_option.py @@ -0,0 +1,5 @@ +"""errors-only is not usable as an inline option""" +# +1: [bad-inline-option] +# pylint: errors-only + +CONST = "This is not a pylint: inline option." diff --git a/pymode/libs/pylint/test/functional/bad_inline_option.rc b/pymode/libs/pylint/test/functional/bad_inline_option.rc new file mode 100644 index 00000000..b211d725 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_inline_option.rc @@ -0,0 +1,2 @@ +[Messages Control] +enable=I diff --git a/pymode/libs/pylint/test/functional/bad_inline_option.txt b/pymode/libs/pylint/test/functional/bad_inline_option.txt new file mode 100644 index 00000000..91ac1af3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_inline_option.txt @@ -0,0 +1 @@ +bad-inline-option:3::Unable to consider inline option 'errors-only' diff --git a/pymode/libs/pylint/test/functional/bad_open_mode.py b/pymode/libs/pylint/test/functional/bad_open_mode.py new file mode 100644 index 00000000..6b749de0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_open_mode.py @@ -0,0 +1,37 @@ +"""Warnings for using open() with an invalid mode string.""" + +open('foo.bar', 'w', 2) +open('foo.bar', 'rw') # [bad-open-mode] +open(name='foo.bar', buffering=10, mode='rw') # [bad-open-mode] +open(mode='rw', name='foo.bar') # [bad-open-mode] +open('foo.bar', 'U+') +open('foo.bar', 'rb+') +open('foo.bar', 'Uw') # [bad-open-mode] +open('foo.bar', 2) # [bad-open-mode] +open('foo.bar', buffering=2) +WRITE_MODE = 'w' +open('foo.bar', 'U' + WRITE_MODE + 'z') # [bad-open-mode] +open('foo.bar', 'br') # [bad-open-mode] +open('foo.bar', 'wU') # [bad-open-mode] +open('foo.bar', 'r+b') +open('foo.bar', 'r+') +open('foo.bar', 'w+') +open('foo.bar', 'xb') # [bad-open-mode] +open('foo.bar', 'rx') # [bad-open-mode] +open('foo.bar', 'Ur') +open('foo.bar', 'rU') +open('foo.bar', 'rUb') +open('foo.bar', 'rUb+') +open('foo.bar', 'rU+b') +open('foo.bar', 'r+Ub') +open('foo.bar', '+rUb') # [bad-open-mode] +open('foo.bar', 'ab+') +open('foo.bar', 'a+b') +open('foo.bar', 'aU') # [bad-open-mode] +open('foo.bar', 'U+b') +open('foo.bar', '+Ub') +open('foo.bar', 'b+U') +open('foo.bar', 'Urb+') +open('foo.bar', 'Ur+b') +open('foo.bar', 'Ubr') # [bad-open-mode] +open('foo.bar', 'Ut') # [bad-open-mode] diff --git a/pymode/libs/pylint/test/functional/bad_open_mode.rc b/pymode/libs/pylint/test/functional/bad_open_mode.rc new file mode 100644 index 00000000..b9ab9770 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_open_mode.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/bad_open_mode.txt b/pymode/libs/pylint/test/functional/bad_open_mode.txt new file mode 100644 index 00000000..b0222863 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_open_mode.txt @@ -0,0 +1,14 @@ +bad-open-mode:4::"""rw"" is not a valid mode for open." +bad-open-mode:5::"""rw"" is not a valid mode for open." +bad-open-mode:6::"""rw"" is not a valid mode for open." +bad-open-mode:9::"""Uw"" is not a valid mode for open." +bad-open-mode:10::"""2"" is not a valid mode for open." +bad-open-mode:13::"""Uwz"" is not a valid mode for open." +bad-open-mode:14::"""br"" is not a valid mode for open." +bad-open-mode:15::"""wU"" is not a valid mode for open." +bad-open-mode:19::"""xb"" is not a valid mode for open." +bad-open-mode:20::"""rx"" is not a valid mode for open." +bad-open-mode:27::"""+rUb"" is not a valid mode for open." +bad-open-mode:30::"""aU"" is not a valid mode for open." +bad-open-mode:36::"""Ubr"" is not a valid mode for open." +bad-open-mode:37::"""Ut"" is not a valid mode for open." diff --git a/pymode/libs/pylint/test/functional/bad_open_mode_py3.py b/pymode/libs/pylint/test/functional/bad_open_mode_py3.py new file mode 100644 index 00000000..0812f5a2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_open_mode_py3.py @@ -0,0 +1,23 @@ +"""Warnings for using open() with an invalid mode string.""" + +NAME = "foo.bar" +open(NAME, "wb") +open(NAME, "w") +open(NAME, "rb") +open(NAME, "x") +open(NAME, "br") +open(NAME, "+r") +open(NAME, "xb") +open(NAME, "rwx") # [bad-open-mode] +open(NAME, "rr") # [bad-open-mode] +open(NAME, "+") # [bad-open-mode] +open(NAME, "xw") # [bad-open-mode] +open(NAME, "ab+") +open(NAME, "a+b") +open(NAME, "+ab") +open(NAME, "+rUb") +open(NAME, "x+b") +open(NAME, "Ua") # [bad-open-mode] +open(NAME, "Ur++") # [bad-open-mode] +open(NAME, "Ut") +open(NAME, "Ubr") diff --git a/pymode/libs/pylint/test/functional/bad_open_mode_py3.rc b/pymode/libs/pylint/test/functional/bad_open_mode_py3.rc new file mode 100644 index 00000000..c4033f84 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_open_mode_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/bad_open_mode_py3.txt b/pymode/libs/pylint/test/functional/bad_open_mode_py3.txt new file mode 100644 index 00000000..0be0ea84 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_open_mode_py3.txt @@ -0,0 +1,6 @@ +bad-open-mode:11::"""rwx"" is not a valid mode for open." +bad-open-mode:12::"""rr"" is not a valid mode for open." +bad-open-mode:13::"""+"" is not a valid mode for open." +bad-open-mode:14::"""xw"" is not a valid mode for open." +bad-open-mode:20::"""Ua"" is not a valid mode for open." +bad-open-mode:21::"""Ur++"" is not a valid mode for open." diff --git a/pymode/libs/pylint/test/functional/bad_reversed_sequence.py b/pymode/libs/pylint/test/functional/bad_reversed_sequence.py new file mode 100644 index 00000000..6deb7957 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_reversed_sequence.py @@ -0,0 +1,71 @@ +""" Checks that reversed() receive proper argument """ +# pylint: disable=missing-docstring +# pylint: disable=too-few-public-methods,no-self-use,no-absolute-import +from collections import deque + +__revision__ = 0 + +class GoodReversed(object): + """ Implements __reversed__ """ + def __reversed__(self): + return [1, 2, 3] + +class SecondGoodReversed(object): + """ Implements __len__ and __getitem__ """ + def __len__(self): + return 3 + + def __getitem__(self, index): + return index + +class BadReversed(object): + """ implements only len() """ + def __len__(self): + return 3 + +class SecondBadReversed(object): + """ implements only __getitem__ """ + def __getitem__(self, index): + return index + +class ThirdBadReversed(dict): + """ dict subclass """ + +def uninferable(seq): + """ This can't be infered at this moment, + make sure we don't have a false positive. + """ + return reversed(seq) + +def test(path): + """ test function """ + seq = reversed() # No argument given + seq = reversed(None) # [bad-reversed-sequence] + seq = reversed([1, 2, 3]) + seq = reversed((1, 2, 3)) + seq = reversed(set()) # [bad-reversed-sequence] + seq = reversed({'a': 1, 'b': 2}) # [bad-reversed-sequence] + seq = reversed(iter([1, 2, 3])) # [bad-reversed-sequence] + seq = reversed(GoodReversed()) + seq = reversed(SecondGoodReversed()) + seq = reversed(BadReversed()) # [bad-reversed-sequence] + seq = reversed(SecondBadReversed()) # [bad-reversed-sequence] + seq = reversed(range(100)) + seq = reversed(ThirdBadReversed()) # [bad-reversed-sequence] + seq = reversed(lambda: None) # [bad-reversed-sequence] + seq = reversed(deque([])) + seq = reversed("123") + seq = uninferable([1, 2, 3]) + seq = reversed(path.split("/")) + return seq + +def test_dict_ancestor_and_reversed(): + """Don't emit for subclasses of dict, with __reversed__ implemented.""" + from collections import OrderedDict + + class Child(dict): + def __reversed__(self): + return reversed(range(10)) + + seq = reversed(OrderedDict()) + return reversed(Child()), seq diff --git a/pymode/libs/pylint/test/functional/bad_reversed_sequence.txt b/pymode/libs/pylint/test/functional/bad_reversed_sequence.txt new file mode 100644 index 00000000..dd0c6f96 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_reversed_sequence.txt @@ -0,0 +1,8 @@ +bad-reversed-sequence:43:test:The first reversed() argument is not a sequence +bad-reversed-sequence:46:test:The first reversed() argument is not a sequence +bad-reversed-sequence:47:test:The first reversed() argument is not a sequence +bad-reversed-sequence:48:test:The first reversed() argument is not a sequence +bad-reversed-sequence:51:test:The first reversed() argument is not a sequence +bad-reversed-sequence:52:test:The first reversed() argument is not a sequence +bad-reversed-sequence:54:test:The first reversed() argument is not a sequence +bad-reversed-sequence:55:test:The first reversed() argument is not a sequence diff --git a/pymode/libs/pylint/test/functional/bad_staticmethod_argument.py b/pymode/libs/pylint/test/functional/bad_staticmethod_argument.py new file mode 100644 index 00000000..a71a40e5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_staticmethod_argument.py @@ -0,0 +1,16 @@ +# pylint: disable=missing-docstring, no-staticmethod-decorator + +class Abcd(object): + + def method1(self): # [bad-staticmethod-argument] + pass + + method1 = staticmethod(method1) + + def method2(cls): # [bad-staticmethod-argument] + pass + + method2 = staticmethod(method2) + + def __init__(self): + pass diff --git a/pymode/libs/pylint/test/functional/bad_staticmethod_argument.txt b/pymode/libs/pylint/test/functional/bad_staticmethod_argument.txt new file mode 100644 index 00000000..a9d5b468 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_staticmethod_argument.txt @@ -0,0 +1,2 @@ +bad-staticmethod-argument:5:Abcd.method1:Static method with 'self' as first argument +bad-staticmethod-argument:10:Abcd.method2:Static method with 'cls' as first argument \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/bad_whitespace.py b/pymode/libs/pylint/test/functional/bad_whitespace.py new file mode 100644 index 00000000..fe22a21e --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_whitespace.py @@ -0,0 +1,3 @@ +# pylint: disable = invalid-name, missing-docstring, unused-variable, unused-argument +def function(hello): + x, y, z = (1,2,3,) # [bad-whitespace, bad-whitespace, missing-final-newline] \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/bad_whitespace.txt b/pymode/libs/pylint/test/functional/bad_whitespace.txt new file mode 100644 index 00000000..524d4d93 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bad_whitespace.txt @@ -0,0 +1,7 @@ +bad-whitespace:3::"Exactly one space required after comma + x, y, z = (1,2,3,) # [bad-whitespace, bad-whitespace, missing-final-newline] + ^" +bad-whitespace:3::"Exactly one space required after comma + x, y, z = (1,2,3,) # [bad-whitespace, bad-whitespace, missing-final-newline] + ^" +missing-final-newline:3::"Final newline missing" diff --git a/pymode/libs/pylint/test/functional/bare_except.py b/pymode/libs/pylint/test/functional/bare_except.py new file mode 100644 index 00000000..47a20792 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bare_except.py @@ -0,0 +1,6 @@ +# pylint: disable=missing-docstring, import-error + +try: + 1 + "2" +except: # [bare-except] + pass diff --git a/pymode/libs/pylint/test/functional/bare_except.txt b/pymode/libs/pylint/test/functional/bare_except.txt new file mode 100644 index 00000000..ccb7ba90 --- /dev/null +++ b/pymode/libs/pylint/test/functional/bare_except.txt @@ -0,0 +1 @@ +bare-except:5::No exception type(s) specified \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/blacklisted_name.py b/pymode/libs/pylint/test/functional/blacklisted_name.py new file mode 100644 index 00000000..b62c6d27 --- /dev/null +++ b/pymode/libs/pylint/test/functional/blacklisted_name.py @@ -0,0 +1,4 @@ +# pylint: disable=missing-docstring + +def baz(): # [blacklisted-name] + pass diff --git a/pymode/libs/pylint/test/functional/blacklisted_name.txt b/pymode/libs/pylint/test/functional/blacklisted_name.txt new file mode 100644 index 00000000..f0fc9c20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/blacklisted_name.txt @@ -0,0 +1 @@ +blacklisted-name:3:baz:Black listed name "baz" diff --git a/pymode/libs/pylint/test/functional/boolean_datetime.py b/pymode/libs/pylint/test/functional/boolean_datetime.py new file mode 100644 index 00000000..9a4a178d --- /dev/null +++ b/pymode/libs/pylint/test/functional/boolean_datetime.py @@ -0,0 +1,30 @@ +""" Checks for boolean uses of datetime.time. """ +# pylint: disable=superfluous-parens,print-statement,no-absolute-import +import datetime + +if datetime.time(0, 0, 0): # [boolean-datetime] + print("datetime.time(0,0,0) is not a bug!") +else: + print("datetime.time(0,0,0) is a bug!") + +if not datetime.time(0, 0, 1): # [boolean-datetime] + print("datetime.time(0,0,1) is not a bug!") +else: + print("datetime.time(0,0,1) is a bug!") + +DATA = not datetime.time(0, 0, 0) # [boolean-datetime] +DATA1 = True if datetime.time(0, 0, 0) else False # [boolean-datetime] +DATA2 = datetime.time(0, 0, 0) or True # [boolean-datetime] +DATA3 = datetime.time(0, 0, 0) and True # [boolean-datetime] +DATA4 = False or True or datetime.time(0, 0, 0) # [boolean-datetime] +DATA5 = False and datetime.time(0, 0, 0) or True # [boolean-datetime] + + +def cant_infer(data): + """ Can't infer what data is """ + hophop = not data + troptrop = True if data else False + toptop = data or True + return hophop, troptrop, toptop + +cant_infer(datetime.time(0, 0, 0)) diff --git a/pymode/libs/pylint/test/functional/boolean_datetime.rc b/pymode/libs/pylint/test/functional/boolean_datetime.rc new file mode 100644 index 00000000..abe8fdf1 --- /dev/null +++ b/pymode/libs/pylint/test/functional/boolean_datetime.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.5 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/boolean_datetime.txt b/pymode/libs/pylint/test/functional/boolean_datetime.txt new file mode 100644 index 00000000..f3287d4f --- /dev/null +++ b/pymode/libs/pylint/test/functional/boolean_datetime.txt @@ -0,0 +1,8 @@ +boolean-datetime:5::Using datetime.time in a boolean context. +boolean-datetime:10::Using datetime.time in a boolean context. +boolean-datetime:15::Using datetime.time in a boolean context. +boolean-datetime:16::Using datetime.time in a boolean context. +boolean-datetime:17::Using datetime.time in a boolean context. +boolean-datetime:18::Using datetime.time in a boolean context. +boolean-datetime:19::Using datetime.time in a boolean context. +boolean-datetime:20::Using datetime.time in a boolean context. diff --git a/pymode/libs/pylint/test/functional/cellvar_escaping_loop.py b/pymode/libs/pylint/test/functional/cellvar_escaping_loop.py new file mode 100644 index 00000000..a6d87a20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/cellvar_escaping_loop.py @@ -0,0 +1,130 @@ +# pylint: disable=print-statement +"""Tests for loopvar-in-closure.""" +from __future__ import print_function + +def good_case(): + """No problems here.""" + lst = [] + for i in range(10): + lst.append(i) + + +def good_case2(): + """No problems here.""" + return [i for i in range(10)] + + +def good_case3(): + """No problems here.""" + lst = [] + for i in range(10): # [unused-variable] + lst.append(lambda i=i: i) + + +def good_case4(): + """No problems here.""" + lst = [] + for i in range(10): + print(i) + lst.append(lambda i: i) + + +def good_case5(): + """No problems here.""" + return (i for i in range(10)) + + +def good_case6(): + """Accept use of the variable after the loop. + + There's already a warning about possibly undefined loop variables, and + the value will not change any more.""" + for i in range(10): + print(i) + return lambda: i # [undefined-loop-variable] + + +def good_case7(): + """Accept use of the variable inside return.""" + for i in range(10): + if i == 8: + return lambda: i + return lambda: -1 + + +def good_case8(): + """Lambda defined and called in loop.""" + for i in range(10): + print((lambda x: i + x)(1)) + + +def good_case9(): + """Another eager binding of the cell variable.""" + funs = [] + for i in range(10): + def func(bound_i=i): + """Ignore.""" + return bound_i + funs.append(func) + return funs + + +def bad_case(): + """Closing over a loop variable.""" + lst = [] + for i in range(10): + print(i) + lst.append(lambda: i) # [cell-var-from-loop] + + +def bad_case2(): + """Closing over a loop variable.""" + return [lambda: i for i in range(10)] # [cell-var-from-loop] + + +def bad_case3(): + """Closing over variable defined in loop.""" + lst = [] + for i in range(10): + j = i * i + lst.append(lambda: j) # [cell-var-from-loop] + return lst + + +def bad_case4(): + """Closing over variable defined in loop.""" + lst = [] + for i in range(10): + def nested(): + """Nested function.""" + return i**2 # [cell-var-from-loop] + lst.append(nested) + return lst + + +def bad_case5(): + """Problematic case. + + If this function is used as + + >>> [x() for x in bad_case5()] + + it behaves 'as expected', i.e. the result is range(10). + + If it's used with + + >>> lst = list(bad_case5()) + >>> [x() for x in lst] + + the result is [9] * 10 again. + """ + return (lambda: i for i in range(10)) # [cell-var-from-loop] + + +def bad_case6(): + """Closing over variable defined in loop.""" + lst = [] + for i, j in zip(range(10), range(10, 20)): + print(j) + lst.append(lambda: i) # [cell-var-from-loop] + return lst diff --git a/pymode/libs/pylint/test/functional/cellvar_escaping_loop.txt b/pymode/libs/pylint/test/functional/cellvar_escaping_loop.txt new file mode 100644 index 00000000..a34ed28d --- /dev/null +++ b/pymode/libs/pylint/test/functional/cellvar_escaping_loop.txt @@ -0,0 +1,8 @@ +unused-variable:20:good_case3:Unused variable 'i' +undefined-loop-variable:44:good_case6.:Using possibly undefined loop variable 'i' +cell-var-from-loop:77:bad_case.:Cell variable i defined in loop +cell-var-from-loop:82:bad_case2.:Cell variable i defined in loop +cell-var-from-loop:90:bad_case3.:Cell variable j defined in loop +cell-var-from-loop:100:bad_case4.nested:Cell variable i defined in loop +cell-var-from-loop:121:bad_case5.:Cell variable i defined in loop +cell-var-from-loop:129:bad_case6.:Cell variable i defined in loop diff --git a/pymode/libs/pylint/test/functional/class_members_py27.py b/pymode/libs/pylint/test/functional/class_members_py27.py new file mode 100644 index 00000000..dd85bcac --- /dev/null +++ b/pymode/libs/pylint/test/functional/class_members_py27.py @@ -0,0 +1,65 @@ +""" Various tests for class members access. """ +# pylint: disable=R0903,print-statement,no-absolute-import, metaclass-assignment,import-error,no-init,missing-docstring, wrong-import-order,wrong-import-position +from missing import Missing +class MyClass(object): + """class docstring""" + + def __init__(self): + """init""" + self.correct = 1 + + def test(self): + """test""" + self.correct += 2 + self.incorrect += 2 # [no-member] + del self.havenot # [no-member] + self.nonexistent1.truc() # [no-member] + self.nonexistent2[1] = 'hehe' # [no-member] + +class XYZMixin(object): + """access to undefined members should be ignored in mixin classes by + default + """ + def __init__(self): + print self.nonexistent + + +class NewClass(object): + """use object.__setattr__""" + def __init__(self): + self.__setattr__('toto', 'tutu') + +from abc import ABCMeta + +class TestMetaclass(object): + """ Test attribute access for metaclasses. """ + __metaclass__ = ABCMeta + +class Metaclass(type): + """ metaclass """ + @classmethod + def test(mcs): + """ classmethod """ + +class UsingMetaclass(object): + """ empty """ + __metaclass__ = Metaclass + +#TestMetaclass.register(int) +#UsingMetaclass.test() +TestMetaclass().register(int) # [no-member] +UsingMetaclass().test() # [no-member] + + +class NoKnownBases(Missing): + """Don't emit no-member if we don't know the bases of a class.""" + +NoKnownBases().lalala() + +# Should be enabled on astroid > 1.4.0 +#class MetaClass(object): +# """Look some methods in the implicit metaclass.""" +# +# @classmethod +# def whatever(cls): +# return cls.mro() + cls.missing() diff --git a/pymode/libs/pylint/test/functional/class_members_py27.rc b/pymode/libs/pylint/test/functional/class_members_py27.rc new file mode 100644 index 00000000..80170b77 --- /dev/null +++ b/pymode/libs/pylint/test/functional/class_members_py27.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=2.7 +max_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/class_members_py27.txt b/pymode/libs/pylint/test/functional/class_members_py27.txt new file mode 100644 index 00000000..e5e60052 --- /dev/null +++ b/pymode/libs/pylint/test/functional/class_members_py27.txt @@ -0,0 +1,6 @@ +no-member:14:MyClass.test:Instance of 'MyClass' has no 'incorrect' member:INFERENCE +no-member:15:MyClass.test:Instance of 'MyClass' has no 'havenot' member:INFERENCE +no-member:16:MyClass.test:Instance of 'MyClass' has no 'nonexistent1' member:INFERENCE +no-member:17:MyClass.test:Instance of 'MyClass' has no 'nonexistent2' member:INFERENCE +no-member:50::Instance of 'TestMetaclass' has no 'register' member:INFERENCE +no-member:51::Instance of 'UsingMetaclass' has no 'test' member:INFERENCE diff --git a/pymode/libs/pylint/test/functional/class_members_py30.py b/pymode/libs/pylint/test/functional/class_members_py30.py new file mode 100644 index 00000000..cb7526e7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/class_members_py30.py @@ -0,0 +1,63 @@ +""" Various tests for class members access. """ +# pylint: disable=R0903,import-error,no-init,missing-docstring, wrong-import-position,wrong-import-order +from missing import Missing +class MyClass(object): + """class docstring""" + + def __init__(self): + """init""" + self.correct = 1 + + def test(self): + """test""" + self.correct += 2 + self.incorrect += 2 # [no-member] + del self.havenot # [no-member] + self.nonexistent1.truc() # [no-member] + self.nonexistent2[1] = 'hehe' # [no-member] + +class XYZMixin(object): + """access to undefined members should be ignored in mixin classes by + default + """ + def __init__(self): + print(self.nonexistent) + + +class NewClass(object): + """use object.__setattr__""" + def __init__(self): + self.__setattr__('toto', 'tutu') + +from abc import ABCMeta + +class TestMetaclass(object, metaclass=ABCMeta): + """ Test attribute access for metaclasses. """ + +class Metaclass(type): + """ metaclass """ + @classmethod + def test(mcs): + """ classmethod """ + +class UsingMetaclass(object, metaclass=Metaclass): + """ empty """ + +# TestMetaclass.register(int) +# UsingMetaclass.test() +TestMetaclass().register(int) # [no-member] +UsingMetaclass().test() # [no-member] + + +class NoKnownBases(Missing): + """Don't emit no-member if we don't know the bases of a class.""" + +NoKnownBases().lalala() + +# Enabled on a more capable astroid (1.4.0) +# class MetaClass(object): +# """Look some methods in the implicit metaclass.""" +# +# @classmethod +# def whatever(cls): +# return cls.mro() + cls.missing() diff --git a/pymode/libs/pylint/test/functional/class_members_py30.rc b/pymode/libs/pylint/test/functional/class_members_py30.rc new file mode 100644 index 00000000..8c6eb56c --- /dev/null +++ b/pymode/libs/pylint/test/functional/class_members_py30.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/class_members_py30.txt b/pymode/libs/pylint/test/functional/class_members_py30.txt new file mode 100644 index 00000000..d9163ec7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/class_members_py30.txt @@ -0,0 +1,7 @@ +no-member:14:MyClass.test:Instance of 'MyClass' has no 'incorrect' member:INFERENCE +no-member:15:MyClass.test:Instance of 'MyClass' has no 'havenot' member:INFERENCE +no-member:16:MyClass.test:Instance of 'MyClass' has no 'nonexistent1' member:INFERENCE +no-member:17:MyClass.test:Instance of 'MyClass' has no 'nonexistent2' member:INFERENCE +no-member:48::Instance of 'TestMetaclass' has no 'register' member:INFERENCE +no-member:49::Instance of 'UsingMetaclass' has no 'test' member:INFERENCE +no-member:63:MetaClass.whatever:Class 'MetaClass' has no 'missing' member:INFERENCE \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/class_scope.py b/pymode/libs/pylint/test/functional/class_scope.py new file mode 100644 index 00000000..dfc8dd83 --- /dev/null +++ b/pymode/libs/pylint/test/functional/class_scope.py @@ -0,0 +1,22 @@ +# pylint: disable=R0903,W0232 +"""check for scope problems""" + +__revision__ = None + +class Well(object): + """well""" + attr = 42 + get_attr = lambda arg=attr: arg * 24 + # +1: [used-before-assignment] + get_attr_bad = lambda arg=revattr: revattr * 42 + revattr = 24 + bad_lambda = lambda: get_attr_bad # [undefined-variable] + + class Data(object): + """base hidden class""" + class Sub(Data): + """whaou, is Data found???""" + attr = Data() # [undefined-variable] + def func(self): + """check Sub is not defined here""" + return Sub(), self # [undefined-variable] diff --git a/pymode/libs/pylint/test/functional/class_scope.txt b/pymode/libs/pylint/test/functional/class_scope.txt new file mode 100644 index 00000000..ea6c45ab --- /dev/null +++ b/pymode/libs/pylint/test/functional/class_scope.txt @@ -0,0 +1,4 @@ +used-before-assignment:11:Well.:Using variable 'revattr' before assignment +undefined-variable:13:Well.:Undefined variable 'get_attr_bad' +undefined-variable:19:Well.Sub:Undefined variable 'Data' +undefined-variable:22:Well.func:Undefined variable 'Sub' diff --git a/pymode/libs/pylint/test/functional/confidence_filter.py b/pymode/libs/pylint/test/functional/confidence_filter.py new file mode 100644 index 00000000..b934c9d3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/confidence_filter.py @@ -0,0 +1,15 @@ +"""Test for the confidence filter.""" +from __future__ import print_function + +class Client(object): + """use provider class""" + + def __init__(self): + self.set_later = 0 + + def set_set_later(self, value): + """set set_later attribute (introduce an inference ambiguity)""" + self.set_later = value + +print(Client().set_later.lower()) +print(Client().foo) # [no-member] diff --git a/pymode/libs/pylint/test/functional/confidence_filter.rc b/pymode/libs/pylint/test/functional/confidence_filter.rc new file mode 100644 index 00000000..74953b68 --- /dev/null +++ b/pymode/libs/pylint/test/functional/confidence_filter.rc @@ -0,0 +1,3 @@ +[Messages Control] +disable=no-init,too-few-public-methods,undefined-variable,print-statement +confidence=INFERENCE,HIGH,UNDEFINED diff --git a/pymode/libs/pylint/test/functional/confidence_filter.txt b/pymode/libs/pylint/test/functional/confidence_filter.txt new file mode 100644 index 00000000..50a3767c --- /dev/null +++ b/pymode/libs/pylint/test/functional/confidence_filter.txt @@ -0,0 +1 @@ +no-member:15::Instance of 'Client' has no 'foo' member:INFERENCE diff --git a/pymode/libs/pylint/test/functional/confusing_with_statement.py b/pymode/libs/pylint/test/functional/confusing_with_statement.py new file mode 100644 index 00000000..1444a979 --- /dev/null +++ b/pymode/libs/pylint/test/functional/confusing_with_statement.py @@ -0,0 +1,27 @@ +# pylint: disable=undefined-variable,not-context-manager,missing-docstring + +# both context managers are named +with one as first, two as second: + pass + +# first matched as tuple; this is ok +with one as (first, second), third: + pass + +# the first context manager doesn't have as name +with one, two as second: + pass + +# the second is a function call; this is ok +with one as first, two(): + pass + +# nested with statements; make sure no message is emited +# this could be a false positive on Py2 +with one as first: + with two: + pass + +# ambiguous looking with statement +with one as first, two: # [confusing-with-statement] + pass diff --git a/pymode/libs/pylint/test/functional/confusing_with_statement.txt b/pymode/libs/pylint/test/functional/confusing_with_statement.txt new file mode 100644 index 00000000..750ad714 --- /dev/null +++ b/pymode/libs/pylint/test/functional/confusing_with_statement.txt @@ -0,0 +1 @@ +confusing-with-statement:26::Following "as" with another context manager looks like a tuple. diff --git a/pymode/libs/pylint/test/functional/consider_using_enumerate.py b/pymode/libs/pylint/test/functional/consider_using_enumerate.py new file mode 100644 index 00000000..f906da0f --- /dev/null +++ b/pymode/libs/pylint/test/functional/consider_using_enumerate.py @@ -0,0 +1,43 @@ +"""Emit a message for iteration through range and len is encountered.""" + +# pylint: disable=missing-docstring, import-error + +def bad(): + iterable = [1, 2, 3] + for obj in range(len(iterable)): # [consider-using-enumerate] + yield iterable[obj] + + +def good(): + iterable = other_obj = [1, 2, 3] + total = 0 + for obj in range(len(iterable)): + total += obj + yield total + yield iterable[obj + 1: 2] + yield iterable[len(obj)] + for obj in iterable: + yield iterable[obj - 1] + + for index, obj in enumerate(iterable): + yield iterable[index] + for index in range(0, 10): + yield iterable[index + 1] + for index in range(10): + yield iterable[index] + for index in range(len([1, 2, 3, 4])): + yield index + for index in range(len(iterable)): + yield [1, 2, 3][index] + yield len([1, 2, 3]) + for index in range(len(iterable)): + yield other_obj[index] + + from unknown import unknown + for index in range(unknown(iterable)): + yield iterable[index] + + for index in range(len(iterable)): + def test(iterable): + return iterable[index] + yield test([1, 2, 3]) diff --git a/pymode/libs/pylint/test/functional/consider_using_enumerate.txt b/pymode/libs/pylint/test/functional/consider_using_enumerate.txt new file mode 100644 index 00000000..36cd55d4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/consider_using_enumerate.txt @@ -0,0 +1 @@ +consider-using-enumerate:7:bad:Consider using enumerate instead of iterating with range and len diff --git a/pymode/libs/pylint/test/functional/continue_in_finally.py b/pymode/libs/pylint/test/functional/continue_in_finally.py new file mode 100644 index 00000000..89e1affb --- /dev/null +++ b/pymode/libs/pylint/test/functional/continue_in_finally.py @@ -0,0 +1,24 @@ +"""Test that `continue` is catched when met inside a `finally` clause.""" + +# pylint: disable=missing-docstring, lost-exception, broad-except + +while True: + try: + pass + finally: + continue # [continue-in-finally] + +while True: + try: + pass + finally: + break + +while True: + try: + pass + except Exception: + pass + else: + continue + \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/continue_in_finally.txt b/pymode/libs/pylint/test/functional/continue_in_finally.txt new file mode 100644 index 00000000..cb2ee168 --- /dev/null +++ b/pymode/libs/pylint/test/functional/continue_in_finally.txt @@ -0,0 +1 @@ +continue-in-finally:9::'continue' not supported inside 'finally' clause diff --git a/pymode/libs/pylint/test/functional/crash_missing_module_type.py b/pymode/libs/pylint/test/functional/crash_missing_module_type.py new file mode 100644 index 00000000..a471ad8a --- /dev/null +++ b/pymode/libs/pylint/test/functional/crash_missing_module_type.py @@ -0,0 +1,18 @@ +""" Test for a crash found in +https://bitbucket.org/logilab/astroid/issue/45/attributeerror-module-object-has-no#comment-11944673 +""" +# pylint: disable=no-init, invalid-name, too-few-public-methods, redefined-outer-name +def decor(trop): + """ decorator """ + return trop + +class Foo(object): + """ Class """ + @decor + def prop(self): + """ method """ + return self + +if __name__ == '__main__': + trop = Foo() + trop.prop = 42 diff --git a/pymode/libs/pylint/test/functional/crash_missing_module_type.txt b/pymode/libs/pylint/test/functional/crash_missing_module_type.txt new file mode 100644 index 00000000..e69de29b diff --git a/pymode/libs/pylint/test/functional/ctor_arguments.py b/pymode/libs/pylint/test/functional/ctor_arguments.py new file mode 100644 index 00000000..f7437b37 --- /dev/null +++ b/pymode/libs/pylint/test/functional/ctor_arguments.py @@ -0,0 +1,104 @@ +"""Test function argument checker on __init__ + +Based on test/functional/arguments.py +""" +# pylint: disable=C0111,R0903,W0231 + + +class Class1Arg(object): + def __init__(self, first_argument): + """one argument function""" + +class Class3Arg(object): + def __init__(self, first_argument, second_argument, third_argument): + """three arguments function""" + +class ClassDefaultArg(object): + def __init__(self, one=1, two=2): + """function with default value""" + +class Subclass1Arg(Class1Arg): + pass + +class ClassAllArgs(Class1Arg): + def __init__(self, *args, **kwargs): + pass + +class ClassMultiInheritance(Class1Arg, Class3Arg): + pass + +class ClassNew(object): + def __new__(cls, first_argument, kwarg=None): + return first_argument, kwarg + +Class1Arg(420) +Class1Arg() # [no-value-for-parameter] +Class1Arg(1337, 347) # [too-many-function-args] + +Class3Arg(420, 789) # [no-value-for-parameter] +# +1:[no-value-for-parameter,no-value-for-parameter,no-value-for-parameter] +Class3Arg() +Class3Arg(1337, 347, 456) +Class3Arg('bab', 'bebe', None, 5.6) # [too-many-function-args] + +ClassDefaultArg(1, two=5) +ClassDefaultArg(two=5) + +Class1Arg(bob=4) # [no-value-for-parameter,unexpected-keyword-arg] +ClassDefaultArg(1, 4, coin="hello") # [unexpected-keyword-arg] + +ClassDefaultArg(1, one=5) # [redundant-keyword-arg] + +Subclass1Arg(420) +Subclass1Arg() # [no-value-for-parameter] +Subclass1Arg(1337, 347) # [too-many-function-args] + +ClassAllArgs() +ClassAllArgs(1, 2, 3, even=4, more=5) + +ClassMultiInheritance(1) +ClassMultiInheritance(1, 2, 3) # [too-many-function-args] + +ClassNew(1, kwarg=1) +ClassNew(1, 2, 3) # [too-many-function-args] +ClassNew(one=2) # [no-value-for-parameter,unexpected-keyword-arg] + + +class Metaclass(type): + def __new__(mcs, name, bases, namespace): + return type.__new__(mcs, name, bases, namespace) + +def with_metaclass(meta, base=object): + """Create a new type that can be used as a metaclass.""" + return meta("NewBase", (base, ), {}) + +class ClassWithMeta(with_metaclass(Metaclass)): + pass + +ClassWithMeta() + + +class BuiltinExc(Exception): + def __init__(self, val=True): + self.val = val + +BuiltinExc(42, 24, badarg=1) # [too-many-function-args,unexpected-keyword-arg] + + +class Clsmethod(object): + def __init__(self, first, second): + self.first = first + self.second = second + + @classmethod + def from_nothing(cls): + return cls(1, 2, 3, 4) # [too-many-function-args] + + @classmethod + def from_nothing1(cls): + return cls() # [no-value-for-parameter,no-value-for-parameter] + + @classmethod + def from_nothing2(cls): + # +1: [no-value-for-parameter,unexpected-keyword-arg] + return cls(1, not_argument=2) diff --git a/pymode/libs/pylint/test/functional/ctor_arguments.txt b/pymode/libs/pylint/test/functional/ctor_arguments.txt new file mode 100644 index 00000000..639e92ce --- /dev/null +++ b/pymode/libs/pylint/test/functional/ctor_arguments.txt @@ -0,0 +1,24 @@ +no-value-for-parameter:35::No value for argument 'first_argument' in constructor call +too-many-function-args:36::Too many positional arguments for constructor call +no-value-for-parameter:38::No value for argument 'third_argument' in constructor call +no-value-for-parameter:40::No value for argument 'first_argument' in constructor call +no-value-for-parameter:40::No value for argument 'second_argument' in constructor call +no-value-for-parameter:40::No value for argument 'third_argument' in constructor call +too-many-function-args:42::Too many positional arguments for constructor call +no-value-for-parameter:47::No value for argument 'first_argument' in constructor call +unexpected-keyword-arg:47::Unexpected keyword argument 'bob' in constructor call +unexpected-keyword-arg:48::Unexpected keyword argument 'coin' in constructor call +redundant-keyword-arg:50::Argument 'one' passed by position and keyword in constructor call +no-value-for-parameter:53::No value for argument 'first_argument' in constructor call +too-many-function-args:54::Too many positional arguments for constructor call +too-many-function-args:60::Too many positional arguments for constructor call +too-many-function-args:63::Too many positional arguments for constructor call +no-value-for-parameter:64::No value for argument 'first_argument' in constructor call +unexpected-keyword-arg:64::Unexpected keyword argument 'one' in constructor call +too-many-function-args:85::Too many positional arguments for constructor call +unexpected-keyword-arg:85::Unexpected keyword argument 'badarg' in constructor call +too-many-function-args:95:Clsmethod.from_nothing:Too many positional arguments for constructor call +no-value-for-parameter:99:Clsmethod.from_nothing1:"No value for argument 'first' in constructor call" +no-value-for-parameter:99:Clsmethod.from_nothing1:"No value for argument 'second' in constructor call" +no-value-for-parameter:104:Clsmethod.from_nothing2:"No value for argument 'second' in constructor call" +unexpected-keyword-arg:104:Clsmethod.from_nothing2:"Unexpected keyword argument 'not_argument' in constructor call" \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/dangerous_default_value.py b/pymode/libs/pylint/test/functional/dangerous_default_value.py new file mode 100644 index 00000000..58a22f44 --- /dev/null +++ b/pymode/libs/pylint/test/functional/dangerous_default_value.py @@ -0,0 +1,79 @@ +# pylint: disable=missing-docstring + + +HEHE = {} + +def function1(value=[]): # [dangerous-default-value] + """docstring""" + return value + +def function2(value=HEHE): # [dangerous-default-value] + """docstring""" + return value + +def function3(value): + """docstring""" + return value + +def function4(value=set()): # [dangerous-default-value] + """set is mutable and dangerous.""" + return value + +def function5(value=frozenset()): + """frozenset is immutable and safe.""" + return value + +GLOBAL_SET = set() + +def function6(value=GLOBAL_SET): # [dangerous-default-value] + """set is mutable and dangerous.""" + return value + +def function7(value=dict()): # [dangerous-default-value] + """dict is mutable and dangerous.""" + return value + +def function8(value=list()): # [dangerous-default-value] + """list is mutable and dangerous.""" + return value + +def function9(value=[1, 2, 3, 4]): # [dangerous-default-value] + """list with items should not output item values in error message""" + return value + +def function10(value={'a': 1, 'b': 2}): # [dangerous-default-value] + """dictionaries with items should not output item values in error message""" + return value + +def function11(value=list([1, 2, 3])): # [dangerous-default-value] + """list with items should not output item values in error message""" + return value + +def function12(value=dict([('a', 1), ('b', 2)])): # [dangerous-default-value] + """dictionaries with items should not output item values in error message""" + return value + +OINK = { + 'a': 1, + 'b': 2 +} + +def function13(value=OINK): # [dangerous-default-value] + """dictionaries with items should not output item values in error message""" + return value + +def function14(value=dict([(1, 2), (1, 2, 3)])): # [dangerous-default-value] + """a dictionary which will not be inferred to a syntax AST, but to an + astroid.Instance. + """ + return value + +INVALID_DICT = dict([(1, 2), (1, 2, 3)]) + +def function15(value=INVALID_DICT): # [dangerous-default-value] + """The same situation as function14.""" + return value + +def function16(value={1}): # [dangerous-default-value] + """set literal as default value""" + return value diff --git a/pymode/libs/pylint/test/functional/dangerous_default_value.rc b/pymode/libs/pylint/test/functional/dangerous_default_value.rc new file mode 100644 index 00000000..a2328edb --- /dev/null +++ b/pymode/libs/pylint/test/functional/dangerous_default_value.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/dangerous_default_value.txt b/pymode/libs/pylint/test/functional/dangerous_default_value.txt new file mode 100644 index 00000000..92180e90 --- /dev/null +++ b/pymode/libs/pylint/test/functional/dangerous_default_value.txt @@ -0,0 +1,14 @@ +dangerous-default-value:6:function1:Dangerous default value [] as argument +dangerous-default-value:10:function2:Dangerous default value HEHE (__builtin__.dict) as argument +dangerous-default-value:18:function4:Dangerous default value set() (__builtin__.set) as argument +dangerous-default-value:28:function6:Dangerous default value GLOBAL_SET (__builtin__.set) as argument +dangerous-default-value:32:function7:Dangerous default value dict() (__builtin__.dict) as argument +dangerous-default-value:36:function8:Dangerous default value list() (__builtin__.list) as argument +dangerous-default-value:40:function9:Dangerous default value [] as argument +dangerous-default-value:44:function10:Dangerous default value {} as argument +dangerous-default-value:48:function11:Dangerous default value list() (__builtin__.list) as argument +dangerous-default-value:52:function12:Dangerous default value dict() (__builtin__.dict) as argument +dangerous-default-value:61:function13:Dangerous default value OINK (__builtin__.dict) as argument +dangerous-default-value:65:function14:Dangerous default value dict() (__builtin__.dict) as argument +dangerous-default-value:73:function15:Dangerous default value INVALID_DICT (__builtin__.dict) as argument +dangerous-default-value:77:function16:Dangerous default value set() as argument diff --git a/pymode/libs/pylint/test/functional/dangerous_default_value_py30.py b/pymode/libs/pylint/test/functional/dangerous_default_value_py30.py new file mode 100644 index 00000000..58a22f44 --- /dev/null +++ b/pymode/libs/pylint/test/functional/dangerous_default_value_py30.py @@ -0,0 +1,79 @@ +# pylint: disable=missing-docstring + + +HEHE = {} + +def function1(value=[]): # [dangerous-default-value] + """docstring""" + return value + +def function2(value=HEHE): # [dangerous-default-value] + """docstring""" + return value + +def function3(value): + """docstring""" + return value + +def function4(value=set()): # [dangerous-default-value] + """set is mutable and dangerous.""" + return value + +def function5(value=frozenset()): + """frozenset is immutable and safe.""" + return value + +GLOBAL_SET = set() + +def function6(value=GLOBAL_SET): # [dangerous-default-value] + """set is mutable and dangerous.""" + return value + +def function7(value=dict()): # [dangerous-default-value] + """dict is mutable and dangerous.""" + return value + +def function8(value=list()): # [dangerous-default-value] + """list is mutable and dangerous.""" + return value + +def function9(value=[1, 2, 3, 4]): # [dangerous-default-value] + """list with items should not output item values in error message""" + return value + +def function10(value={'a': 1, 'b': 2}): # [dangerous-default-value] + """dictionaries with items should not output item values in error message""" + return value + +def function11(value=list([1, 2, 3])): # [dangerous-default-value] + """list with items should not output item values in error message""" + return value + +def function12(value=dict([('a', 1), ('b', 2)])): # [dangerous-default-value] + """dictionaries with items should not output item values in error message""" + return value + +OINK = { + 'a': 1, + 'b': 2 +} + +def function13(value=OINK): # [dangerous-default-value] + """dictionaries with items should not output item values in error message""" + return value + +def function14(value=dict([(1, 2), (1, 2, 3)])): # [dangerous-default-value] + """a dictionary which will not be inferred to a syntax AST, but to an + astroid.Instance. + """ + return value + +INVALID_DICT = dict([(1, 2), (1, 2, 3)]) + +def function15(value=INVALID_DICT): # [dangerous-default-value] + """The same situation as function14.""" + return value + +def function16(value={1}): # [dangerous-default-value] + """set literal as default value""" + return value diff --git a/pymode/libs/pylint/test/functional/dangerous_default_value_py30.rc b/pymode/libs/pylint/test/functional/dangerous_default_value_py30.rc new file mode 100644 index 00000000..8c6eb56c --- /dev/null +++ b/pymode/libs/pylint/test/functional/dangerous_default_value_py30.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/dangerous_default_value_py30.txt b/pymode/libs/pylint/test/functional/dangerous_default_value_py30.txt new file mode 100644 index 00000000..7571c00f --- /dev/null +++ b/pymode/libs/pylint/test/functional/dangerous_default_value_py30.txt @@ -0,0 +1,14 @@ +dangerous-default-value:6:function1:Dangerous default value [] as argument +dangerous-default-value:10:function2:Dangerous default value HEHE (builtins.dict) as argument +dangerous-default-value:18:function4:Dangerous default value set() (builtins.set) as argument +dangerous-default-value:28:function6:Dangerous default value GLOBAL_SET (builtins.set) as argument +dangerous-default-value:32:function7:Dangerous default value dict() (builtins.dict) as argument +dangerous-default-value:36:function8:Dangerous default value list() (builtins.list) as argument +dangerous-default-value:40:function9:Dangerous default value [] as argument +dangerous-default-value:44:function10:Dangerous default value {} as argument +dangerous-default-value:48:function11:Dangerous default value list() (builtins.list) as argument +dangerous-default-value:52:function12:Dangerous default value dict() (builtins.dict) as argument +dangerous-default-value:61:function13:Dangerous default value OINK (builtins.dict) as argument +dangerous-default-value:65:function14:Dangerous default value dict() (builtins.dict) as argument +dangerous-default-value:73:function15:Dangerous default value INVALID_DICT (builtins.dict) as argument +dangerous-default-value:77:function16:Dangerous default value set() as argument \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/defined_and_used_on_same_line.py b/pymode/libs/pylint/test/functional/defined_and_used_on_same_line.py new file mode 100644 index 00000000..a61bcd76 --- /dev/null +++ b/pymode/libs/pylint/test/functional/defined_and_used_on_same_line.py @@ -0,0 +1,29 @@ +"""Check for definitions and usage happening on the same line.""" +#pylint: disable=missing-docstring,multiple-statements,no-absolute-import,parameter-unpacking,wrong-import-position +from __future__ import print_function +print([index + for index in range(10)]) + +print((index + for index in range(10))) + +FILTER_FUNC = lambda x: not x + +def func(xxx): return xxx + +def func2(xxx): return xxx + func2(1) + +import sys; print(sys.exc_info()) + +for i in range(10): print(i) + +j = 4; LAMB = lambda x: x+j + +FUNC4 = lambda a, b: a != b + +# test http://www.logilab.org/ticket/6954: + +with open('f') as f: print(f.read()) + +with open('f') as f, open(f.read()) as g: + print(g.read()) diff --git a/pymode/libs/pylint/test/functional/deprecated_lambda.py b/pymode/libs/pylint/test/functional/deprecated_lambda.py new file mode 100644 index 00000000..6a785a6b --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_lambda.py @@ -0,0 +1,23 @@ +# pylint: disable=missing-docstring,bad-builtin,invalid-name,no-absolute-import + +import functools + +# Don't do this, use a comprehension instead. +assert map(lambda x: x*2, [1, 2, 3]) == [2, 4, 6] # [deprecated-lambda] + +assert filter(lambda x: x != 1, [1, 2, 3]) == [2, 3] # [deprecated-lambda] + +# It's still ok to use map and filter with anything but an inline lambda. +double = lambda x: x * 2 +assert map(double, [1, 2, 3]) == [2, 4, 6] + +# It's also ok to pass lambdas to other functions. +assert functools.reduce(lambda x, y: x * y, [1, 2, 3, 4]) == 24 + +# Or to a undefined function or one with varargs +def f(*a): + return len(a) + +f(lambda x, y: x + y, [1, 2, 3]) + +undefined_function(lambda: 2) # pylint: disable=undefined-variable diff --git a/pymode/libs/pylint/test/functional/deprecated_lambda.rc b/pymode/libs/pylint/test/functional/deprecated_lambda.rc new file mode 100644 index 00000000..a6502339 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_lambda.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/deprecated_lambda.txt b/pymode/libs/pylint/test/functional/deprecated_lambda.txt new file mode 100644 index 00000000..78cd9fbf --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_lambda.txt @@ -0,0 +1,2 @@ +deprecated-lambda:6::map/filter on lambda could be replaced by comprehension +deprecated-lambda:8::map/filter on lambda could be replaced by comprehension diff --git a/pymode/libs/pylint/test/functional/deprecated_methods_py2.py b/pymode/libs/pylint/test/functional/deprecated_methods_py2.py new file mode 100644 index 00000000..ceba1e4f --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_methods_py2.py @@ -0,0 +1,9 @@ +""" Functional test for deprecated methods in Python 2 """ +# pylint: disable=no-member +import os +import xml.etree.ElementTree + +os.popen2('') # [deprecated-method] +os.popen3('') # [deprecated-method] +os.popen4('') # [deprecated-method] +xml.etree.ElementTree.Element('elem').getchildren() # [deprecated-method] diff --git a/pymode/libs/pylint/test/functional/deprecated_methods_py2.rc b/pymode/libs/pylint/test/functional/deprecated_methods_py2.rc new file mode 100644 index 00000000..a6502339 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_methods_py2.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/deprecated_methods_py2.txt b/pymode/libs/pylint/test/functional/deprecated_methods_py2.txt new file mode 100644 index 00000000..e1038185 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_methods_py2.txt @@ -0,0 +1,4 @@ +deprecated-method:6::Using deprecated method popen2() +deprecated-method:7::Using deprecated method popen3() +deprecated-method:8::Using deprecated method popen4() +deprecated-method:9::Using deprecated method getchildren() diff --git a/pymode/libs/pylint/test/functional/deprecated_methods_py3.py b/pymode/libs/pylint/test/functional/deprecated_methods_py3.py new file mode 100644 index 00000000..06a8517b --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_methods_py3.py @@ -0,0 +1,35 @@ +""" Functional tests for method deprecation. """ +# pylint: disable=missing-docstring, super-init-not-called, not-callable +import base64 +import cgi +import inspect +import logging +import nntplib +import platform +import unittest +import xml.etree.ElementTree + + +class MyTest(unittest.TestCase): + def test(self): + self.assert_(True) # [deprecated-method] + +xml.etree.ElementTree.Element('tag').getchildren() # [deprecated-method] +xml.etree.ElementTree.Element('tag').getiterator() # [deprecated-method] +xml.etree.ElementTree.XMLParser('tag', None, None).doctype(None, None, None) # [deprecated-method] +nntplib.NNTP(None).xpath(None) # [deprecated-method] +inspect.getmoduleinfo(inspect) # [deprecated-method] +inspect.getmodulename(inspect) # [deprecated-method] +inspect.getargspec(None) # [deprecated-method] +logging.warn("a") # [deprecated-method] +platform.popen([]) # [deprecated-method] +base64.encodestring("42") # [deprecated-method] +base64.decodestring("42") # [deprecated-method] +cgi.escape("a") # [deprecated-method] + + +class SuperCrash(unittest.TestCase): + + def __init__(self): + # should not crash. + super(SuperCrash, self)() diff --git a/pymode/libs/pylint/test/functional/deprecated_methods_py3.rc b/pymode/libs/pylint/test/functional/deprecated_methods_py3.rc new file mode 100644 index 00000000..28c99bc2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_methods_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.3 diff --git a/pymode/libs/pylint/test/functional/deprecated_methods_py3.txt b/pymode/libs/pylint/test/functional/deprecated_methods_py3.txt new file mode 100644 index 00000000..29a2ce86 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_methods_py3.txt @@ -0,0 +1,13 @@ +deprecated-method:15:MyTest.test:Using deprecated method assert_() +deprecated-method:17::Using deprecated method getchildren() +deprecated-method:18::Using deprecated method getiterator() +deprecated-method:19::Using deprecated method doctype() +deprecated-method:20::Using deprecated method xpath() +deprecated-method:21::Using deprecated method getmoduleinfo() +deprecated-method:22::Using deprecated method getmodulename() +deprecated-method:23::Using deprecated method getargspec() +deprecated-method:24::Using deprecated method warn() +deprecated-method:25::Using deprecated method popen() +deprecated-method:26::Using deprecated method encodestring() +deprecated-method:27::Using deprecated method decodestring() +deprecated-method:28::Using deprecated method escape() diff --git a/pymode/libs/pylint/test/functional/deprecated_module_py2.py b/pymode/libs/pylint/test/functional/deprecated_module_py2.py new file mode 100644 index 00000000..b0d18654 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_module_py2.py @@ -0,0 +1,8 @@ +"""Test deprecated modules.""" +# pylint: disable=unused-import,no-name-in-module,import-error,ungrouped-imports + +import Bastion # [deprecated-module] +import rexec # [deprecated-module] + +from Bastion import bastion_module # [deprecated-module] +from rexec import rexec_module # [deprecated-module] diff --git a/pymode/libs/pylint/test/functional/deprecated_module_py2.rc b/pymode/libs/pylint/test/functional/deprecated_module_py2.rc new file mode 100644 index 00000000..41315d22 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_module_py2.rc @@ -0,0 +1,3 @@ +[testoptions] +max_pyver=3.0 +except_implementations=Jython \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/deprecated_module_py2.txt b/pymode/libs/pylint/test/functional/deprecated_module_py2.txt new file mode 100644 index 00000000..54f55550 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_module_py2.txt @@ -0,0 +1,4 @@ +deprecated-module:4::Uses of a deprecated module 'Bastion' +deprecated-module:5::Uses of a deprecated module 'rexec' +deprecated-module:7::Uses of a deprecated module 'Bastion' +deprecated-module:8::Uses of a deprecated module 'rexec' diff --git a/pymode/libs/pylint/test/functional/deprecated_module_py3.py b/pymode/libs/pylint/test/functional/deprecated_module_py3.py new file mode 100644 index 00000000..7a0d68e9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_module_py3.py @@ -0,0 +1,5 @@ +"""Test deprecated modules.""" +# pylint: disable=unused-import + +import optparse # [deprecated-module] + diff --git a/pymode/libs/pylint/test/functional/deprecated_module_py3.rc b/pymode/libs/pylint/test/functional/deprecated_module_py3.rc new file mode 100644 index 00000000..a2ab06c5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_module_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/deprecated_module_py3.txt b/pymode/libs/pylint/test/functional/deprecated_module_py3.txt new file mode 100644 index 00000000..c41bf3b6 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_module_py3.txt @@ -0,0 +1 @@ +deprecated-module:4::Uses of a deprecated module 'optparse' diff --git a/pymode/libs/pylint/test/functional/deprecated_module_uninstalled.py b/pymode/libs/pylint/test/functional/deprecated_module_uninstalled.py new file mode 100644 index 00000000..52d55fa8 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_module_uninstalled.py @@ -0,0 +1,5 @@ +"""Test deprecated modules uninstalled.""" +# pylint: disable=unused-import,no-name-in-module,import-error + +from uninstalled import uninstalled_module # [deprecated-module] +import uninstalled # [deprecated-module] diff --git a/pymode/libs/pylint/test/functional/deprecated_module_uninstalled.rc b/pymode/libs/pylint/test/functional/deprecated_module_uninstalled.rc new file mode 100644 index 00000000..05973daa --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_module_uninstalled.rc @@ -0,0 +1,2 @@ +[Messages Control] +deprecated-modules=uninstalled \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/deprecated_module_uninstalled.txt b/pymode/libs/pylint/test/functional/deprecated_module_uninstalled.txt new file mode 100644 index 00000000..6f14ddd2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/deprecated_module_uninstalled.txt @@ -0,0 +1,2 @@ +deprecated-module:4::Uses of a deprecated module 'uninstalled' +deprecated-module:5::Uses of a deprecated module 'uninstalled' diff --git a/pymode/libs/pylint/test/functional/docstrings.py b/pymode/libs/pylint/test/functional/docstrings.py new file mode 100644 index 00000000..eccb0577 --- /dev/null +++ b/pymode/libs/pylint/test/functional/docstrings.py @@ -0,0 +1,83 @@ +# pylint: disable=R0201 +# -1: [missing-docstring] +from __future__ import print_function + +# +1: [empty-docstring] +def function0(): + """""" + +# +1: [missing-docstring] +def function1(value): + # missing docstring + print(value) + +def function2(value): + """docstring""" + print(value) + +def function3(value): + """docstring""" + print(value) + +# +1: [missing-docstring] +class AAAA(object): + # missing docstring + +## class BBBB: +## # missing docstring +## pass + +## class CCCC: +## """yeah !""" +## def method1(self): +## pass + +## def method2(self): +## """ yeah !""" +## pass + + # +1: [missing-docstring] + def method1(self): + pass + + def method2(self): + """ yeah !""" + pass + + # +1: [empty-docstring] + def method3(self): + """""" + pass + + def __init__(self): + pass + +class DDDD(AAAA): + """yeah !""" + + def __init__(self): + AAAA.__init__(self) + + # +1: [empty-docstring] + def method2(self): + """""" + pass + + def method3(self): + pass + + # +1: [missing-docstring] + def method4(self): + pass + +# pylint: disable=missing-docstring +def function4(): + pass + +# pylint: disable=empty-docstring +def function5(): + """""" + pass + +def function6(): + """ I am a {} docstring.""".format("good") diff --git a/pymode/libs/pylint/test/functional/docstrings.txt b/pymode/libs/pylint/test/functional/docstrings.txt new file mode 100644 index 00000000..e30e4629 --- /dev/null +++ b/pymode/libs/pylint/test/functional/docstrings.txt @@ -0,0 +1,8 @@ +missing-docstring:1::Missing module docstring +empty-docstring:6:function0:Empty function docstring +missing-docstring:10:function1:Missing function docstring +missing-docstring:23:AAAA:Missing class docstring +missing-docstring:40:AAAA.method1:Missing method docstring:INFERENCE +empty-docstring:48:AAAA.method3:Empty method docstring:INFERENCE +empty-docstring:62:DDDD.method2:Empty method docstring:INFERENCE +missing-docstring:70:DDDD.method4:Missing method docstring:INFERENCE diff --git a/pymode/libs/pylint/test/functional/duplicate_argument_name.py b/pymode/libs/pylint/test/functional/duplicate_argument_name.py new file mode 100644 index 00000000..2b939ed5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/duplicate_argument_name.py @@ -0,0 +1,11 @@ +"""Check for duplicate function arguments.""" + + +def foo1(_, _): # [duplicate-argument-name] + """Function with duplicate argument name.""" + +def foo2(_, *_): # [duplicate-argument-name] + """Function with duplicate argument name.""" + +def foo3(_, _=3): # [duplicate-argument-name] + """Function with duplicate argument name.""" diff --git a/pymode/libs/pylint/test/functional/duplicate_argument_name.txt b/pymode/libs/pylint/test/functional/duplicate_argument_name.txt new file mode 100644 index 00000000..c51e4dfc --- /dev/null +++ b/pymode/libs/pylint/test/functional/duplicate_argument_name.txt @@ -0,0 +1,3 @@ +duplicate-argument-name:4:foo1:Duplicate argument name _ in function definition +duplicate-argument-name:7:foo2:Duplicate argument name _ in function definition +duplicate-argument-name:10:foo3:Duplicate argument name _ in function definition diff --git a/pymode/libs/pylint/test/functional/duplicate_bases.py b/pymode/libs/pylint/test/functional/duplicate_bases.py new file mode 100644 index 00000000..974f9758 --- /dev/null +++ b/pymode/libs/pylint/test/functional/duplicate_bases.py @@ -0,0 +1,15 @@ +"""Test duplicate bases error.""" +# pylint: disable=missing-docstring,too-few-public-methods,no-init + + +class Duplicates(str, str): # [duplicate-bases] + pass + + +class Alpha(str): + pass + + +class NotDuplicates(Alpha, str): + """The error should not be emitted for this case, since the + other same base comes from the ancestors.""" diff --git a/pymode/libs/pylint/test/functional/duplicate_bases.txt b/pymode/libs/pylint/test/functional/duplicate_bases.txt new file mode 100644 index 00000000..beb91b53 --- /dev/null +++ b/pymode/libs/pylint/test/functional/duplicate_bases.txt @@ -0,0 +1 @@ +duplicate-bases:5:Duplicates:Duplicate bases for class 'Duplicates' \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/duplicate_dict_literal_key.py b/pymode/libs/pylint/test/functional/duplicate_dict_literal_key.py new file mode 100644 index 00000000..3fae8b7e --- /dev/null +++ b/pymode/libs/pylint/test/functional/duplicate_dict_literal_key.py @@ -0,0 +1,14 @@ +"""Check multiple key definition""" +# pylint: disable=C0103 + +correct_dict = { + 'tea': 'for two', + 'two': 'for tea', +} + +wrong_dict = { # [duplicate-key] + 'tea': 'for two', + 'two': 'for tea', + 'tea': 'time', + +} diff --git a/pymode/libs/pylint/test/functional/duplicate_dict_literal_key.txt b/pymode/libs/pylint/test/functional/duplicate_dict_literal_key.txt new file mode 100644 index 00000000..97804800 --- /dev/null +++ b/pymode/libs/pylint/test/functional/duplicate_dict_literal_key.txt @@ -0,0 +1 @@ +duplicate-key:9::Duplicate key 'tea' in dictionary diff --git a/pymode/libs/pylint/test/functional/duplicate_except.py b/pymode/libs/pylint/test/functional/duplicate_except.py new file mode 100644 index 00000000..c68cf7a2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/duplicate_except.py @@ -0,0 +1,12 @@ +"""Test for duplicate-check.""" + +def main(): + """The second ValueError should be flagged.""" + try: + raise ValueError('test') + except ValueError: + return 1 + except ValueError: # [duplicate-except] + return 2 + except (OSError, TypeError): + return 3 diff --git a/pymode/libs/pylint/test/functional/duplicate_except.txt b/pymode/libs/pylint/test/functional/duplicate_except.txt new file mode 100644 index 00000000..a037948e --- /dev/null +++ b/pymode/libs/pylint/test/functional/duplicate_except.txt @@ -0,0 +1 @@ +duplicate-except:9:main:Catching previously caught exception type ValueError diff --git a/pymode/libs/pylint/test/functional/eval_used.py b/pymode/libs/pylint/test/functional/eval_used.py new file mode 100644 index 00000000..799121bb --- /dev/null +++ b/pymode/libs/pylint/test/functional/eval_used.py @@ -0,0 +1,11 @@ +"""test for eval usage""" + +eval('os.listdir(".")') # [eval-used] +eval('os.listdir(".")', globals={}) # [eval-used] + +eval('os.listdir(".")', globals=globals()) # [eval-used] + +def func(): + """ eval in local scope""" + eval('b = 1') # [eval-used] + diff --git a/pymode/libs/pylint/test/functional/eval_used.txt b/pymode/libs/pylint/test/functional/eval_used.txt new file mode 100644 index 00000000..7f0e0414 --- /dev/null +++ b/pymode/libs/pylint/test/functional/eval_used.txt @@ -0,0 +1,4 @@ +eval-used:3::Use of eval +eval-used:4::Use of eval +eval-used:6::Use of eval +eval-used:10:func:Use of eval \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/exception_is_binary_op.py b/pymode/libs/pylint/test/functional/exception_is_binary_op.py new file mode 100644 index 00000000..f71163d1 --- /dev/null +++ b/pymode/libs/pylint/test/functional/exception_is_binary_op.py @@ -0,0 +1,12 @@ +"""Warn about binary operations used as exceptions.""" +from __future__ import print_function +try: + pass +except Exception or BaseException: # [binary-op-exception] + print("caught1") +except Exception and BaseException: # [binary-op-exception] + print("caught2") +except Exception or BaseException: # [binary-op-exception] + print("caught3") +except (Exception or BaseException) as exc: # [binary-op-exception] + print("caught4") diff --git a/pymode/libs/pylint/test/functional/exception_is_binary_op.txt b/pymode/libs/pylint/test/functional/exception_is_binary_op.txt new file mode 100644 index 00000000..e6512c96 --- /dev/null +++ b/pymode/libs/pylint/test/functional/exception_is_binary_op.txt @@ -0,0 +1,4 @@ +binary-op-exception:5::"Exception to catch is the result of a binary ""or"" operation" +binary-op-exception:7::"Exception to catch is the result of a binary ""and"" operation" +binary-op-exception:9::"Exception to catch is the result of a binary ""or"" operation" +binary-op-exception:11::"Exception to catch is the result of a binary ""or"" operation" diff --git a/pymode/libs/pylint/test/functional/exec_used_py2.py b/pymode/libs/pylint/test/functional/exec_used_py2.py new file mode 100644 index 00000000..8f7f07d9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/exec_used_py2.py @@ -0,0 +1,10 @@ +# pylint: disable=missing-docstring + +exec 'a = __revision__' # [exec-used] +VALUES = {} +exec 'a = 1' in VALUES # [exec-used] + +exec 'a = 1' in globals() # [exec-used] + +def func(): + exec 'b = 1' # [exec-used] diff --git a/pymode/libs/pylint/test/functional/exec_used_py2.rc b/pymode/libs/pylint/test/functional/exec_used_py2.rc new file mode 100644 index 00000000..a6502339 --- /dev/null +++ b/pymode/libs/pylint/test/functional/exec_used_py2.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/exec_used_py2.txt b/pymode/libs/pylint/test/functional/exec_used_py2.txt new file mode 100644 index 00000000..6b0e7d1a --- /dev/null +++ b/pymode/libs/pylint/test/functional/exec_used_py2.txt @@ -0,0 +1,4 @@ +exec-used:3::Use of exec +exec-used:5::Use of exec +exec-used:7::Use of exec +exec-used:10:func:Use of exec \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/exec_used_py3.py b/pymode/libs/pylint/test/functional/exec_used_py3.py new file mode 100644 index 00000000..c6e819b4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/exec_used_py3.py @@ -0,0 +1,10 @@ +# pylint: disable=missing-docstring + +exec('a = __revision__') # [exec-used] +exec('a = 1', globals={}) # [exec-used] + +exec('a = 1', globals=globals()) # [exec-used] + +def func(): + exec('b = 1') # [exec-used] + diff --git a/pymode/libs/pylint/test/functional/exec_used_py3.rc b/pymode/libs/pylint/test/functional/exec_used_py3.rc new file mode 100644 index 00000000..c093be20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/exec_used_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/exec_used_py3.txt b/pymode/libs/pylint/test/functional/exec_used_py3.txt new file mode 100644 index 00000000..67885745 --- /dev/null +++ b/pymode/libs/pylint/test/functional/exec_used_py3.txt @@ -0,0 +1,4 @@ +exec-used:3::Use of exec +exec-used:4::Use of exec +exec-used:6::Use of exec +exec-used:9:func:Use of exec \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/fixme.py b/pymode/libs/pylint/test/functional/fixme.py new file mode 100644 index 00000000..9d5d966b --- /dev/null +++ b/pymode/libs/pylint/test/functional/fixme.py @@ -0,0 +1,18 @@ +# pylint: disable=missing-docstring, unused-variable + + +# +1: [fixme] +# FIXME: beep + + +def function(): + variable = "FIXME: Ignore me!" + # +1: [fixme] + test = "text" # FIXME: Valid test + + # +1: [fixme] + # TODO: Do something with the variables + # +1: [fixme] + xxx = "n/a" # XXX: Fix this later + # +1: [fixme] + #FIXME: no space after hash diff --git a/pymode/libs/pylint/test/functional/fixme.txt b/pymode/libs/pylint/test/functional/fixme.txt new file mode 100644 index 00000000..581788ae --- /dev/null +++ b/pymode/libs/pylint/test/functional/fixme.txt @@ -0,0 +1,5 @@ +fixme:5::"FIXME: beep" +fixme:11::"FIXME: Valid test" +fixme:14::"TODO: Do something with the variables" +fixme:16::"XXX: Fix this later" +fixme:18::"FIXME: no space after hash" \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/formatting.txt b/pymode/libs/pylint/test/functional/formatting.txt new file mode 100644 index 00000000..e69de29b diff --git a/pymode/libs/pylint/test/functional/function_redefined.py b/pymode/libs/pylint/test/functional/function_redefined.py new file mode 100644 index 00000000..d043cb00 --- /dev/null +++ b/pymode/libs/pylint/test/functional/function_redefined.py @@ -0,0 +1,74 @@ +# pylint: disable=R0201,missing-docstring,using-constant-test +from __future__ import division +__revision__ = '' + +class AAAA(object): + """docstring""" + def __init__(self): + pass + def method1(self): + """docstring""" + + def method2(self): + """docstring""" + + def method2(self): # [function-redefined] + """docstring""" + +class AAAA(object): # [function-redefined] + """docstring""" + def __init__(self): + pass + def yeah(self): + """hehehe""" + def yoo(self): + """yoo""" +def func1(): + """docstring""" + +def func2(): + """docstring""" + +def func2(): # [function-redefined] + """docstring""" + __revision__ = 1 # [redefined-outer-name] + 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(): # [function-redefined] + "this one redefine the one defined line 42" + + +def with_inner_function_1(): + """docstring""" + def callback(): + """callback docstring""" + pass + return callback + +def with_inner_function_2(): + """docstring""" + def callback(): + """does not redefine callback returned by with_inner_function_1""" + pass + return callback + +def some_func(): + """Don't emit if we defined a variable with the same name as a + __future__ directive. + """ + division = 2 + return division diff --git a/pymode/libs/pylint/test/functional/function_redefined.txt b/pymode/libs/pylint/test/functional/function_redefined.txt new file mode 100644 index 00000000..56fabc5e --- /dev/null +++ b/pymode/libs/pylint/test/functional/function_redefined.txt @@ -0,0 +1,5 @@ +function-redefined:15:AAAA.method2:method already defined line 12 +function-redefined:18:AAAA:class already defined line 5 +function-redefined:32:func2:function already defined line 29 +redefined-outer-name:34:func2:Redefining name '__revision__' from outer scope (line 3) +function-redefined:51:exclusive_func2:function already defined line 45 diff --git a/pymode/libs/pylint/test/functional/future_import.py b/pymode/libs/pylint/test/functional/future_import.py new file mode 100644 index 00000000..e4d3785a --- /dev/null +++ b/pymode/libs/pylint/test/functional/future_import.py @@ -0,0 +1,3 @@ +"""a docstring""" + +from __future__ import generators diff --git a/pymode/libs/pylint/test/functional/future_unicode_literals.py b/pymode/libs/pylint/test/functional/future_unicode_literals.py new file mode 100644 index 00000000..30c2bd6b --- /dev/null +++ b/pymode/libs/pylint/test/functional/future_unicode_literals.py @@ -0,0 +1,6 @@ +"""Unicode literals in Python 2.*""" +from __future__ import unicode_literals + + +BAD_STRING = b'\u1234' # >= 2.7.4:[anomalous-unicode-escape-in-string] +GOOD_STRING = '\u1234' diff --git a/pymode/libs/pylint/test/functional/future_unicode_literals.rc b/pymode/libs/pylint/test/functional/future_unicode_literals.rc new file mode 100644 index 00000000..eb52949d --- /dev/null +++ b/pymode/libs/pylint/test/functional/future_unicode_literals.rc @@ -0,0 +1,2 @@ +[testoptions] +except_implementations=Jython \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/future_unicode_literals.txt b/pymode/libs/pylint/test/functional/future_unicode_literals.txt new file mode 100644 index 00000000..60d291e7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/future_unicode_literals.txt @@ -0,0 +1 @@ +anomalous-unicode-escape-in-string:5::"Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix." diff --git a/pymode/libs/pylint/test/functional/generated_members.py b/pymode/libs/pylint/test/functional/generated_members.py new file mode 100644 index 00000000..e2ab9642 --- /dev/null +++ b/pymode/libs/pylint/test/functional/generated_members.py @@ -0,0 +1,8 @@ +"""Test the generated-members config option.""" +from __future__ import print_function + +class Klass(object): + """A class with a generated member.""" + +print(Klass().DoesNotExist) +print(Klass().aBC_set1) diff --git a/pymode/libs/pylint/test/functional/generated_members.rc b/pymode/libs/pylint/test/functional/generated_members.rc new file mode 100644 index 00000000..4657c4e5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/generated_members.rc @@ -0,0 +1,5 @@ +[Messages Control] +disable=too-few-public-methods,print-statement + +[typecheck] +generated-members=DoesNotExist,"[a-zA-Z]+_set{1,2}"" diff --git a/pymode/libs/pylint/test/functional/genexpr_variable_scope.py b/pymode/libs/pylint/test/functional/genexpr_variable_scope.py new file mode 100644 index 00000000..a00d72d3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/genexpr_variable_scope.py @@ -0,0 +1,5 @@ +"""test name defined in generator expression are not available +outside the genexpr scope +""" +from __future__ import print_function +print(n) # [undefined-variable] diff --git a/pymode/libs/pylint/test/functional/genexpr_variable_scope.txt b/pymode/libs/pylint/test/functional/genexpr_variable_scope.txt new file mode 100644 index 00000000..3cfa6c5e --- /dev/null +++ b/pymode/libs/pylint/test/functional/genexpr_variable_scope.txt @@ -0,0 +1 @@ +undefined-variable:5::Undefined variable 'n' diff --git a/pymode/libs/pylint/test/functional/globals.py b/pymode/libs/pylint/test/functional/globals.py new file mode 100644 index 00000000..c2844b1f --- /dev/null +++ b/pymode/libs/pylint/test/functional/globals.py @@ -0,0 +1,25 @@ +"""Warnings about global statements and usage of global variables.""" +from __future__ import print_function + +global CSTE # [global-at-module-level] +print(CSTE) # [undefined-variable] + +CONSTANT = 1 + +def fix_contant(value): + """all this is ok, but try not using global ;)""" + global CONSTANT # [global-statement] + print(CONSTANT) + CONSTANT = value + + +def other(): + """global behaviour test""" + global HOP # [global-variable-not-assigned] + print(HOP) # [undefined-variable] + + +def define_constant(): + """ok but somevar is not defined at the module scope""" + global SOMEVAR # [global-variable-undefined] + SOMEVAR = 2 diff --git a/pymode/libs/pylint/test/functional/globals.txt b/pymode/libs/pylint/test/functional/globals.txt new file mode 100644 index 00000000..c6759064 --- /dev/null +++ b/pymode/libs/pylint/test/functional/globals.txt @@ -0,0 +1,6 @@ +global-at-module-level:4::Using the global statement at the module level +undefined-variable:5::Undefined variable 'CSTE' +global-statement:11:fix_contant:Using the global statement +global-variable-not-assigned:18:other:Using global for 'HOP' but no assignment is done +undefined-variable:19:other:Undefined variable 'HOP' +global-variable-undefined:24:define_constant:Global variable 'SOMEVAR' undefined at the module level diff --git a/pymode/libs/pylint/test/functional/import_error.py b/pymode/libs/pylint/test/functional/import_error.py new file mode 100644 index 00000000..de72e2fc --- /dev/null +++ b/pymode/libs/pylint/test/functional/import_error.py @@ -0,0 +1,27 @@ +""" Test that import errors are detected. """ +# pylint: disable=invalid-name, unused-import, no-absolute-import, bare-except, broad-except +import totally_missing # [import-error] + +try: + import maybe_missing +except ImportError: + maybe_missing = None + +try: + import maybe_missing_1 +except (ImportError, SyntaxError): + maybe_missing_1 = None + +try: + import maybe_missing_2 # [import-error] +except ValueError: + maybe_missing_2 = None + + +try: + if maybe_missing: + import really_missing +except ImportError: + pass + +from .collections import missing # [import-error] diff --git a/pymode/libs/pylint/test/functional/import_error.txt b/pymode/libs/pylint/test/functional/import_error.txt new file mode 100644 index 00000000..b8746e9d --- /dev/null +++ b/pymode/libs/pylint/test/functional/import_error.txt @@ -0,0 +1,3 @@ +import-error:3::"Unable to import 'totally_missing'" +import-error:16::"Unable to import 'maybe_missing_2'" +import-error:27::"Unable to import 'functional.collections'" diff --git a/pymode/libs/pylint/test/functional/inconsistent_mro.py b/pymode/libs/pylint/test/functional/inconsistent_mro.py new file mode 100644 index 00000000..0b65068d --- /dev/null +++ b/pymode/libs/pylint/test/functional/inconsistent_mro.py @@ -0,0 +1,9 @@ +"""Tests for inconsistent-mro.""" +# pylint: disable=missing-docstring,too-few-public-methods,no-init + +class Str(str): + pass + + +class Inconsistent(str, Str): # [inconsistent-mro] + pass diff --git a/pymode/libs/pylint/test/functional/inconsistent_mro.txt b/pymode/libs/pylint/test/functional/inconsistent_mro.txt new file mode 100644 index 00000000..1ae9687d --- /dev/null +++ b/pymode/libs/pylint/test/functional/inconsistent_mro.txt @@ -0,0 +1 @@ +inconsistent-mro:8:Inconsistent:Inconsistent method resolution order for class 'Inconsistent' diff --git a/pymode/libs/pylint/test/functional/indexing_exception.py b/pymode/libs/pylint/test/functional/indexing_exception.py new file mode 100644 index 00000000..9ac1a7c6 --- /dev/null +++ b/pymode/libs/pylint/test/functional/indexing_exception.py @@ -0,0 +1,15 @@ +""" +Check for indexing exceptions. +""" +# pylint: disable=import-error, no-absolute-import + +from unknown import ExtensionException +__revision__ = 0 + +class SubException(IndexError): + """ empty """ + +_ = IndexError("test")[0] # [indexing-exception] +_ = ZeroDivisionError("error")[0] # [indexing-exception] +_ = ExtensionException("error")[0] +_ = SubException("error")[1] # [indexing-exception] diff --git a/pymode/libs/pylint/test/functional/indexing_exception.rc b/pymode/libs/pylint/test/functional/indexing_exception.rc new file mode 100644 index 00000000..9540bc95 --- /dev/null +++ b/pymode/libs/pylint/test/functional/indexing_exception.rc @@ -0,0 +1,5 @@ +[testoptions] +max_pyver=3.0 + +[Messages Control] +enable=python3 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/indexing_exception.txt b/pymode/libs/pylint/test/functional/indexing_exception.txt new file mode 100644 index 00000000..9f306780 --- /dev/null +++ b/pymode/libs/pylint/test/functional/indexing_exception.txt @@ -0,0 +1,3 @@ +indexing-exception:12::Indexing exceptions will not work on Python 3 +indexing-exception:13::Indexing exceptions will not work on Python 3 +indexing-exception:15::Indexing exceptions will not work on Python 3 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/inherit_non_class.py b/pymode/libs/pylint/test/functional/inherit_non_class.py new file mode 100644 index 00000000..0929ed1d --- /dev/null +++ b/pymode/libs/pylint/test/functional/inherit_non_class.py @@ -0,0 +1,81 @@ +"""Test that inheriting from something which is not +a class emits a warning. """ + +# pylint: disable=no-init, import-error, invalid-name, using-constant-test +# pylint: disable=missing-docstring, too-few-public-methods, no-absolute-import + +from missing import Missing + +if 1: + Ambiguous = None +else: + Ambiguous = int + +class Empty(object): + """ Empty class. """ + +def return_class(): + """ Return a class. """ + return Good3 + +class Bad(1): # [inherit-non-class] + """ Can't inherit from instance. """ + +class Bad1(lambda abc: 42): # [inherit-non-class] + """ Can't inherit from lambda. """ + +class Bad2(object()): # [inherit-non-class] + """ Can't inherit from an instance of object. """ + +class Bad3(return_class): # [inherit-non-class] + """ Can't inherit from function. """ + +class Bad4(Empty()): # [inherit-non-class] + """ Can't inherit from instance. """ + +class Good(object): + pass + +class Good1(int): + pass + +class Good2(type): + pass + +class Good3(type(int)): + pass + +class Good4(return_class()): + pass + +class Good5(Good4, int, object): + pass + +class Good6(Ambiguous): + """ Inherits from something ambiguous. + + This could emit a warning when we will have + flow detection. + """ + +class Unknown(Missing): + pass + +class Unknown1(Good5 if True else Bad1): + pass + + +class NotInheritableBool(bool): # [inherit-non-class] + pass + + +class NotInheritableRange(range): # [inherit-non-class] + pass + + +class NotInheritableSlice(slice): # [inherit-non-class] + pass + + +class NotInheritableMemoryView(memoryview): # [inherit-non-class] + pass diff --git a/pymode/libs/pylint/test/functional/inherit_non_class.txt b/pymode/libs/pylint/test/functional/inherit_non_class.txt new file mode 100644 index 00000000..3be70cb9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/inherit_non_class.txt @@ -0,0 +1,9 @@ +inherit-non-class:21:Bad:Inheriting '1', which is not a class. +inherit-non-class:24:Bad1:"Inheriting 'lambda abc: 42', which is not a class." +inherit-non-class:27:Bad2:Inheriting 'object()', which is not a class. +inherit-non-class:30:Bad3:Inheriting 'return_class', which is not a class. +inherit-non-class:33:Bad4:Inheriting 'Empty()', which is not a class. +inherit-non-class:68:NotInheritableBool:Inheriting 'bool', which is not a class. +inherit-non-class:72:NotInheritableRange:Inheriting 'range', which is not a class. +inherit-non-class:76:NotInheritableSlice:Inheriting 'slice', which is not a class. +inherit-non-class:80:NotInheritableMemoryView:Inheriting 'memoryview', which is not a class. \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/init_is_generator.py b/pymode/libs/pylint/test/functional/init_is_generator.py new file mode 100644 index 00000000..882200c7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/init_is_generator.py @@ -0,0 +1,5 @@ +# pylint: disable=missing-docstring,too-few-public-methods + +class SomeClass(object): + def __init__(self): # [init-is-generator] + yield None diff --git a/pymode/libs/pylint/test/functional/init_is_generator.txt b/pymode/libs/pylint/test/functional/init_is_generator.txt new file mode 100644 index 00000000..5d2132a1 --- /dev/null +++ b/pymode/libs/pylint/test/functional/init_is_generator.txt @@ -0,0 +1 @@ +init-is-generator:4:SomeClass.__init__:__init__ method is a generator diff --git a/pymode/libs/pylint/test/functional/init_not_called.py b/pymode/libs/pylint/test/functional/init_not_called.py new file mode 100644 index 00000000..38c0f7a1 --- /dev/null +++ b/pymode/libs/pylint/test/functional/init_not_called.py @@ -0,0 +1,64 @@ +# pylint: disable=R0903,import-error,missing-docstring,wrong-import-position +"""test for __init__ not called +""" +from __future__ import print_function + +class AAAA: # <3.0:[old-style-class] + """ancestor 1""" + + def __init__(self): + print('init', self) + +class BBBB: # <3.0:[old-style-class] + """ancestor 2""" + + def __init__(self): + print('init', self) + +class CCCC: # <3.0:[old-style-class,no-init] + """ancestor 3""" + + +class ZZZZ(AAAA, BBBB, CCCC): + """derived class""" + + def __init__(self): # [super-init-not-called] + AAAA.__init__(self) + +class NewStyleA(object): + """new style class""" + def __init__(self): + super(NewStyleA, self).__init__() + print('init', self) + +class NewStyleB(NewStyleA): + """derived new style class""" + def __init__(self): + super(NewStyleB, self).__init__() + +class NoInit(object): + """No __init__ defined""" + +class Init(NoInit): + """Don't complain for not calling the super __init__""" + + def __init__(self, arg): + self.arg = arg + +class NewStyleC(object): + """__init__ defined by assignemnt.""" + def xx_init(self): + """Initializer.""" + pass + + __init__ = xx_init + +class AssignedInit(NewStyleC): + """No init called.""" + def __init__(self): # [super-init-not-called] + self.arg = 0 + +from missing import Missing + +class UnknownBases(Missing): + """Don't emit no-init if the bases aren't known.""" diff --git a/pymode/libs/pylint/test/functional/init_not_called.txt b/pymode/libs/pylint/test/functional/init_not_called.txt new file mode 100644 index 00000000..b0bc4188 --- /dev/null +++ b/pymode/libs/pylint/test/functional/init_not_called.txt @@ -0,0 +1,6 @@ +old-style-class:6:AAAA:Old-style class defined. +old-style-class:12:BBBB:Old-style class defined. +no-init:18:CCCC:Class has no __init__ method +old-style-class:18:CCCC:Old-style class defined. +super-init-not-called:25:ZZZZ.__init__:__init__ method from base class 'BBBB' is not called +super-init-not-called:58:AssignedInit.__init__:__init__ method from base class 'NewStyleC' is not called diff --git a/pymode/libs/pylint/test/functional/invalid_all_object.py b/pymode/libs/pylint/test/functional/invalid_all_object.py new file mode 100644 index 00000000..4468c987 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_all_object.py @@ -0,0 +1,6 @@ +# pylint: disable=missing-docstring +__all__ = ( + 1, # [invalid-all-object] + lambda: None, # [invalid-all-object] + None, # [invalid-all-object] +) diff --git a/pymode/libs/pylint/test/functional/invalid_all_object.txt b/pymode/libs/pylint/test/functional/invalid_all_object.txt new file mode 100644 index 00000000..eaec65bf --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_all_object.txt @@ -0,0 +1,3 @@ +invalid-all-object:3::Invalid object '1' in __all__, must contain only strings +invalid-all-object:4::"Invalid object 'lambda : None' in __all__, must contain only strings" +invalid-all-object:5::Invalid object 'None' in __all__, must contain only strings \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/invalid_encoded_data.py b/pymode/libs/pylint/test/functional/invalid_encoded_data.py new file mode 100644 index 00000000..743031ce --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_encoded_data.py @@ -0,0 +1,5 @@ +# coding: utf-8 +"""Test data file with encoding errors.""" + + +STR = 'СуÑеÑтвительное' # [invalid-encoded-data] diff --git a/pymode/libs/pylint/test/functional/invalid_encoded_data.rc b/pymode/libs/pylint/test/functional/invalid_encoded_data.rc new file mode 100644 index 00000000..7aae89b5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_encoded_data.rc @@ -0,0 +1,3 @@ +[testoptions] +max_pyver=3.0 +except_implementations=Jython diff --git a/pymode/libs/pylint/test/functional/invalid_encoded_data.txt b/pymode/libs/pylint/test/functional/invalid_encoded_data.txt new file mode 100644 index 00000000..c6e69dc1 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_encoded_data.txt @@ -0,0 +1 @@ +invalid-encoded-data:5::"Cannot decode using encoding ""utf-8"", unexpected byte at position 11" diff --git a/pymode/libs/pylint/test/functional/invalid_exceptions_caught.py b/pymode/libs/pylint/test/functional/invalid_exceptions_caught.py new file mode 100644 index 00000000..1eca134d --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_exceptions_caught.py @@ -0,0 +1,109 @@ +"""Test for catching non-exceptions.""" +# pylint: disable=too-many-ancestors, no-absolute-import, import-error, multiple-imports,wrong-import-position +from __future__ import print_function + +import socket, binascii + +class MyException(object): + """Custom 'exception'.""" + +class MySecondException(object): + """Custom 'exception'.""" + +class MyGoodException(Exception): + """Custom exception.""" + +class MySecondGoodException(MyGoodException): + """Custom exception.""" + +class SkipException(socket.error): + """Not an exception for Python 2, but one in 3.""" + +class SecondSkipException(SkipException): + """Also a good exception.""" + +try: + 1 + 1 +except MyException: # [catching-non-exception] + print("caught") + +try: + 1 + 2 +# +1:[catching-non-exception,catching-non-exception] +except (MyException, MySecondException): + print("caught") + +try: + 1 + 3 +except MyGoodException: + print("caught") + +try: + 1 + 3 +except (MyGoodException, MySecondGoodException): + print("caught") + +try: + 1 + 3 +except (SkipException, SecondSkipException): + print("caught") + +try: + 1 + 42 +# +1:[catching-non-exception,catching-non-exception] +except (None, list()): + print("caught") + +try: + 1 + 24 +except None: # [catching-non-exception] + print("caught") + +EXCEPTION = None +EXCEPTION = ZeroDivisionError +try: + 1 + 46 +except EXCEPTION: + print("caught") + +try: + 1 + 42 +# +1:[catching-non-exception,catching-non-exception,catching-non-exception] +except (list([4, 5, 6]), None, ZeroDivisionError, 4): + print("caught") + +EXCEPTION_TUPLE = (ZeroDivisionError, OSError) +NON_EXCEPTION_TUPLE = (ZeroDivisionError, OSError, 4) + +try: + 1 + 42 +except EXCEPTION_TUPLE: + print("caught") + +try: + 1 + 42 +except NON_EXCEPTION_TUPLE: # [catching-non-exception] + print("caught") + +from missing_import import UnknownError +UNKNOWN_COMPONENTS = (ZeroDivisionError, UnknownError) + +try: + 1 + 42 +except UNKNOWN_COMPONENTS: + print("caught") + +try: + 1 + 42 +except binascii.Error: + print('builtin and detected') + +try: + 1 + 45 +except object: # [catching-non-exception] + print('caught') + +try: + 1 + 42 +except range: # [catching-non-exception] + print('caught') diff --git a/pymode/libs/pylint/test/functional/invalid_exceptions_caught.txt b/pymode/libs/pylint/test/functional/invalid_exceptions_caught.txt new file mode 100644 index 00000000..3cd4822f --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_exceptions_caught.txt @@ -0,0 +1,12 @@ +catching-non-exception:27::"Catching an exception which doesn't inherit from BaseException: MyException" +catching-non-exception:33::"Catching an exception which doesn't inherit from BaseException: MyException" +catching-non-exception:33::"Catching an exception which doesn't inherit from BaseException: MySecondException" +catching-non-exception:54::"Catching an exception which doesn't inherit from BaseException: None" +catching-non-exception:54::"Catching an exception which doesn't inherit from BaseException: list()" +catching-non-exception:59::"Catching an exception which doesn't inherit from BaseException: None" +catching-non-exception:72::"Catching an exception which doesn't inherit from BaseException: 4" +catching-non-exception:72::"Catching an exception which doesn't inherit from BaseException: None" +catching-non-exception:72::"Catching an exception which doesn't inherit from BaseException: list([4, 5, 6])" +catching-non-exception:85::"Catching an exception which doesn't inherit from BaseException: NON_EXCEPTION_TUPLE" +catching-non-exception:103::"Catching an exception which doesn't inherit from BaseException: object" +catching-non-exception:108::"Catching an exception which doesn't inherit from BaseException: range" diff --git a/pymode/libs/pylint/test/functional/invalid_exceptions_raised.py b/pymode/libs/pylint/test/functional/invalid_exceptions_raised.py new file mode 100644 index 00000000..118d3981 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_exceptions_raised.py @@ -0,0 +1,83 @@ +# pylint:disable=too-few-public-methods,old-style-class,no-init,import-error,missing-docstring, not-callable +"""test pb with exceptions and old/new style classes""" + + +class ValidException(Exception): + """Valid Exception.""" + +class OldStyleClass: + """Not an exception.""" + +class NewStyleClass(object): + """Not an exception.""" + + +def good_case(): + """raise""" + raise ValidException('hop') + +def good_case1(): + """zlib.error is defined in C module.""" + import zlib + raise zlib.error(4) + +def good_case2(): + """decimal.DivisionByZero is defined in C on Python 3.""" + import decimal + raise decimal.DivisionByZero(4) + +def good_case3(): + """io.BlockingIOError is defined in C.""" + import io + raise io.BlockingIOError + +def bad_case0(): + """raise""" + # +2:<3.0:[nonstandard-exception] + # +1:>=3.0:[raising-non-exception] + raise OldStyleClass('hop') + +def bad_case1(): + """raise""" + raise NewStyleClass() # [raising-non-exception] + +def bad_case2(): + """raise""" + # +2:<3.0:[nonstandard-exception] + # +1:>=3.0:[raising-non-exception] + raise OldStyleClass('hop') + +def bad_case3(): + """raise""" + raise NewStyleClass # [raising-non-exception] + +def bad_case4(): + """raise""" + raise NotImplemented('hop') # [notimplemented-raised] + +def bad_case5(): + """raise""" + raise 1 # [raising-bad-type] + +def bad_case6(): + """raise""" + raise None # [raising-bad-type] + +def bad_case7(): + """raise list""" + raise list # [raising-non-exception] + +def bad_case8(): + """raise tuple""" + raise tuple # [raising-non-exception] + +def bad_case9(): + """raise dict""" + raise dict # [raising-non-exception] + +def unknown_bases(): + """Don't emit when we don't know the bases.""" + from lala import bala + class MyException(bala): + pass + raise MyException diff --git a/pymode/libs/pylint/test/functional/invalid_exceptions_raised.txt b/pymode/libs/pylint/test/functional/invalid_exceptions_raised.txt new file mode 100644 index 00000000..47eb0fec --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_exceptions_raised.txt @@ -0,0 +1,12 @@ +nonstandard-exception:38:bad_case0:"Exception doesn't inherit from standard ""Exception"" class" +raising-non-exception:38:bad_case0:Raising a new style class which doesn't inherit from BaseException +raising-non-exception:42:bad_case1:Raising a new style class which doesn't inherit from BaseException +nonstandard-exception:48:bad_case2:"Exception doesn't inherit from standard ""Exception"" class" +raising-non-exception:48:bad_case2:Raising a new style class which doesn't inherit from BaseException +raising-non-exception:52:bad_case3:Raising a new style class which doesn't inherit from BaseException +notimplemented-raised:56:bad_case4:NotImplemented raised - should raise NotImplementedError +raising-bad-type:60:bad_case5:Raising int while only classes or instances are allowed +raising-bad-type:64:bad_case6:Raising NoneType while only classes or instances are allowed +raising-non-exception:68:bad_case7:Raising a new style class which doesn't inherit from BaseException +raising-non-exception:72:bad_case8:Raising a new style class which doesn't inherit from BaseException +raising-non-exception:76:bad_case9:Raising a new style class which doesn't inherit from BaseException \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/invalid_name.py b/pymode/libs/pylint/test/functional/invalid_name.py new file mode 100644 index 00000000..b4107b3a --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_name.py @@ -0,0 +1,31 @@ +""" Tests for invalid-name checker. """ +# pylint: disable=unused-import, no-absolute-import, wrong-import-position + +AAA = 24 +try: + import collections +except ImportError: + collections = None + +aaa = 42 # [invalid-name] +try: + import time +except ValueError: + time = None # [invalid-name] + +try: + from sys import argv, executable as python +except ImportError: + argv = 42 + python = 24 + +def test(): + """ Shouldn't emit an invalid-name here. """ + try: + import re + except ImportError: + re = None + return re + +def a(): # [invalid-name] + """yo""" diff --git a/pymode/libs/pylint/test/functional/invalid_name.txt b/pymode/libs/pylint/test/functional/invalid_name.txt new file mode 100644 index 00000000..b5e9b7e4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_name.txt @@ -0,0 +1,3 @@ +invalid-name:10::"Invalid constant name ""aaa""" +invalid-name:14::"Invalid constant name ""time""" +invalid-name:30:a:"Invalid function name ""a""" diff --git a/pymode/libs/pylint/test/functional/invalid_sequence_index.py b/pymode/libs/pylint/test/functional/invalid_sequence_index.py new file mode 100644 index 00000000..32b75f26 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_sequence_index.py @@ -0,0 +1,216 @@ +"""Errors for invalid sequence indices""" +# pylint: disable=too-few-public-methods, no-self-use + +__revision__ = 0 + +TESTLIST = [1, 2, 3] +TESTTUPLE = (1, 2, 3) +TESTSTR = '123' + +# getitem tests with bad indices +def function1(): + """list index is a function""" + return TESTLIST[id] # [invalid-sequence-index] + +def function2(): + """list index is None""" + return TESTLIST[None] # [invalid-sequence-index] + +def function3(): + """list index is a float expression""" + return TESTLIST[float(0)] # [invalid-sequence-index] + +def function4(): + """list index is a str constant""" + return TESTLIST['0'] # [invalid-sequence-index] + +def function5(): + """list index does not implement __index__""" + class NonIndexType(object): + """Class without __index__ method""" + pass + + return TESTLIST[NonIndexType()] # [invalid-sequence-index] + +def function6(): + """Tuple index is None""" + return TESTTUPLE[None] # [invalid-sequence-index] + +def function7(): + """String index is None""" + return TESTSTR[None] # [invalid-sequence-index] + +def function8(): + """Index of subclass of tuple is None""" + class TupleTest(tuple): + """Subclass of tuple""" + pass + return TupleTest()[None] # [invalid-sequence-index] + +# getitem tests with good indices +def function9(): + """list index is an int constant""" + return TESTLIST[0] # no error + +def function10(): + """list index is a integer expression""" + return TESTLIST[int(0.0)] # no error + +def function11(): + """list index is a slice""" + return TESTLIST[slice(1, 2, 3)] # no error + +def function12(): + """list index implements __index__""" + class IndexType(object): + """Class with __index__ method""" + def __index__(self): + """Allow objects of this class to be used as slice indices""" + return 0 + + return TESTLIST[IndexType()] # no error + +def function13(): + """list index implements __index__ in a superclass""" + class IndexType(object): + """Class with __index__ method""" + def __index__(self): + """Allow objects of this class to be used as slice indices""" + return 0 + + class IndexSubType(IndexType): + """Class with __index__ in parent""" + pass + + return TESTLIST[IndexSubType()] # no error + +def function14(): + """Tuple index is an int constant""" + return TESTTUPLE[0] + +def function15(): + """String index is an int constant""" + return TESTSTR[0] + +def function16(): + """Index of subclass of tuple is an int constant""" + class TupleTest(tuple): + """Subclass of tuple""" + pass + return TupleTest()[0] # no error + +def function17(): + """Index of subclass of tuple with custom __getitem__ is None""" + class TupleTest(tuple): + """Subclass of tuple with custom __getitem__""" + def __getitem__(self, index): + """Allow non-integer indices""" + return 0 + return TupleTest()[None] # no error + +def function18(): + """Index of subclass of tuple with __getitem__ in superclass is None""" + class TupleTest(tuple): + """Subclass of tuple with custom __getitem__""" + def __getitem__(self, index): + """Allow non-integer indices""" + return 0 + + class SubTupleTest(TupleTest): + """Subclass of a subclass of tuple""" + pass + + return SubTupleTest()[None] # no error + +# Test with set and delete statements +def function19(): + """Set with None and integer indices""" + TESTLIST[None] = 0 # [invalid-sequence-index] + TESTLIST[0] = 0 # no error + +def function20(): + """Delete with None and integer indicies""" + del TESTLIST[None] # [invalid-sequence-index] + del TESTLIST[0] # no error + +def function21(): + """Set and delete on a subclass of list""" + class ListTest(list): + """Inherit all list get/set/del handlers""" + pass + test = ListTest() + + # Set and delete with invalid indices + test[None] = 0 # [invalid-sequence-index] + del test[None] # [invalid-sequence-index] + + # Set and delete with valid indices + test[0] = 0 # no error + del test[0] # no error + +def function22(): + """Get, set, and delete on a subclass of list that overrides __setitem__""" + class ListTest(list): + """Override setitem but not get or del""" + def __setitem__(self, key, value): + pass + test = ListTest() + + # failure on the getitem with None + test[None][0] = 0 # [invalid-sequence-index] + # failure on the getitem with None + del test[None] # [invalid-sequence-index] + + test[0][0] = 0 # getitem with int and setitem with int, no error + test[None] = 0 # setitem overridden, no error + test[0] = 0 # setitem with int, no error + del test[0] # delitem with int, no error + +def function23(): + """Get, set, and delete on a subclass of list that overrides __delitem__""" + class ListTest(list): + """Override delitem but not get or set""" + def __delitem__(self, key): + pass + test = ListTest() + + # failure on the getitem with None + test[None][0] = 0 # [invalid-sequence-index] + # setitem with invalid index + test[None] = 0 # [invalid-sequence-index] + + test[0][0] = 0 # getitem with int and setitem with int, no error + test[0] = 0 # setitem with int, no error + del test[None] # delitem overriden, no error + del test[0] # delitem with int, no error + +def function24(): + """Get, set, and delete on a subclass of list that overrides __getitem__""" + class ListTest(list): + """Override gelitem but not del or set""" + def __getitem__(self, key): + pass + test = ListTest() + + # setitem with invalid index + test[None] = 0 # [invalid-sequence-index] + # delitem with invalid index + del test[None] # [invalid-sequence-index] + + test[None][0] = 0 # getitem overriden, no error + test[0][0] = 0 # getitem with int and setitem with int, no error + test[0] = 0 # setitem with int, no error + del test[0] # delitem with int, no error + +# Teest ExtSlice usage +def function25(): + """Extended slice used with a list""" + return TESTLIST[..., 0] # [invalid-sequence-index] + +def function26(): + """Extended slice used with an object that implements __getitem__""" + class ExtSliceTest(object): + """Permit extslice syntax by implementing __getitem__""" + def __getitem__(self, index): + return 0 + return ExtSliceTest()[..., 0] # no error diff --git a/pymode/libs/pylint/test/functional/invalid_sequence_index.txt b/pymode/libs/pylint/test/functional/invalid_sequence_index.txt new file mode 100644 index 00000000..204aebc9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_sequence_index.txt @@ -0,0 +1,20 @@ +invalid-sequence-index:13:function1:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:17:function2:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:21:function3:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:25:function4:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:33:function5:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:37:function6:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:41:function7:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:48:function8:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:128:function19:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:133:function20:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:144:function21:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:145:function21:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:159:function22:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:160:function22:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:162:function22:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:178:function23:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:180:function23:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:196:function24:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:198:function24:Sequence index is not an int, slice, or instance with __index__ +invalid-sequence-index:208:function25:Sequence index is not an int, slice, or instance with __index__ \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/invalid_slice_index.py b/pymode/libs/pylint/test/functional/invalid_slice_index.py new file mode 100644 index 00000000..63ba252b --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_slice_index.py @@ -0,0 +1,60 @@ +"""Errors for invalid slice indices""" +# pylint: disable=too-few-public-methods, no-self-use + + +TESTLIST = [1, 2, 3] + +# Invalid indices +def function1(): + """functions used as indices""" + return TESTLIST[id:id:] # [invalid-slice-index,invalid-slice-index] + +def function2(): + """strings used as indices""" + return TESTLIST['0':'1':] # [invalid-slice-index,invalid-slice-index] + +def function3(): + """class without __index__ used as index""" + + class NoIndexTest(object): + """Class with no __index__ method""" + pass + + return TESTLIST[NoIndexTest()::] # [invalid-slice-index] + +# Valid indices +def function4(): + """integers used as indices""" + return TESTLIST[0:0:0] # no error + +def function5(): + """None used as indices""" + return TESTLIST[None:None:None] # no error + +def function6(): + """class with __index__ used as index""" + class IndexTest(object): + """Class with __index__ method""" + def __index__(self): + """Allow objects of this class to be used as slice indices""" + return 0 + + return TESTLIST[IndexTest():None:None] # no error + +def function7(): + """class with __index__ in superclass used as index""" + class IndexType(object): + """Class with __index__ method""" + def __index__(self): + """Allow objects of this class to be used as slice indices""" + return 0 + + class IndexSubType(IndexType): + """Class with __index__ in parent""" + pass + + return TESTLIST[IndexSubType():None:None] # no error + +def function8(): + """slice object used as index""" + return TESTLIST[slice(1, 2, 3)] # no error diff --git a/pymode/libs/pylint/test/functional/invalid_slice_index.txt b/pymode/libs/pylint/test/functional/invalid_slice_index.txt new file mode 100644 index 00000000..cd4dc6e9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_slice_index.txt @@ -0,0 +1,5 @@ +invalid-slice-index:10:function1:Slice index is not an int, None, or instance with __index__ +invalid-slice-index:10:function1:Slice index is not an int, None, or instance with __index__ +invalid-slice-index:14:function2:Slice index is not an int, None, or instance with __index__ +invalid-slice-index:14:function2:Slice index is not an int, None, or instance with __index__ +invalid-slice-index:23:function3:Slice index is not an int, None, or instance with __index__ diff --git a/pymode/libs/pylint/test/functional/invalid_star_assignment_target.py b/pymode/libs/pylint/test/functional/invalid_star_assignment_target.py new file mode 100644 index 00000000..452f9a72 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_star_assignment_target.py @@ -0,0 +1,4 @@ +"""Test for *a = b """ + +*FIRST = [1, 2, 3] # [invalid-star-assignment-target] +(*FIRST, ) = [1, 2, 3] diff --git a/pymode/libs/pylint/test/functional/invalid_star_assignment_target.rc b/pymode/libs/pylint/test/functional/invalid_star_assignment_target.rc new file mode 100644 index 00000000..c093be20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_star_assignment_target.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/invalid_star_assignment_target.txt b/pymode/libs/pylint/test/functional/invalid_star_assignment_target.txt new file mode 100644 index 00000000..7a4c232f --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_star_assignment_target.txt @@ -0,0 +1 @@ +invalid-star-assignment-target:3::Starred assignment target must be in a list or tuple diff --git a/pymode/libs/pylint/test/functional/invalid_unary_operand_type.py b/pymode/libs/pylint/test/functional/invalid_unary_operand_type.py new file mode 100644 index 00000000..ee49c2c7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_unary_operand_type.py @@ -0,0 +1,51 @@ +"""Detect problems with invalid operands used on invalid objects.""" +# pylint: disable=missing-docstring,too-few-public-methods,invalid-name +# pylint: disable=unused-variable + +import collections + + +class Implemented(object): + def __invert__(self): + return 42 + def __pos__(self): + return 42 + def __neg__(self): + return 42 + + +def these_are_good(): + negative = -1 + negative1 = -1.0 + positive = +1 + positive2 = +1.0 + inverted = ~1 + not_int = not 1 + not_float = not 2.0 + not_string = not "" + not_list = not [] + not_dict = not {} + not_tuple = not (1, 2) + inverted_instance = ~Implemented() + positive_instance = +Implemented() + negative_instance = -Implemented() + not_instance = not Implemented() + + +def these_are_bad(): + invert_list = ~[] # [invalid-unary-operand-type] + invert_tuple = ~() # [invalid-unary-operand-type] + invert_dict = ~dict() # [invalid-unary-operand-type] + invert_dict_1 = ~{} # [invalid-unary-operand-type] + invert_set = ~set() # [invalid-unary-operand-type] + neg_set = -set() # [invalid-unary-operand-type] + neg_str = -"" # [invalid-unary-operand-type] + invert_str = ~"" # [invalid-unary-operand-type] + pos_str = +"" # [invalid-unary-operand-type] + class A(object): + pass + invert_func = ~(lambda: None) # [invalid-unary-operand-type] + invert_class = ~A # [invalid-unary-operand-type] + invert_instance = ~A() # [invalid-unary-operand-type] + invert_module = ~collections # [invalid-unary-operand-type] + invert_float = ~2.0 # [invalid-unary-operand-type] diff --git a/pymode/libs/pylint/test/functional/invalid_unary_operand_type.rc b/pymode/libs/pylint/test/functional/invalid_unary_operand_type.rc new file mode 100644 index 00000000..a7e3bb9f --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_unary_operand_type.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=4.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/invalid_unary_operand_type.txt b/pymode/libs/pylint/test/functional/invalid_unary_operand_type.txt new file mode 100644 index 00000000..22fa7271 --- /dev/null +++ b/pymode/libs/pylint/test/functional/invalid_unary_operand_type.txt @@ -0,0 +1,14 @@ +invalid-unary-operand-type:36:these_are_bad:"bad operand type for unary ~: list" +invalid-unary-operand-type:37:these_are_bad:"bad operand type for unary ~: tuple" +invalid-unary-operand-type:38:these_are_bad:"bad operand type for unary ~: dict" +invalid-unary-operand-type:39:these_are_bad:"bad operand type for unary ~: dict" +invalid-unary-operand-type:40:these_are_bad:"bad operand type for unary ~: set" +invalid-unary-operand-type:41:these_are_bad:"bad operand type for unary -: set" +invalid-unary-operand-type:42:these_are_bad:"bad operand type for unary -: str" +invalid-unary-operand-type:43:these_are_bad:"bad operand type for unary ~: str" +invalid-unary-operand-type:44:these_are_bad:"bad operand type for unary +: str" +invalid-unary-operand-type:47:these_are_bad:"bad operand type for unary ~: " +invalid-unary-operand-type:48:these_are_bad:"bad operand type for unary ~: A" +invalid-unary-operand-type:49:these_are_bad:"bad operand type for unary ~: A" +invalid-unary-operand-type:50:these_are_bad:"bad operand type for unary ~: collections" +invalid-unary-operand-type:51:these_are_bad:"bad operand type for unary ~: float" \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/iterable_context.py b/pymode/libs/pylint/test/functional/iterable_context.py new file mode 100644 index 00000000..05d85894 --- /dev/null +++ b/pymode/libs/pylint/test/functional/iterable_context.py @@ -0,0 +1,179 @@ +""" +Checks that primitive values are not used in an +iterating/mapping context. +""" +# pylint: disable=missing-docstring,invalid-name,too-few-public-methods,no-init,no-self-use,import-error,unused-argument,bad-mcs-method-argument,wrong-import-position +from __future__ import print_function + +# primitives +numbers = [1, 2, 3] + +for i in numbers: + pass + +for i in iter(numbers): + pass + +for i in "123": + pass + +for i in u"123": + pass + +for i in b"123": + pass + +for i in bytearray(b"123"): + pass + +for i in set(numbers): + pass + +for i in frozenset(numbers): + pass + +for i in dict(a=1, b=2): + pass + +# comprehensions +for i in [x for x in range(10)]: + pass + +for i in {x for x in range(1, 100, 2)}: + pass + +for i in {x: 10 - x for x in range(10)}: + pass + +# generators +def powers_of_two(): + k = 0 + while k < 10: + yield 2 ** k + k += 1 + +for i in powers_of_two(): + pass + +for i in powers_of_two: # [not-an-iterable] + pass + +# check for custom iterators +class A(object): + pass + +class B(object): + def __iter__(self): + return self + + def __next__(self): + return 1 + + def next(self): + return 1 + +class C(object): + "old-style iterator" + def __getitem__(self, k): + if k > 10: + raise IndexError + return k + 1 + + def __len__(self): + return 10 + +for i in C(): + print(i) + + +def test(*args): + print(args) + + +test(*A()) # [not-an-iterable] +test(*B()) +test(*B) # [not-an-iterable] +for i in A(): # [not-an-iterable] + pass +for i in B(): + pass +for i in B: # [not-an-iterable] + pass + +for i in range: # [not-an-iterable] + pass + +# check that primitive non-iterable types are catched +for i in True: # [not-an-iterable] + pass + +for i in None: # [not-an-iterable] + pass + +for i in 8.5: # [not-an-iterable] + pass + +for i in 10: # [not-an-iterable] + pass + + +# skip uninferable instances +from some_missing_module import Iterable + +class MyClass(Iterable): + pass + +m = MyClass() +for i in m: + print(i) + +# skip checks if statement is inside mixin/base/abstract class +class ManagedAccessViewMixin(object): + access_requirements = None + + def get_access_requirements(self): + return self.access_requirements + + def dispatch(self, *_args, **_kwargs): + klasses = self.get_access_requirements() + + # no error should be emitted here + for requirement in klasses: + print(requirement) + +class BaseType(object): + valid_values = None + + def validate(self, value): + if self.valid_values is None: + return True + else: + # error should not be emitted here + for v in self.valid_values: + if value == v: + return True + return False + +class AbstractUrlMarkManager(object): + def __init__(self): + self._lineparser = None + self._init_lineparser() + # error should not be emitted here + for line in self._lineparser: + print(line) + + def _init_lineparser(self): + raise NotImplementedError + +# class is not named as abstract +# but still is deduceably abstract +class UrlMarkManager(object): + def __init__(self): + self._lineparser = None + self._init_lineparser() + # error should not be emitted here + for line in self._lineparser: + print(line) + + def _init_lineparser(self): + raise NotImplementedError diff --git a/pymode/libs/pylint/test/functional/iterable_context.txt b/pymode/libs/pylint/test/functional/iterable_context.txt new file mode 100644 index 00000000..fbe14332 --- /dev/null +++ b/pymode/libs/pylint/test/functional/iterable_context.txt @@ -0,0 +1,10 @@ +not-an-iterable:58::Non-iterable value powers_of_two is used in an iterating context +not-an-iterable:93::Non-iterable value A() is used in an iterating context +not-an-iterable:95::Non-iterable value B is used in an iterating context +not-an-iterable:96::Non-iterable value A() is used in an iterating context +not-an-iterable:100::Non-iterable value B is used in an iterating context +not-an-iterable:103::Non-iterable value range is used in an iterating context +not-an-iterable:107::Non-iterable value True is used in an iterating context +not-an-iterable:110::Non-iterable value None is used in an iterating context +not-an-iterable:113::Non-iterable value 8.5 is used in an iterating context +not-an-iterable:116::Non-iterable value 10 is used in an iterating context diff --git a/pymode/libs/pylint/test/functional/iterable_context_py2.py b/pymode/libs/pylint/test/functional/iterable_context_py2.py new file mode 100644 index 00000000..8687f84a --- /dev/null +++ b/pymode/libs/pylint/test/functional/iterable_context_py2.py @@ -0,0 +1,18 @@ +""" +Checks that iterable metaclasses are recognized by pylint. +""" +# pylint: disable=missing-docstring,too-few-public-methods,no-init,no-self-use,unused-argument,bad-mcs-method-argument + +# metaclasses as iterables +class Meta(type): + def __iter__(self): + return iter((1, 2, 3)) + +class SomeClass(object): + __metaclass__ = Meta + + +for i in SomeClass: + print i +for i in SomeClass(): # [not-an-iterable] + print i diff --git a/pymode/libs/pylint/test/functional/iterable_context_py2.rc b/pymode/libs/pylint/test/functional/iterable_context_py2.rc new file mode 100644 index 00000000..61e01ea3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/iterable_context_py2.rc @@ -0,0 +1,3 @@ +[testoptions] +max_pyver=2.7 + diff --git a/pymode/libs/pylint/test/functional/iterable_context_py2.txt b/pymode/libs/pylint/test/functional/iterable_context_py2.txt new file mode 100644 index 00000000..8de579a3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/iterable_context_py2.txt @@ -0,0 +1 @@ +not-an-iterable:17::Non-iterable value SomeClass() is used in an iterating context diff --git a/pymode/libs/pylint/test/functional/iterable_context_py3.py b/pymode/libs/pylint/test/functional/iterable_context_py3.py new file mode 100644 index 00000000..cb2a5050 --- /dev/null +++ b/pymode/libs/pylint/test/functional/iterable_context_py3.py @@ -0,0 +1,18 @@ +""" +Checks that iterable metaclasses are recognized by pylint. +""" +# pylint: disable=missing-docstring,too-few-public-methods,no-init,no-self-use,unused-argument,bad-mcs-method-argument + +# metaclasses as iterables +class Meta(type): + def __iter__(self): + return iter((1, 2, 3)) + +class SomeClass(metaclass=Meta): + pass + + +for i in SomeClass: + print(i) +for i in SomeClass(): # [not-an-iterable] + print(i) diff --git a/pymode/libs/pylint/test/functional/iterable_context_py3.rc b/pymode/libs/pylint/test/functional/iterable_context_py3.rc new file mode 100644 index 00000000..9bf6df0e --- /dev/null +++ b/pymode/libs/pylint/test/functional/iterable_context_py3.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=3.0 + diff --git a/pymode/libs/pylint/test/functional/iterable_context_py3.txt b/pymode/libs/pylint/test/functional/iterable_context_py3.txt new file mode 100644 index 00000000..8de579a3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/iterable_context_py3.txt @@ -0,0 +1 @@ +not-an-iterable:17::Non-iterable value SomeClass() is used in an iterating context diff --git a/pymode/libs/pylint/test/functional/line_endings.py b/pymode/libs/pylint/test/functional/line_endings.py new file mode 100644 index 00000000..d6bc3fcf --- /dev/null +++ b/pymode/libs/pylint/test/functional/line_endings.py @@ -0,0 +1,4 @@ +"mixing line endings are not welcome" +# +1: [unexpected-line-ending-format, mixed-line-endings] +CONST = 1 + diff --git a/pymode/libs/pylint/test/functional/line_endings.rc b/pymode/libs/pylint/test/functional/line_endings.rc new file mode 100644 index 00000000..95532ea8 --- /dev/null +++ b/pymode/libs/pylint/test/functional/line_endings.rc @@ -0,0 +1,2 @@ +[Format] +expected-line-ending-format=LF diff --git a/pymode/libs/pylint/test/functional/line_endings.txt b/pymode/libs/pylint/test/functional/line_endings.txt new file mode 100644 index 00000000..a18a8723 --- /dev/null +++ b/pymode/libs/pylint/test/functional/line_endings.txt @@ -0,0 +1,2 @@ +mixed-line-endings:3::Mixed line endings LF and CRLF +unexpected-line-ending-format:3::Unexpected line ending format. There is 'CRLF' while it should be 'LF'. diff --git a/pymode/libs/pylint/test/functional/line_too_long.py b/pymode/libs/pylint/test/functional/line_too_long.py new file mode 100644 index 00000000..0baf10f8 --- /dev/null +++ b/pymode/libs/pylint/test/functional/line_too_long.py @@ -0,0 +1,26 @@ +# pylint: disable=invalid-encoded-data +# +1: [line-too-long] +##################################################################################################### +# +1: [line-too-long] +""" that one is too long tooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo loooooong""" + +# The next line is exactly 80 characters long. +A = '--------------------------------------------------------------------------' + +# Do not trigger the line-too-long warning if the only token that makes the +# line longer than 80 characters is a trailing pylint disable. +var = 'This line has a disable pragma and whitespace trailing beyond 80 chars. ' # pylint:disable=invalid-name + +# +1: [line-too-long] +badname = 'This line is already longer than 100 characters even without the pragma. Trust me. Please.' # pylint:disable=invalid-name + +# http://example.com/this/is/a/very/long/url?but=splitting&urls=is&a=pain&so=they&can=be&long + + +def function(): + # +3: [line-too-long] + """This is a docstring. + + That contains a very, very long line that exceeds the 100 characters limit by a good margin. So good? + """ + pass diff --git a/pymode/libs/pylint/test/functional/line_too_long.txt b/pymode/libs/pylint/test/functional/line_too_long.txt new file mode 100644 index 00000000..1f02bfdb --- /dev/null +++ b/pymode/libs/pylint/test/functional/line_too_long.txt @@ -0,0 +1,4 @@ +line-too-long:3::Line too long (101/100) +line-too-long:5::Line too long (104/100) +line-too-long:15::Line too long (102/100) +line-too-long:24::Line too long (105/100) diff --git a/pymode/libs/pylint/test/functional/logging_format_interpolation.py b/pymode/libs/pylint/test/functional/logging_format_interpolation.py new file mode 100644 index 00000000..5432d337 --- /dev/null +++ b/pymode/libs/pylint/test/functional/logging_format_interpolation.py @@ -0,0 +1,25 @@ +# pylint: disable=E1101, no-absolute-import, import-error,line-too-long, missing-docstring,wrong-import-order + +try: + import __builtin__ as builtins +except ImportError: + import builtins + +# Muck up the names in an effort to confuse... +import logging as renamed_logging +import os as logging + +FORMAT_STR = '{0}, {1}' + +# Statements that should be flagged: +renamed_logging.debug('{0}, {1}'.format(4, 5)) # [logging-format-interpolation] +renamed_logging.log(renamed_logging.DEBUG, 'msg: {}'.format('Run!')) # [logging-format-interpolation] +renamed_logging.debug(FORMAT_STR.format(4, 5)) # [logging-format-interpolation] +renamed_logging.log(renamed_logging.DEBUG, FORMAT_STR.format(4, 5)) # [logging-format-interpolation] + +# Statements that should not be flagged: +renamed_logging.debug(format(66, 'x')) +renamed_logging.debug(builtins.format(66, 'x')) +renamed_logging.log(renamed_logging.DEBUG, 'msg: Run!'.upper()) +logging.debug('{0}, {1}'.format(4, 5)) +logging.log(logging.DEBUG, 'msg: {}'.format('Run!')) diff --git a/pymode/libs/pylint/test/functional/logging_format_interpolation.txt b/pymode/libs/pylint/test/functional/logging_format_interpolation.txt new file mode 100644 index 00000000..75281658 --- /dev/null +++ b/pymode/libs/pylint/test/functional/logging_format_interpolation.txt @@ -0,0 +1,4 @@ +logging-format-interpolation:15::Use % formatting in logging functions and pass the % parameters as arguments +logging-format-interpolation:16::Use % formatting in logging functions and pass the % parameters as arguments +logging-format-interpolation:17::Use % formatting in logging functions and pass the % parameters as arguments +logging-format-interpolation:18::Use % formatting in logging functions and pass the % parameters as arguments diff --git a/pymode/libs/pylint/test/functional/logging_not_lazy.py b/pymode/libs/pylint/test/functional/logging_not_lazy.py new file mode 100644 index 00000000..b843431e --- /dev/null +++ b/pymode/libs/pylint/test/functional/logging_not_lazy.py @@ -0,0 +1,17 @@ +# pylint: disable=missing-docstring,no-member,deprecated-method + +# Muck up the names in an effort to confuse... +import logging as renamed_logging +import os as logging + +# Statements that should be flagged: +renamed_logging.warn('%s, %s' % (4, 5)) # [logging-not-lazy] +renamed_logging.exception('%s' % 'Exceptional!') # [logging-not-lazy] +renamed_logging.log(renamed_logging.INFO, 'msg: %s' % 'Run!') # [logging-not-lazy] + +# Statements that should not be flagged: +renamed_logging.warn('%s, %s', 4, 5) +renamed_logging.log(renamed_logging.INFO, 'msg: %s', 'Run!') +renamed_logging.warn('%s' + ' the rest of a single string') +logging.warn('%s, %s' % (4, 5)) +logging.log(logging.INFO, 'msg: %s' % 'Run!') diff --git a/pymode/libs/pylint/test/functional/logging_not_lazy.txt b/pymode/libs/pylint/test/functional/logging_not_lazy.txt new file mode 100644 index 00000000..60c6c590 --- /dev/null +++ b/pymode/libs/pylint/test/functional/logging_not_lazy.txt @@ -0,0 +1,3 @@ +logging-not-lazy:8::Specify string format arguments as logging function parameters +logging-not-lazy:9::Specify string format arguments as logging function parameters +logging-not-lazy:10::Specify string format arguments as logging function parameters diff --git a/pymode/libs/pylint/test/functional/long_lines_with_utf8.py b/pymode/libs/pylint/test/functional/long_lines_with_utf8.py new file mode 100644 index 00000000..a1d90edc --- /dev/null +++ b/pymode/libs/pylint/test/functional/long_lines_with_utf8.py @@ -0,0 +1,7 @@ +# coding: utf-8 +"""Test data file files with non-ASCII content.""" + + +THIS_IS_A_LONG_VARIABLE_NAME = 'СущеÑтвительное ЧаÑтица' # but the line is okay + +THIS_IS_A_VERY_LONG_VARIABLE_NAME = 'СущеÑтвительное ЧаÑтица' # and the line is not okay # [line-too-long] diff --git a/pymode/libs/pylint/test/functional/long_lines_with_utf8.txt b/pymode/libs/pylint/test/functional/long_lines_with_utf8.txt new file mode 100644 index 00000000..42a7976a --- /dev/null +++ b/pymode/libs/pylint/test/functional/long_lines_with_utf8.txt @@ -0,0 +1 @@ +line-too-long:7::Line too long (108/100) diff --git a/pymode/libs/pylint/test/functional/mapping_context.py b/pymode/libs/pylint/test/functional/mapping_context.py new file mode 100644 index 00000000..6968994f --- /dev/null +++ b/pymode/libs/pylint/test/functional/mapping_context.py @@ -0,0 +1,97 @@ +""" +Checks that only valid values are used in a mapping context. +""" +# pylint: disable=missing-docstring,invalid-name,too-few-public-methods,no-self-use,import-error,wrong-import-position +from __future__ import print_function + + +def test(**kwargs): + print(kwargs) + + +# dictionary value/comprehension +dict_value = dict(a=1, b=2, c=3) +dict_comp = {chr(x): x for x in range(256)} +test(**dict_value) +test(**dict_comp) + + +# in order to be used in kwargs custom mapping class should define +# __iter__(), __getitem__(key) and keys(). +class CustomMapping(object): + def __init__(self): + self.data = dict(a=1, b=2, c=3, d=4, e=5) + + def __getitem__(self, key): + return self.data[key] + + def keys(self): + return self.data.keys() + +test(**CustomMapping()) +test(**CustomMapping) # [not-a-mapping] + +class NotMapping(object): + pass + +test(**NotMapping()) # [not-a-mapping] + +# skip checks if statement is inside mixin/base/abstract class +class SomeMixin(object): + kwargs = None + + def get_kwargs(self): + return self.kwargs + + def run(self, **kwargs): + print(kwargs) + + def dispatch(self): + kws = self.get_kwargs() + self.run(**kws) + +class AbstractThing(object): + kwargs = None + + def get_kwargs(self): + return self.kwargs + + def run(self, **kwargs): + print(kwargs) + + def dispatch(self): + kws = self.get_kwargs() + self.run(**kws) + +class BaseThing(object): + kwargs = None + + def get_kwargs(self): + return self.kwargs + + def run(self, **kwargs): + print(kwargs) + + def dispatch(self): + kws = self.get_kwargs() + self.run(**kws) + +# abstract class +class Thing(object): + def get_kwargs(self): + raise NotImplementedError + + def run(self, **kwargs): + print(kwargs) + + def dispatch(self): + kwargs = self.get_kwargs() + self.run(**kwargs) + +# skip uninferable instances +from some_missing_module import Mapping + +class MyClass(Mapping): + pass + +test(**MyClass()) diff --git a/pymode/libs/pylint/test/functional/mapping_context.txt b/pymode/libs/pylint/test/functional/mapping_context.txt new file mode 100644 index 00000000..201da1a6 --- /dev/null +++ b/pymode/libs/pylint/test/functional/mapping_context.txt @@ -0,0 +1,2 @@ +not-a-mapping:32::Non-mapping value CustomMapping is used in a mapping context +not-a-mapping:37::Non-mapping value NotMapping() is used in a mapping context diff --git a/pymode/libs/pylint/test/functional/mapping_context_py2.py b/pymode/libs/pylint/test/functional/mapping_context_py2.py new file mode 100644 index 00000000..afe4400e --- /dev/null +++ b/pymode/libs/pylint/test/functional/mapping_context_py2.py @@ -0,0 +1,19 @@ +# pylint: disable=missing-docstring,invalid-name,too-few-public-methods +from __future__ import print_function + + +def test(**kwargs): + print(kwargs) + +# metaclasses as mappings +class Meta(type): + def __getitem__(self, key): + return ord(key) + def keys(self): + return ['a', 'b', 'c'] + +class SomeClass(object): + __metaclass__ = Meta + +test(**SomeClass) +test(**SomeClass()) # [not-a-mapping] diff --git a/pymode/libs/pylint/test/functional/mapping_context_py2.rc b/pymode/libs/pylint/test/functional/mapping_context_py2.rc new file mode 100644 index 00000000..61e01ea3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/mapping_context_py2.rc @@ -0,0 +1,3 @@ +[testoptions] +max_pyver=2.7 + diff --git a/pymode/libs/pylint/test/functional/mapping_context_py2.txt b/pymode/libs/pylint/test/functional/mapping_context_py2.txt new file mode 100644 index 00000000..59cca6c4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/mapping_context_py2.txt @@ -0,0 +1 @@ +not-a-mapping:19::Non-mapping value SomeClass() is used in a mapping context diff --git a/pymode/libs/pylint/test/functional/mapping_context_py3.py b/pymode/libs/pylint/test/functional/mapping_context_py3.py new file mode 100644 index 00000000..042d4d02 --- /dev/null +++ b/pymode/libs/pylint/test/functional/mapping_context_py3.py @@ -0,0 +1,19 @@ +# pylint: disable=missing-docstring,invalid-name,too-few-public-methods,no-self-use +from __future__ import print_function + +def test(**kwargs): + print(kwargs) + +# metaclasses as mappings +class Meta(type): + def __getitem__(cls, key): + return ord(key) + + def keys(cls): + return ['a', 'b', 'c'] + +class SomeClass(metaclass=Meta): + pass + +test(**SomeClass) +test(**SomeClass()) # [not-a-mapping] diff --git a/pymode/libs/pylint/test/functional/mapping_context_py3.rc b/pymode/libs/pylint/test/functional/mapping_context_py3.rc new file mode 100644 index 00000000..9bf6df0e --- /dev/null +++ b/pymode/libs/pylint/test/functional/mapping_context_py3.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=3.0 + diff --git a/pymode/libs/pylint/test/functional/mapping_context_py3.txt b/pymode/libs/pylint/test/functional/mapping_context_py3.txt new file mode 100644 index 00000000..59cca6c4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/mapping_context_py3.txt @@ -0,0 +1 @@ +not-a-mapping:19::Non-mapping value SomeClass() is used in a mapping context diff --git a/pymode/libs/pylint/test/functional/member_checks.py b/pymode/libs/pylint/test/functional/member_checks.py new file mode 100644 index 00000000..9ae71766 --- /dev/null +++ b/pymode/libs/pylint/test/functional/member_checks.py @@ -0,0 +1,171 @@ +# pylint: disable=print-statement,missing-docstring,no-self-use,too-few-public-methods,bare-except,broad-except +# pylint: disable=using-constant-test,expression-not-assigned, redefined-variable-type +from __future__ import print_function + +class Provider(object): + """provide some attributes and method""" + cattr = 4 + def __init__(self): + self.attr = 4 + def method(self, val): + """impressive method""" + return self.attr * val + def hophop(self): + """hop method""" + print('hop hop hop', self) + + +class Client(object): + """use provider class""" + + def __init__(self): + self._prov = Provider() + self._prov_attr = Provider.cattr + self._prov_attr2 = Provider.cattribute # [no-member] + self.set_later = 0 + + def set_set_later(self, value): + """set set_later attribute (introduce an inference ambiguity)""" + self.set_later = value + + def use_method(self): + """use provider's method""" + self._prov.hophop() + self._prov.hophophop() # [no-member] + + def use_attr(self): + """use provider's attr""" + print(self._prov.attr) + print(self._prov.attribute) # [no-member] + + def debug(self): + """print debug information""" + print(self.__class__.__name__) + print(self.__doc__) + print(self.__dict__) + print(self.__module__) + + def test_bt_types(self): + """test access to unexistant member of builtin types""" + lis = [] + lis.apppend(self) # [no-member] + dic = {} + dic.set(self) # [no-member] + tup = () + tup.append(self) # [no-member] + string = 'toto' + print(string.loower()) # [no-member] + integer = 1 + print(integer.whatever) # [no-member] + + def test_no_false_positives(self): + none = None + print(none.whatever) + # No misssing in the parents. + super(Client, self).misssing() # [no-member] + + +class Mixin(object): + """No no-member should be emitted for mixins.""" + +class Getattr(object): + """no-member shouldn't be emitted for classes with dunder getattr.""" + + def __getattr__(self, attr): + return self.__dict__[attr] + + +class Getattribute(object): + """no-member shouldn't be emitted for classes with dunder getattribute.""" + + def __getattribute__(self, attr): + return 42 + +print(object.__init__) +print(property.__init__) +print(Client().set_later.lower()) # [no-member] +print(Mixin().nanana()) +print(Getattr().nananan()) +print(Getattribute().batman()) + +try: + Client().missing_method() +except AttributeError: + pass + +try: + Client().indeed() # [no-member] +except ImportError: + pass + +try: + Client.missing() +except AttributeError: + Client.missing() # [no-member] + +try: + Client.missing() +except AttributeError: + try: + Client.missing() # [no-member] + except ValueError: + pass + +try: + if Client: + Client().missing() +except AttributeError: + pass + +try: + Client().indeed() +except AttributeError: + try: + Client.missing() # [no-member] + except Exception: + pass + + +class SuperChecks(str, str): # pylint: disable=duplicate-bases + """Don't fail when the MRO is invalid.""" + def test(self): + super(SuperChecks, self).lalala() + +type(Client()).ala +type({}).bala +type('').portocala + + +def socket_false_positive(): + """Test a regression + Version used: + + - Pylint 0.10.0 + - Logilab common 0.15.0 + - Logilab astroid 0.15.1 + + False E1101 positive, line 23: + Instance of '_socketobject' has no 'connect' member + """ + + import socket + sckt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sckt.connect(('127.0.0.1', 80)) + sckt.close() + + +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() + + +class NoDunderNameInInstance(object): + """Emit a warning when accessing __name__ from an instance.""" + def __init__(self): + self.var = self.__name__ # [no-member] diff --git a/pymode/libs/pylint/test/functional/member_checks.txt b/pymode/libs/pylint/test/functional/member_checks.txt new file mode 100644 index 00000000..8acc7762 --- /dev/null +++ b/pymode/libs/pylint/test/functional/member_checks.txt @@ -0,0 +1,15 @@ +no-member:24:Client.__init__:Class 'Provider' has no 'cattribute' member:INFERENCE +no-member:34:Client.use_method:Instance of 'Provider' has no 'hophophop' member:INFERENCE +no-member:39:Client.use_attr:Instance of 'Provider' has no 'attribute' member:INFERENCE +no-member:51:Client.test_bt_types:Instance of 'list' has no 'apppend' member:INFERENCE +no-member:53:Client.test_bt_types:Instance of 'dict' has no 'set' member:INFERENCE +no-member:55:Client.test_bt_types:Instance of 'tuple' has no 'append' member:INFERENCE +no-member:57:Client.test_bt_types:Instance of 'str' has no 'loower' member:INFERENCE +no-member:59:Client.test_bt_types:Instance of 'int' has no 'whatever' member:INFERENCE +no-member:65:Client.test_no_false_positives:Super of 'Client' has no 'misssing' member:INFERENCE +no-member:86::Instance of 'int' has no 'lower' member:INFERENCE_FAILURE +no-member:97::Instance of 'Client' has no 'indeed' member:INFERENCE +no-member:104::Class 'Client' has no 'missing' member:INFERENCE +no-member:110::Class 'Client' has no 'missing' member:INFERENCE +no-member:124::Class 'Client' has no 'missing' member:INFERENCE +no-member:171:NoDunderNameInInstance.__init__:Instance of 'NoDunderNameInInstance' has no '__name__' member:INFERENCE diff --git a/pymode/libs/pylint/test/functional/membership_protocol.py b/pymode/libs/pylint/test/functional/membership_protocol.py new file mode 100644 index 00000000..6b514d84 --- /dev/null +++ b/pymode/libs/pylint/test/functional/membership_protocol.py @@ -0,0 +1,123 @@ +# pylint: disable=missing-docstring,pointless-statement,expression-not-assigned,too-few-public-methods,import-error,no-init,wrong-import-position + +# standard types +1 in [1, 2, 3] +1 in {'a': 1, 'b': 2} +1 in {1, 2, 3} +1 in (1, 2, 3) +'1' in "123" +'1' in u"123" +'1' in bytearray(b"123") +1 in frozenset([1, 2, 3]) + +# comprehensions +1 in [x ** 2 % 10 for x in range(10)] +1 in {x ** 2 % 10 for x in range(10)} +1 in {x: x ** 2 % 10 for x in range(10)} + +# iterators +1 in iter([1, 2, 3]) + +# generator +def count(upto=float("inf")): + i = 0 + while True: + if i > upto: + break + yield i + i += 1 + +10 in count(upto=10) + +# custom instance +class UniversalContainer(object): + def __contains__(self, key): + return True + +42 in UniversalContainer() + +# custom iterable +class CustomIterable(object): + def __iter__(self): + return iter((1, 2, 3)) +3 in CustomIterable() + +# old-style iterable +class OldStyleIterable(object): + def __getitem__(self, key): + if key < 10: + return 2 ** key + else: + raise IndexError("bad index") +64 in OldStyleIterable() + +# do not emit warning if class has unknown bases +from some_missing_module import ImportedClass + +class MaybeIterable(ImportedClass): + pass + +10 in MaybeIterable() + +# do not emit warning inside mixins/abstract/base classes +class UsefulMixin(object): + stuff = None + + def get_stuff(self): + return self.stuff + + def act(self, thing): + stuff = self.get_stuff() + if thing in stuff: + pass + +class BaseThing(object): + valid_values = None + + def validate(self, value): + if self.valid_values is None: + return True + else: + # error should not be emitted here + return value in self.valid_values + +class AbstractThing(object): + valid_values = None + + def validate(self, value): + if self.valid_values is None: + return True + else: + # error should not be emitted here + return value in self.valid_values + +# class is not named as abstract +# but still is deduceably abstract +class Thing(object): + valid_values = None + + def __init__(self): + self._init_values() + + def validate(self, value): + if self.valid_values is None: + return True + else: + # error should not be emitted here + return value in self.valid_values + + def _init_values(self): + raise NotImplementedError + +# error cases +42 in 42 # [unsupported-membership-test] +42 not in None # [unsupported-membership-test] +42 in 8.5 # [unsupported-membership-test] + +class EmptyClass(object): + pass + +42 not in EmptyClass() # [unsupported-membership-test] +42 in EmptyClass # [unsupported-membership-test] +42 not in count # [unsupported-membership-test] +42 in range # [unsupported-membership-test] diff --git a/pymode/libs/pylint/test/functional/membership_protocol.txt b/pymode/libs/pylint/test/functional/membership_protocol.txt new file mode 100644 index 00000000..edb2227a --- /dev/null +++ b/pymode/libs/pylint/test/functional/membership_protocol.txt @@ -0,0 +1,7 @@ +unsupported-membership-test:113::Value '42' doesn't support membership test +unsupported-membership-test:114::Value 'None' doesn't support membership test +unsupported-membership-test:115::Value '8.5' doesn't support membership test +unsupported-membership-test:120::Value 'EmptyClass()' doesn't support membership test +unsupported-membership-test:121::Value 'EmptyClass' doesn't support membership test +unsupported-membership-test:122::Value 'count' doesn't support membership test +unsupported-membership-test:123::Value 'range' doesn't support membership test diff --git a/pymode/libs/pylint/test/functional/membership_protocol_py2.py b/pymode/libs/pylint/test/functional/membership_protocol_py2.py new file mode 100644 index 00000000..1a016377 --- /dev/null +++ b/pymode/libs/pylint/test/functional/membership_protocol_py2.py @@ -0,0 +1,36 @@ +# pylint: disable=missing-docstring,too-few-public-methods,no-init,no-self-use,unused-argument,pointless-statement,expression-not-assigned,undefined-variable + +# metaclasses that support membership test protocol +class MetaIterable(type): + def __iter__(cls): + return iter((1, 2, 3)) + +class MetaOldIterable(type): + def __getitem__(cls, key): + if key < 10: + return key ** 2 + else: + raise IndexError("bad index") + +class MetaContainer(type): + def __contains__(cls, key): + return False + + +class IterableClass(object): + __metaclass__ = MetaIterable + +class OldIterableClass(object): + __metaclass__ = MetaOldIterable + +class ContainerClass(object): + __metaclass__ = MetaContainer + + +def test(): + 1 in IterableClass + 1 in OldIterableClass + 1 in ContainerClass + 1 in IterableClass() # [unsupported-membership-test] + 1 in OldIterableClass() # [unsupported-membership-test] + 1 in ContainerClass() # [unsupported-membership-test] diff --git a/pymode/libs/pylint/test/functional/membership_protocol_py2.rc b/pymode/libs/pylint/test/functional/membership_protocol_py2.rc new file mode 100644 index 00000000..c78f32fa --- /dev/null +++ b/pymode/libs/pylint/test/functional/membership_protocol_py2.rc @@ -0,0 +1,3 @@ +[testoptions] +max_pyver=3.0 + diff --git a/pymode/libs/pylint/test/functional/membership_protocol_py2.txt b/pymode/libs/pylint/test/functional/membership_protocol_py2.txt new file mode 100644 index 00000000..4ba75755 --- /dev/null +++ b/pymode/libs/pylint/test/functional/membership_protocol_py2.txt @@ -0,0 +1,3 @@ +unsupported-membership-test:34:test:Value 'IterableClass()' doesn't support membership test +unsupported-membership-test:35:test:Value 'OldIterableClass()' doesn't support membership test +unsupported-membership-test:36:test:Value 'ContainerClass()' doesn't support membership test diff --git a/pymode/libs/pylint/test/functional/membership_protocol_py3.py b/pymode/libs/pylint/test/functional/membership_protocol_py3.py new file mode 100644 index 00000000..6a77f203 --- /dev/null +++ b/pymode/libs/pylint/test/functional/membership_protocol_py3.py @@ -0,0 +1,36 @@ +# pylint: disable=missing-docstring,too-few-public-methods,no-init,no-self-use,unused-argument,pointless-statement,expression-not-assigned + +# metaclasses that support membership test protocol +class MetaIterable(type): + def __iter__(cls): + return iter((1, 2, 3)) + +class MetaOldIterable(type): + def __getitem__(cls, key): + if key < 10: + return key ** 2 + else: + raise IndexError("bad index") + +class MetaContainer(type): + def __contains__(cls, key): + return False + + +class IterableClass(metaclass=MetaOldIterable): + pass + +class OldIterableClass(metaclass=MetaOldIterable): + pass + +class ContainerClass(metaclass=MetaContainer): + pass + + +def test(): + 1 in IterableClass + 1 in OldIterableClass + 1 in ContainerClass + 1 in IterableClass() # [unsupported-membership-test] + 1 in OldIterableClass() # [unsupported-membership-test] + 1 in ContainerClass() # [unsupported-membership-test] diff --git a/pymode/libs/pylint/test/functional/membership_protocol_py3.rc b/pymode/libs/pylint/test/functional/membership_protocol_py3.rc new file mode 100644 index 00000000..9bf6df0e --- /dev/null +++ b/pymode/libs/pylint/test/functional/membership_protocol_py3.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=3.0 + diff --git a/pymode/libs/pylint/test/functional/membership_protocol_py3.txt b/pymode/libs/pylint/test/functional/membership_protocol_py3.txt new file mode 100644 index 00000000..4ba75755 --- /dev/null +++ b/pymode/libs/pylint/test/functional/membership_protocol_py3.txt @@ -0,0 +1,3 @@ +unsupported-membership-test:34:test:Value 'IterableClass()' doesn't support membership test +unsupported-membership-test:35:test:Value 'OldIterableClass()' doesn't support membership test +unsupported-membership-test:36:test:Value 'ContainerClass()' doesn't support membership test diff --git a/pymode/libs/pylint/test/functional/method_hidden.py b/pymode/libs/pylint/test/functional/method_hidden.py new file mode 100644 index 00000000..e0588aff --- /dev/null +++ b/pymode/libs/pylint/test/functional/method_hidden.py @@ -0,0 +1,16 @@ +# pylint: disable=too-few-public-methods,print-statement +"""check method hidding ancestor attribute +""" +from __future__ import print_function + +class Abcd(object): + """dummy""" + def __init__(self): + self.abcd = 1 + +class Cdef(Abcd): + """dummy""" + def abcd(self): # [method-hidden] + """test + """ + print(self) diff --git a/pymode/libs/pylint/test/functional/method_hidden.txt b/pymode/libs/pylint/test/functional/method_hidden.txt new file mode 100644 index 00000000..7dfb8d3e --- /dev/null +++ b/pymode/libs/pylint/test/functional/method_hidden.txt @@ -0,0 +1 @@ +method-hidden:13:Cdef.abcd:An attribute defined in functional.method_hidden line 9 hides this method diff --git a/pymode/libs/pylint/test/functional/misplaced_bare_raise.py b/pymode/libs/pylint/test/functional/misplaced_bare_raise.py new file mode 100644 index 00000000..30b49bd2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/misplaced_bare_raise.py @@ -0,0 +1,75 @@ +# pylint: disable=missing-docstring, broad-except, unreachable +# pylint: disable=unused-variable, too-few-public-methods, invalid-name + +try: + raise # [misplaced-bare-raise] +except Exception: + pass + +try: + pass +except Exception: + raise + +# pylint: disable=misplaced-comparison-constant +try: + pass +except Exception: + if 1 == 2: + raise + +def test(): + try: + pass + except Exception: + def chest(): + try: + pass + except Exception: + raise + raise + +def test1(): + try: + if 1 > 2: + def best(): + raise # [misplaced-bare-raise] + except Exception: + pass + raise # [misplaced-bare-raise] +raise # [misplaced-bare-raise] + +try: + pass +finally: + # This might work or might not to, depending if there's + # an error raised inside the try block. But relying on this + # behaviour can be error prone, so we decided to warn + # against it. + raise # [misplaced-bare-raise] + + +class A(object): + try: + pass + except Exception: + raise + raise # [misplaced-bare-raise] + +# This works in Python 2, but the intent is nevertheless +# unclear. It will also not work on Python 3, so it's best +# not to rely on it. +exc = None +try: + 1/0 +except ZeroDivisionError as exc: + pass +if exc: + raise # [misplaced-bare-raise] + +# Don't emit if we're in ``__exit__``. +class ContextManager(object): + def __enter__(self): + return self + def __exit__(self, *args): + raise diff --git a/pymode/libs/pylint/test/functional/misplaced_bare_raise.txt b/pymode/libs/pylint/test/functional/misplaced_bare_raise.txt new file mode 100644 index 00000000..fb39640a --- /dev/null +++ b/pymode/libs/pylint/test/functional/misplaced_bare_raise.txt @@ -0,0 +1,7 @@ +misplaced-bare-raise:5::The raise statement is not inside an except clause +misplaced-bare-raise:36:test1.best:The raise statement is not inside an except clause +misplaced-bare-raise:39:test1:The raise statement is not inside an except clause +misplaced-bare-raise:40::The raise statement is not inside an except clause +misplaced-bare-raise:49::The raise statement is not inside an except clause +misplaced-bare-raise:57:A:The raise statement is not inside an except clause +misplaced-bare-raise:68::The raise statement is not inside an except clause diff --git a/pymode/libs/pylint/test/functional/misplaced_comparison_constant.py b/pymode/libs/pylint/test/functional/misplaced_comparison_constant.py new file mode 100644 index 00000000..45a03d1e --- /dev/null +++ b/pymode/libs/pylint/test/functional/misplaced_comparison_constant.py @@ -0,0 +1,36 @@ +"""Check that the constants are on the right side of the comparisons""" + +# pylint: disable=singleton-comparison, missing-docstring, too-few-public-methods + +class MyClass(object): + def __init__(self): + self.attr = 1 + + def dummy_return(self): + return self.attr + +def dummy_return(): + return 2 + +def bad_comparisons(): + """this is not ok""" + instance = MyClass() + for i in range(10): + if 5 <= i: # [misplaced-comparison-constant] + pass + if 1 == i: # [misplaced-comparison-constant] + pass + if 3 < dummy_return(): # [misplaced-comparison-constant] + pass + if 4 != instance.dummy_return(): # [misplaced-comparison-constant] + pass + if 1 == instance.attr: # [misplaced-comparison-constant] + pass + if "aaa" == instance.attr: # [misplaced-comparison-constant] + pass + +def good_comparison(): + """this is ok""" + for i in range(10): + if i == 5: + pass diff --git a/pymode/libs/pylint/test/functional/misplaced_comparison_constant.txt b/pymode/libs/pylint/test/functional/misplaced_comparison_constant.txt new file mode 100644 index 00000000..a7f0d956 --- /dev/null +++ b/pymode/libs/pylint/test/functional/misplaced_comparison_constant.txt @@ -0,0 +1,6 @@ +misplaced-comparison-constant:19:bad_comparisons:Comparison should be i >= 5 +misplaced-comparison-constant:21:bad_comparisons:Comparison should be i == 1 +misplaced-comparison-constant:23:bad_comparisons:Comparison should be dummy_return() > 3 +misplaced-comparison-constant:25:bad_comparisons:Comparison should be instance.dummy_return() != 4 +misplaced-comparison-constant:27:bad_comparisons:Comparison should be instance.attr == 1 +misplaced-comparison-constant:29:bad_comparisons:Comparison should be instance.attr == 'aaa' diff --git a/pymode/libs/pylint/test/functional/misplaced_future.py b/pymode/libs/pylint/test/functional/misplaced_future.py new file mode 100644 index 00000000..b4317d96 --- /dev/null +++ b/pymode/libs/pylint/test/functional/misplaced_future.py @@ -0,0 +1,6 @@ +"""Test that __future__ is not the first statement after the docstring.""" +import collections +from __future__ import print_function # [misplaced-future] +from __future__ import with_statement + +DATA = collections diff --git a/pymode/libs/pylint/test/functional/misplaced_future.txt b/pymode/libs/pylint/test/functional/misplaced_future.txt new file mode 100644 index 00000000..bc874cf1 --- /dev/null +++ b/pymode/libs/pylint/test/functional/misplaced_future.txt @@ -0,0 +1 @@ +misplaced-future:3::"__future__ import is not the first non docstring statement" \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/missing_docstring.py b/pymode/libs/pylint/test/functional/missing_docstring.py new file mode 100644 index 00000000..2d6f1fd9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/missing_docstring.py @@ -0,0 +1,54 @@ +# [missing-docstring] +# pylint: disable=too-few-public-methods + +def public_documented(): + """It has a docstring.""" + + +def _private_undocumented(): + # Doesn't need a docstring + pass + + +def _private_documented(): + """It has a docstring.""" + + +class ClassDocumented(object): + """It has a docstring.""" + + +class ClassUndocumented(object): # [missing-docstring] + pass + + +def public_undocumented(): # [missing-docstring] + pass + + +def __sizeof__(): + # Special + pass + + +def __mangled(): + pass + + +class Property(object): + """Don't warn about setters and deleters.""" + + def __init__(self): + self._value = None + + @property + def test(self): + """Default docstring for setters and deleters.""" + + @test.setter + def test(self, value): + self._value = value + + @test.deleter + def test(self): + pass diff --git a/pymode/libs/pylint/test/functional/missing_docstring.txt b/pymode/libs/pylint/test/functional/missing_docstring.txt new file mode 100644 index 00000000..2680319b --- /dev/null +++ b/pymode/libs/pylint/test/functional/missing_docstring.txt @@ -0,0 +1,3 @@ +missing-docstring:1::Missing module docstring +missing-docstring:21:ClassUndocumented:Missing class docstring +missing-docstring:25:public_undocumented:Missing function docstring diff --git a/pymode/libs/pylint/test/functional/missing_final_newline.py b/pymode/libs/pylint/test/functional/missing_final_newline.py new file mode 100644 index 00000000..370f902b --- /dev/null +++ b/pymode/libs/pylint/test/functional/missing_final_newline.py @@ -0,0 +1,4 @@ +"""This file does not have a final newline.""" +from __future__ import print_function +# +1:[missing-final-newline] +print(1) \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/missing_final_newline.txt b/pymode/libs/pylint/test/functional/missing_final_newline.txt new file mode 100644 index 00000000..b53c980a --- /dev/null +++ b/pymode/libs/pylint/test/functional/missing_final_newline.txt @@ -0,0 +1 @@ +missing-final-newline:4::Final newline missing diff --git a/pymode/libs/pylint/test/functional/missing_self_argument.py b/pymode/libs/pylint/test/functional/missing_self_argument.py new file mode 100644 index 00000000..6477fcf4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/missing_self_argument.py @@ -0,0 +1,20 @@ +"""Checks that missing self in method defs don't crash Pylint.""" + + + +class MyClass(object): + """A class with some methods missing self args.""" + + def __init__(self): + self.var = "var" + + def method(): # [no-method-argument] + """A method without a self argument.""" + + def setup(): # [no-method-argument] + """A method without a self argument, but usage.""" + self.var = 1 # [undefined-variable] + + def correct(self): + """Correct.""" + self.var = "correct" diff --git a/pymode/libs/pylint/test/functional/missing_self_argument.txt b/pymode/libs/pylint/test/functional/missing_self_argument.txt new file mode 100644 index 00000000..1deef186 --- /dev/null +++ b/pymode/libs/pylint/test/functional/missing_self_argument.txt @@ -0,0 +1,6 @@ +no-method-argument:11:MyClass.method:Method has no argument +no-method-argument:13:MyClass.met:"""Method has no argument +"" +" +no-method-argument:14:MyClass.setup:Method has no argument +undefined-variable:16:MyClass.setup:Undefined variable 'self' diff --git a/pymode/libs/pylint/test/functional/mixed_indentation.py b/pymode/libs/pylint/test/functional/mixed_indentation.py new file mode 100644 index 00000000..724ecff7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/mixed_indentation.py @@ -0,0 +1,10 @@ +"""test mixed tabs and spaces""" +from __future__ import print_function + +def spaces_func(): + """yo""" + print("yo") + +def tab_func(): + """yo""" # [mixed-indentation] + print("yo") # [mixed-indentation] diff --git a/pymode/libs/pylint/test/functional/mixed_indentation.txt b/pymode/libs/pylint/test/functional/mixed_indentation.txt new file mode 100644 index 00000000..d4857e66 --- /dev/null +++ b/pymode/libs/pylint/test/functional/mixed_indentation.txt @@ -0,0 +1,2 @@ +mixed-indentation:9::Found indentation with tabs instead of spaces +mixed-indentation:10::Found indentation with tabs instead of spaces diff --git a/pymode/libs/pylint/test/functional/multiple_imports.py b/pymode/libs/pylint/test/functional/multiple_imports.py new file mode 100644 index 00000000..9008dcc5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/multiple_imports.py @@ -0,0 +1,2 @@ +# pylint: disable=missing-docstring, unused-import +import os, socket # [multiple-imports] diff --git a/pymode/libs/pylint/test/functional/multiple_imports.txt b/pymode/libs/pylint/test/functional/multiple_imports.txt new file mode 100644 index 00000000..a5df51e6 --- /dev/null +++ b/pymode/libs/pylint/test/functional/multiple_imports.txt @@ -0,0 +1 @@ +multiple-imports:2::Multiple imports on one line (os, socket) diff --git a/pymode/libs/pylint/test/functional/name_styles.py b/pymode/libs/pylint/test/functional/name_styles.py new file mode 100644 index 00000000..a100bd54 --- /dev/null +++ b/pymode/libs/pylint/test/functional/name_styles.py @@ -0,0 +1,119 @@ +"""Test for the invalid-name warning.""" +# pylint: disable=no-absolute-import +from __future__ import print_function +import abc +import collections + +GOOD_CONST_NAME = '' +bad_const_name = 0 # [invalid-name] + + +def BADFUNCTION_name(): # [invalid-name] + """Bad function name.""" + BAD_LOCAL_VAR = 1 # [invalid-name] + print(BAD_LOCAL_VAR) + + +def func_bad_argname(NOT_GOOD): # [invalid-name] + """Function with a badly named argument.""" + return NOT_GOOD + + +def no_nested_args(arg1, arg21, arg22): + """Well-formed function.""" + print(arg1, arg21, arg22) + + +class bad_class_name(object): # [invalid-name] + """Class with a bad name.""" + + +class CorrectClassName(object): + """Class with a good name.""" + + def __init__(self): + self._good_private_name = 10 + self.__good_real_private_name = 11 + self.good_attribute_name = 12 + self._Bad_AtTR_name = None # [invalid-name] + self.Bad_PUBLIC_name = None # [invalid-name] + + zz = 'Bad Class Attribute' # [invalid-name] + GOOD_CLASS_ATTR = 'Good Class Attribute' + + def BadMethodName(self): # [invalid-name] + """A Method with a bad name.""" + + def good_method_name(self): + """A method with a good name.""" + + def __DunDER_IS_not_free_for_all__(self): # [invalid-name] + """Another badly named method.""" + + +class DerivedFromCorrect(CorrectClassName): + """A derived class with an invalid inherited members. + + Derived attributes and methods with invalid names do not trigger warnings. + """ + zz = 'Now a good class attribute' + + def __init__(self): + super(DerivedFromCorrect, self).__init__() + self._Bad_AtTR_name = None # Ignored + + def BadMethodName(self): + """Ignored since the method is in the interface.""" + + +V = [WHAT_Ever_inListComp for WHAT_Ever_inListComp in GOOD_CONST_NAME] + +def class_builder(): + """Function returning a class object.""" + + class EmbeddedClass(object): + """Useless class.""" + + return EmbeddedClass + +# +1:[invalid-name] +BAD_NAME_FOR_CLASS = collections.namedtuple('Named', ['tuple']) +NEXT_BAD_NAME_FOR_CLASS = class_builder() # [invalid-name] + +GoodName = collections.namedtuple('Named', ['tuple']) +ToplevelClass = class_builder() + +# Aliases for classes have the same name constraints. +AlsoCorrect = CorrectClassName +NOT_CORRECT = CorrectClassName # [invalid-name] + + +def test_globals(): + """Names in global statements are also checked.""" + global NOT_CORRECT + global AlsoCorrect # [invalid-name] + NOT_CORRECT = 1 + AlsoCorrect = 2 + + +class FooClass(object): + """A test case for property names. + + Since by default, the regex for attributes is the same as the one + for method names, we check the warning messages to contain the + string 'attribute'. + """ + @property + def PROPERTY_NAME(self): # [invalid-name] + """Ignored.""" + pass + + @abc.abstractproperty + def ABSTRACT_PROPERTY_NAME(self): # [invalid-name] + """Ignored.""" + pass + + @PROPERTY_NAME.setter + def PROPERTY_NAME_SETTER(self): # [invalid-name] + """Ignored.""" + pass diff --git a/pymode/libs/pylint/test/functional/name_styles.rc b/pymode/libs/pylint/test/functional/name_styles.rc new file mode 100644 index 00000000..1a63e671 --- /dev/null +++ b/pymode/libs/pylint/test/functional/name_styles.rc @@ -0,0 +1,5 @@ +[testoptions] +min_pyver=2.6 + +[Messages Control] +disable=too-few-public-methods,abstract-class-not-used,global-statement diff --git a/pymode/libs/pylint/test/functional/name_styles.txt b/pymode/libs/pylint/test/functional/name_styles.txt new file mode 100644 index 00000000..79352aa7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/name_styles.txt @@ -0,0 +1,17 @@ +invalid-name:8::"Invalid constant name ""bad_const_name""" +invalid-name:11:BADFUNCTION_name:"Invalid function name ""BADFUNCTION_name""" +invalid-name:13:BADFUNCTION_name:"Invalid variable name ""BAD_LOCAL_VAR""" +invalid-name:17:func_bad_argname:"Invalid argument name ""NOT_GOOD""" +invalid-name:27:bad_class_name:"Invalid class name ""bad_class_name""" +invalid-name:38:CorrectClassName.__init__:"Invalid attribute name ""_Bad_AtTR_name""" +invalid-name:39:CorrectClassName.__init__:"Invalid attribute name ""Bad_PUBLIC_name""" +invalid-name:41:CorrectClassName:"Invalid class attribute name ""zz""" +invalid-name:44:CorrectClassName.BadMethodName:"Invalid method name ""BadMethodName""":INFERENCE +invalid-name:50:CorrectClassName.__DunDER_IS_not_free_for_all__:"Invalid method name ""__DunDER_IS_not_free_for_all__""":INFERENCE +invalid-name:80::"Invalid class name ""BAD_NAME_FOR_CLASS""" +invalid-name:81::"Invalid class name ""NEXT_BAD_NAME_FOR_CLASS""" +invalid-name:88::"Invalid class name ""NOT_CORRECT""" +invalid-name:94:test_globals:"Invalid constant name ""AlsoCorrect""" +invalid-name:107:FooClass.PROPERTY_NAME:"Invalid attribute name ""PROPERTY_NAME""":INFERENCE +invalid-name:112:FooClass.ABSTRACT_PROPERTY_NAME:"Invalid attribute name ""ABSTRACT_PROPERTY_NAME""":INFERENCE +invalid-name:117:FooClass.PROPERTY_NAME_SETTER:"Invalid attribute name ""PROPERTY_NAME_SETTER""":INFERENCE diff --git a/pymode/libs/pylint/test/functional/namedtuple_member_inference.py b/pymode/libs/pylint/test/functional/namedtuple_member_inference.py new file mode 100644 index 00000000..4488ceb0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/namedtuple_member_inference.py @@ -0,0 +1,22 @@ +"""Test namedtuple attributes. + +Regression test for: +https://bitbucket.org/logilab/pylint/issue/93/pylint-crashes-on-namedtuple-attribute +""" +from __future__ import absolute_import, print_function +from collections import namedtuple + +__revision__ = None + +Thing = namedtuple('Thing', ()) + +Fantastic = namedtuple('Fantastic', ['foo']) + +def test(): + """Test member access in named tuples.""" + print(Thing.x) # [no-member] + fan = Fantastic(1) + print(fan.foo) + # Should not raise protected-access. + fan2 = fan._replace(foo=2) + print(fan2.foo) diff --git a/pymode/libs/pylint/test/functional/namedtuple_member_inference.txt b/pymode/libs/pylint/test/functional/namedtuple_member_inference.txt new file mode 100644 index 00000000..1532814c --- /dev/null +++ b/pymode/libs/pylint/test/functional/namedtuple_member_inference.txt @@ -0,0 +1,2 @@ +no-member:17:test:Class 'Thing' has no 'x' member:INFERENCE +no-member:23:test:Instance of 'Fantastic' has no 'foo' member:INFERENCE diff --git a/pymode/libs/pylint/test/functional/names_in__all__.py b/pymode/libs/pylint/test/functional/names_in__all__.py new file mode 100644 index 00000000..afcb2cd2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/names_in__all__.py @@ -0,0 +1,49 @@ +# pylint: disable=too-few-public-methods,no-self-use, no-absolute-import,import-error +"""Test Pylint's use of __all__. + +* NonExistant is not defined in this module, and it is listed in + __all__. An error is expected. + +* This module imports path and republished it in __all__. No errors + are expected. +""" +from __future__ import print_function +from os import path +from collections import deque +from missing import Missing + +__all__ = [ + 'Dummy', + '', # [undefined-all-variable] + Missing, + SomeUndefined, # [undefined-variable] + 'NonExistant', # [undefined-all-variable] + 'path', + 'func', # [undefined-all-variable] + 'inner', # [undefined-all-variable] + 'InnerKlass', deque.__name__] # [undefined-all-variable] + + +class Dummy(object): + """A class defined in this module.""" + pass + +DUMMY = Dummy() + +def function(): + """Function docstring + """ + pass + +function() + +class Klass(object): + """A klass which contains a function""" + def func(self): + """A klass method""" + inner = None + print(inner) + + class InnerKlass(object): + """A inner klass""" + pass diff --git a/pymode/libs/pylint/test/functional/names_in__all__.txt b/pymode/libs/pylint/test/functional/names_in__all__.txt new file mode 100644 index 00000000..94873296 --- /dev/null +++ b/pymode/libs/pylint/test/functional/names_in__all__.txt @@ -0,0 +1,6 @@ +undefined-all-variable:17::Undefined variable name '' in __all__ +undefined-variable:19::Undefined variable 'SomeUndefined' +undefined-all-variable:20::Undefined variable name 'NonExistant' in __all__ +undefined-all-variable:22::Undefined variable name 'func' in __all__ +undefined-all-variable:23::Undefined variable name 'inner' in __all__ +undefined-all-variable:24::Undefined variable name 'InnerKlass' in __all__ diff --git a/pymode/libs/pylint/test/functional/newstyle__slots__.py b/pymode/libs/pylint/test/functional/newstyle__slots__.py new file mode 100644 index 00000000..306d6a14 --- /dev/null +++ b/pymode/libs/pylint/test/functional/newstyle__slots__.py @@ -0,0 +1,17 @@ +# pylint: disable=R0903 +"""test __slots__ on old style class""" + + +class NewStyleClass(object): + """correct usage""" + __slots__ = ('a', 'b') + + +class OldStyleClass: # <3.0:[old-style-class,slots-on-old-class] + """bad usage""" + __slots__ = ('a', 'b') + + def __init__(self): + pass + +__slots__ = 'hop' diff --git a/pymode/libs/pylint/test/functional/newstyle__slots__.txt b/pymode/libs/pylint/test/functional/newstyle__slots__.txt new file mode 100644 index 00000000..43203906 --- /dev/null +++ b/pymode/libs/pylint/test/functional/newstyle__slots__.txt @@ -0,0 +1,2 @@ +old-style-class:10:OldStyleClass:Old-style class defined. +slots-on-old-class:10:OldStyleClass:Use of __slots__ on an old style class:INFERENCE diff --git a/pymode/libs/pylint/test/functional/newstyle_properties.py b/pymode/libs/pylint/test/functional/newstyle_properties.py new file mode 100644 index 00000000..110fa3b4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/newstyle_properties.py @@ -0,0 +1,53 @@ +# pylint: disable=too-few-public-methods +"""Test properties on old style classes and property.setter/deleter usage""" + + +def getter(self): + """interesting""" + return self + +class CorrectClass(object): + """correct usage""" + method = property(getter, doc='hop') + +class OldStyleClass: # <3.0:[old-style-class] + """bad usage""" + method = property(getter, doc='hop') # <3.0:[property-on-old-class] + + def __init__(self): + pass + + +def decorator(func): + """Redefining decorator.""" + def wrapped(self): + """Wrapper function.""" + return func(self) + return wrapped + + +class SomeClass(object): + """another docstring""" + + def __init__(self): + self._prop = None + + @property + def prop(self): + """I'm the 'prop' property.""" + return self._prop + + @prop.setter + def prop(self, value): + """I'm the 'prop' property.""" + self._prop = value + + @prop.deleter + def prop(self): + """I'm the 'prop' property.""" + del self._prop + + @decorator + def noregr(self): + """Just a normal method with a decorator.""" + return self.prop diff --git a/pymode/libs/pylint/test/functional/newstyle_properties.txt b/pymode/libs/pylint/test/functional/newstyle_properties.txt new file mode 100644 index 00000000..a16686b6 --- /dev/null +++ b/pymode/libs/pylint/test/functional/newstyle_properties.txt @@ -0,0 +1,2 @@ +old-style-class:13:OldStyleClass:Old-style class defined. +property-on-old-class:15:OldStyleClass:"Use of ""property"" on an old style class":INFERENCE diff --git a/pymode/libs/pylint/test/functional/no_classmethod_decorator.py b/pymode/libs/pylint/test/functional/no_classmethod_decorator.py new file mode 100644 index 00000000..b9e51cba --- /dev/null +++ b/pymode/libs/pylint/test/functional/no_classmethod_decorator.py @@ -0,0 +1,35 @@ +"""Checks class methods are declared with a decorator if within the class +scope and if classmethod's argument is a member of the class +""" + +# pylint: disable=too-few-public-methods, using-constant-test, no-self-argument + +class MyClass(object): + """Some class""" + def __init__(self): + pass + + def cmethod(cls): + """class method-to-be""" + cmethod = classmethod(cmethod) # [no-classmethod-decorator] + + if True: + cmethod = classmethod(cmethod) # [no-classmethod-decorator] + + @classmethod + def my_second_method(cls): + """correct class method definition""" + + def other_method(cls): + """some method""" + cmethod2 = classmethod(other_method) # [no-classmethod-decorator] + +def helloworld(): + """says hello""" + + +MyClass.new_class_method = classmethod(helloworld) + +class MyOtherClass(object): + """Some other class""" + _make = classmethod(tuple.__new__) diff --git a/pymode/libs/pylint/test/functional/no_classmethod_decorator.txt b/pymode/libs/pylint/test/functional/no_classmethod_decorator.txt new file mode 100644 index 00000000..ba51f0be --- /dev/null +++ b/pymode/libs/pylint/test/functional/no_classmethod_decorator.txt @@ -0,0 +1,3 @@ +no-classmethod-decorator:14:MyClass:Consider using a decorator instead of calling classmethod +no-classmethod-decorator:17:MyClass:Consider using a decorator instead of calling classmethod +no-classmethod-decorator:25:MyClass:Consider using a decorator instead of calling classmethod diff --git a/pymode/libs/pylint/test/functional/no_name_in_module.py b/pymode/libs/pylint/test/functional/no_name_in_module.py new file mode 100644 index 00000000..44bb04d3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/no_name_in_module.py @@ -0,0 +1,66 @@ +#pylint: disable=W0401,W0611,no-absolute-import,invalid-name,import-error,bare-except,broad-except,wrong-import-order,ungrouped-imports +"""check unexistant names imported are reported""" +from __future__ import print_function + +import collections.tutu # [no-name-in-module] +from collections import toto # [no-name-in-module] +toto.yo() + +from xml.etree import ElementTree +ElementTree.nonexistant_function() # [no-member] +ElementTree.another.nonexistant.function() # [no-member] +print(collections.yo) # [no-member] + +import sys +print(sys.stdout, 'hello world') +print(sys.stdoout, 'bye bye world') # [no-member] + + +import re +re.finditer('*', 'yo') + +from rie import * +from re import findiiter, compiile # [no-name-in-module,no-name-in-module] + +import os +'SOMEVAR' in os.environ # [pointless-statement] + +try: + from collections import something +except ImportError: + something = None + +try: + from collections import anything # [no-name-in-module] +except ValueError: + anything = None + +try: + import collections.missing +except ImportError: + pass + +try: + import collections.indeed_missing # [no-name-in-module] +except ValueError: + pass + +try: + import collections.emit # [no-name-in-module] +except Exception: + pass + +try: + import collections.emit1 # [no-name-in-module] +except: + pass + +try: + if something: + import collections.emit2 # [no-name-in-module] +except Exception: + pass + +from .no_self_use import Super +from .no_self_use import lala # [no-name-in-module] +from .no_self_use.bla import lala1 # [no-name-in-module] diff --git a/pymode/libs/pylint/test/functional/no_name_in_module.txt b/pymode/libs/pylint/test/functional/no_name_in_module.txt new file mode 100644 index 00000000..ad4b7bfc --- /dev/null +++ b/pymode/libs/pylint/test/functional/no_name_in_module.txt @@ -0,0 +1,16 @@ +no-name-in-module:5::No name 'tutu' in module 'collections' +no-name-in-module:6::No name 'toto' in module 'collections' +no-member:10::Module 'xml.etree.ElementTree' has no 'nonexistant_function' member:INFERENCE +no-member:11::Module 'xml.etree.ElementTree' has no 'another' member:INFERENCE +no-member:12::Module 'collections' has no 'yo' member:INFERENCE +no-member:16::Module 'sys' has no 'stdoout' member:INFERENCE +no-name-in-module:23::No name 'compiile' in module 're' +no-name-in-module:23::No name 'findiiter' in module 're' +pointless-statement:26::Statement seems to have no effect +no-name-in-module:34::No name 'anything' in module 'collections' +no-name-in-module:44::No name 'indeed_missing' in module 'collections' +no-name-in-module:49::No name 'emit' in module 'collections' +no-name-in-module:54::No name 'emit1' in module 'collections' +no-name-in-module:60::No name 'emit2' in module 'collections' +no-name-in-module:65::No name 'lala' in module 'functional.no_self_use' +no-name-in-module:66::No name 'bla' in module 'functional.no_self_use' diff --git a/pymode/libs/pylint/test/functional/no_self_use.py b/pymode/libs/pylint/test/functional/no_self_use.py new file mode 100644 index 00000000..65e12fa7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/no_self_use.py @@ -0,0 +1,78 @@ +# pylint: disable=R0903,W0232,missing-docstring +"""test detection of method which could be a function""" + +from __future__ import print_function + +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): # [no-self-use] + """this method isn' a real method since it doesn't need self""" + print('hello') + + +class Base(object): + """an abstract class""" + + def __init__(self): + self.aaa = 2 + + def check(self, arg): + """an abstract method, could not be a function""" + raise NotImplementedError + + +class Sub(Base): + """a concret class""" + + def check(self, arg): + """a concret method, could not be a function since it need + polymorphism benefits + """ + return arg == 0 + +class Super(object): + """same as before without abstract""" + attr = 1 + def method(self): + """regular""" + print(self.attr) + +class Sub1(Super): + """override method with need for self""" + def method(self): + """no i can not be a function""" + print(42) + + def __len__(self): + """no i can not be a function""" + print(42) + + def __cmp__(self, other): + """no i can not be a function""" + print(42) + + def __copy__(self): + return 24 + + def __getstate__(self): + return 42 + + +class Prop(object): + + @property + def count(self): + """Don't emit no-self-use for properties. + + They can't be functions and they can be part of an + API specification. + """ + return 42 diff --git a/pymode/libs/pylint/test/functional/no_self_use.txt b/pymode/libs/pylint/test/functional/no_self_use.txt new file mode 100644 index 00000000..e68dcff9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/no_self_use.txt @@ -0,0 +1 @@ +no-self-use:16:Toto.function_method:Method could be a function \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/no_self_use_py3.py b/pymode/libs/pylint/test/functional/no_self_use_py3.py new file mode 100644 index 00000000..f4015083 --- /dev/null +++ b/pymode/libs/pylint/test/functional/no_self_use_py3.py @@ -0,0 +1,12 @@ +# pylint: disable=missing-docstring,no-init,unused-argument,invalid-name,too-few-public-methods + +class A: + def __init__(self): + self.store = {} + + def get(self, key, default=None): + return self.store.get(key, default) + +class B(A): + def get_memo(self, obj): + return super().get(obj) diff --git a/pymode/libs/pylint/test/functional/no_self_use_py3.rc b/pymode/libs/pylint/test/functional/no_self_use_py3.rc new file mode 100644 index 00000000..a2ab06c5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/no_self_use_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/no_self_use_py3.txt b/pymode/libs/pylint/test/functional/no_self_use_py3.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/pymode/libs/pylint/test/functional/no_self_use_py3.txt @@ -0,0 +1 @@ + diff --git a/pymode/libs/pylint/test/functional/no_staticmethod_decorator.py b/pymode/libs/pylint/test/functional/no_staticmethod_decorator.py new file mode 100644 index 00000000..9acc5d74 --- /dev/null +++ b/pymode/libs/pylint/test/functional/no_staticmethod_decorator.py @@ -0,0 +1,35 @@ +"""Checks static methods are declared with a decorator if within the class +scope and if static method's argument is a member of the class +""" + +# pylint: disable=too-few-public-methods, using-constant-test, no-method-argument + +class MyClass(object): + """Some class""" + def __init__(self): + pass + + def smethod(): + """static method-to-be""" + smethod = staticmethod(smethod) # [no-staticmethod-decorator] + + if True: + smethod = staticmethod(smethod) # [no-staticmethod-decorator] + + @staticmethod + def my_second_method(): + """correct static method definition""" + + def other_method(): + """some method""" + smethod2 = staticmethod(other_method) # [no-staticmethod-decorator] + +def helloworld(): + """says hello""" + + +MyClass.new_static_method = staticmethod(helloworld) + +class MyOtherClass(object): + """Some other class""" + _make = staticmethod(tuple.__new__) diff --git a/pymode/libs/pylint/test/functional/no_staticmethod_decorator.txt b/pymode/libs/pylint/test/functional/no_staticmethod_decorator.txt new file mode 100644 index 00000000..c0aea0ea --- /dev/null +++ b/pymode/libs/pylint/test/functional/no_staticmethod_decorator.txt @@ -0,0 +1,3 @@ +no-staticmethod-decorator:14:MyClass:Consider using a decorator instead of calling staticmethod +no-staticmethod-decorator:17:MyClass:Consider using a decorator instead of calling staticmethod +no-staticmethod-decorator:25:MyClass:Consider using a decorator instead of calling staticmethod diff --git a/pymode/libs/pylint/test/functional/non_iterator_returned.py b/pymode/libs/pylint/test/functional/non_iterator_returned.py new file mode 100644 index 00000000..d2fa7580 --- /dev/null +++ b/pymode/libs/pylint/test/functional/non_iterator_returned.py @@ -0,0 +1,95 @@ +"""Check non-iterators returned by __iter__ """ + +# pylint: disable=too-few-public-methods, missing-docstring, no-self-use + +import six + +class FirstGoodIterator(object): + """ yields in iterator. """ + + def __iter__(self): + for index in range(10): + yield index + +class SecondGoodIterator(object): + """ __iter__ and next """ + + def __iter__(self): + return self + + def __next__(self): + """ Infinite iterator, but still an iterator """ + return 1 + + def next(self): + """Same as __next__, but for Python 2.""" + return 1 + +class ThirdGoodIterator(object): + """ Returns other iterator, not the current instance """ + + def __iter__(self): + return SecondGoodIterator() + +class FourthGoodIterator(object): + """ __iter__ returns iter(...) """ + + def __iter__(self): + return iter(range(10)) + + +class IteratorMetaclass(type): + def __next__(cls): + return 1 + + def next(cls): + return 2 + + +@six.add_metaclass(IteratorMetaclass) +class IteratorClass(object): + """Iterable through the metaclass.""" + + +class FifthGoodIterator(object): + """__iter__ returns a class which uses an iterator-metaclass.""" + def __iter__(self): + return IteratorClass + +class FileBasedIterator(object): + def __init__(self, path): + self.path = path + self.file = None + + def __iter__(self): + if self.file is not None: + self.file.close() + self.file = open(self.path) + # self file has two infered values: None and + # we don't want to emit error in this case + return self.file + + +class FirstBadIterator(object): + """ __iter__ returns a list """ + + def __iter__(self): # [non-iterator-returned] + return [] + +class SecondBadIterator(object): + """ __iter__ without next """ + + def __iter__(self): # [non-iterator-returned] + return self + +class ThirdBadIterator(object): + """ __iter__ returns an instance of another non-iterator """ + + def __iter__(self): # [non-iterator-returned] + return SecondBadIterator() + +class FourthBadIterator(object): + """__iter__ returns a class.""" + + def __iter__(self): # [non-iterator-returned] + return ThirdBadIterator diff --git a/pymode/libs/pylint/test/functional/non_iterator_returned.txt b/pymode/libs/pylint/test/functional/non_iterator_returned.txt new file mode 100644 index 00000000..fa1d5bec --- /dev/null +++ b/pymode/libs/pylint/test/functional/non_iterator_returned.txt @@ -0,0 +1,4 @@ +non-iterator-returned:76:FirstBadIterator.__iter__:__iter__ returns non-iterator +non-iterator-returned:82:SecondBadIterator.__iter__:__iter__ returns non-iterator +non-iterator-returned:88:ThirdBadIterator.__iter__:__iter__ returns non-iterator +non-iterator-returned:94:FourthBadIterator.__iter__:__iter__ returns non-iterator diff --git a/pymode/libs/pylint/test/functional/nonexistent_operator.py b/pymode/libs/pylint/test/functional/nonexistent_operator.py new file mode 100644 index 00000000..f05a6494 --- /dev/null +++ b/pymode/libs/pylint/test/functional/nonexistent_operator.py @@ -0,0 +1,15 @@ +"""check operator use""" +# pylint: disable=invalid-name, pointless-statement +a = 1 +a += 5 +a = +a +b = ++a # [nonexistent-operator] +++a # [nonexistent-operator] +c = (++a) * b # [nonexistent-operator] + +a = 1 +a -= 5 +b = --a # [nonexistent-operator] +b = a +--a # [nonexistent-operator] +c = (--a) * b # [nonexistent-operator] diff --git a/pymode/libs/pylint/test/functional/nonexistent_operator.txt b/pymode/libs/pylint/test/functional/nonexistent_operator.txt new file mode 100644 index 00000000..da59f414 --- /dev/null +++ b/pymode/libs/pylint/test/functional/nonexistent_operator.txt @@ -0,0 +1,6 @@ +nonexistent-operator:6::"Use of the non-existent ++ operator" +nonexistent-operator:7::"Use of the non-existent ++ operator" +nonexistent-operator:8::"Use of the non-existent ++ operator" +nonexistent-operator:12::"Use of the non-existent -- operator" +nonexistent-operator:14::"Use of the non-existent -- operator" +nonexistent-operator:15::"Use of the non-existent -- operator" diff --git a/pymode/libs/pylint/test/functional/nonlocal_and_global.py b/pymode/libs/pylint/test/functional/nonlocal_and_global.py new file mode 100644 index 00000000..94005243 --- /dev/null +++ b/pymode/libs/pylint/test/functional/nonlocal_and_global.py @@ -0,0 +1,12 @@ +"""Test that a name is both nonlocal and global in the same scope.""" +# pylint: disable=missing-docstring,global-variable-not-assigned,invalid-name,nonlocal-without-binding + +def bad(): # [nonlocal-and-global] + nonlocal missing + global missing + +def good(): + nonlocal missing + def test(): + global missing + return test diff --git a/pymode/libs/pylint/test/functional/nonlocal_and_global.rc b/pymode/libs/pylint/test/functional/nonlocal_and_global.rc new file mode 100644 index 00000000..c093be20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/nonlocal_and_global.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/nonlocal_and_global.txt b/pymode/libs/pylint/test/functional/nonlocal_and_global.txt new file mode 100644 index 00000000..46f0d0fe --- /dev/null +++ b/pymode/libs/pylint/test/functional/nonlocal_and_global.txt @@ -0,0 +1 @@ +nonlocal-and-global:4:bad:"Name 'missing' is nonlocal and global" \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/nonlocal_without_binding.py b/pymode/libs/pylint/test/functional/nonlocal_without_binding.py new file mode 100644 index 00000000..0501dd89 --- /dev/null +++ b/pymode/libs/pylint/test/functional/nonlocal_without_binding.py @@ -0,0 +1,30 @@ +""" Checks that reversed() receive proper argument """ +# pylint: disable=missing-docstring,invalid-name,unused-variable +# pylint: disable=too-few-public-methods,no-self-use,no-absolute-import + +def test(): + def parent(): + a = 42 + def stuff(): + nonlocal a + + c = 24 + def parent2(): + a = 42 + def stuff(): + def other_stuff(): + nonlocal a + nonlocal c + +b = 42 +def func(): + def other_func(): + nonlocal b # [nonlocal-without-binding] + +class SomeClass(object): + nonlocal x # [nonlocal-without-binding] + + def func(self): + nonlocal some_attr # [nonlocal-without-binding] + + \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/nonlocal_without_binding.rc b/pymode/libs/pylint/test/functional/nonlocal_without_binding.rc new file mode 100644 index 00000000..a2ab06c5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/nonlocal_without_binding.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/nonlocal_without_binding.txt b/pymode/libs/pylint/test/functional/nonlocal_without_binding.txt new file mode 100644 index 00000000..05a1105c --- /dev/null +++ b/pymode/libs/pylint/test/functional/nonlocal_without_binding.txt @@ -0,0 +1,3 @@ +nonlocal-without-binding:22:func.other_func:nonlocal name b found without binding +nonlocal-without-binding:25:SomeClass:nonlocal name x found without binding +nonlocal-without-binding:28:SomeClass.func:nonlocal name some_attr found without binding \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/not_async_context_manager.py b/pymode/libs/pylint/test/functional/not_async_context_manager.py new file mode 100644 index 00000000..647d1045 --- /dev/null +++ b/pymode/libs/pylint/test/functional/not_async_context_manager.py @@ -0,0 +1,71 @@ +"""Test that an async context manager receives a proper object.""" +# pylint: disable=missing-docstring, import-error, too-few-public-methods +import contextlib + +from ala import Portocala + + +@contextlib.contextmanager +def ctx_manager(): + yield + + +class ContextManager(object): + def __enter__(self): + pass + def __exit__(self, *args): + pass + +class PartialAsyncContextManager(object): + def __aenter__(self): + pass + +class SecondPartialAsyncContextManager(object): + def __aexit__(self, *args): + pass + +class UnknownBases(Portocala): + def __aenter__(self): + pass + + +class AsyncManagerMixin(object): + pass + +class GoodAsyncManager(object): + def __aenter__(self): + pass + def __aexit__(self, *args): + pass + +class InheritExit(object): + def __aexit__(self, *args): + pass + +class SecondGoodAsyncManager(InheritExit): + def __aenter__(self): + pass + + +async def bad_coro(): + async with 42: # [not-async-context-manager] + pass + async with ctx_manager(): # [not-async-context-manager] + pass + async with ContextManager(): # [not-async-context-manager] + pass + async with PartialAsyncContextManager(): # [not-async-context-manager] + pass + async with SecondPartialAsyncContextManager(): # [not-async-context-manager] + pass + + +async def good_coro(): + async with UnknownBases(): + pass + async with AsyncManagerMixin(): + pass + async with GoodAsyncManager(): + pass + async with SecondGoodAsyncManager(): + pass diff --git a/pymode/libs/pylint/test/functional/not_async_context_manager.rc b/pymode/libs/pylint/test/functional/not_async_context_manager.rc new file mode 100644 index 00000000..03004f2c --- /dev/null +++ b/pymode/libs/pylint/test/functional/not_async_context_manager.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.5 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/not_async_context_manager.txt b/pymode/libs/pylint/test/functional/not_async_context_manager.txt new file mode 100644 index 00000000..ae9fad79 --- /dev/null +++ b/pymode/libs/pylint/test/functional/not_async_context_manager.txt @@ -0,0 +1,5 @@ +not-async-context-manager:51:bad_coro:Async context manager 'int' doesn't implement __aenter__ and __aexit__. +not-async-context-manager:53:bad_coro:Async context manager 'generator' doesn't implement __aenter__ and __aexit__. +not-async-context-manager:55:bad_coro:Async context manager 'ContextManager' doesn't implement __aenter__ and __aexit__. +not-async-context-manager:57:bad_coro:Async context manager 'PartialAsyncContextManager' doesn't implement __aenter__ and __aexit__. +not-async-context-manager:59:bad_coro:Async context manager 'SecondPartialAsyncContextManager' doesn't implement __aenter__ and __aexit__. diff --git a/pymode/libs/pylint/test/functional/not_callable.py b/pymode/libs/pylint/test/functional/not_callable.py new file mode 100644 index 00000000..c2a3ab47 --- /dev/null +++ b/pymode/libs/pylint/test/functional/not_callable.py @@ -0,0 +1,112 @@ +# pylint: disable=missing-docstring,no-self-use,too-few-public-methods + +REVISION = None + +REVISION() # [not-callable] + +def correct(): + 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() # [not-callable] +LIST = [] +INCORRECT = LIST() # [not-callable] +DICT = {} +INCORRECT = DICT() # [not-callable] +TUPLE = () +INCORRECT = TUPLE() # [not-callable] +INT = 1 +INCORRECT = INT() # [not-callable] + +# Test calling properties. Pylint can detect when using only the +# getter, but it doesn't infer properly when having a getter +# and a setter. +class MyProperty(property): + """ test subclasses """ + +class PropertyTest(object): + """ class """ + + def __init__(self): + self.attr = 4 + + @property + def test(self): + """ Get the attribute """ + return self.attr + + @test.setter + def test(self, value): + """ Set the attribute """ + self.attr = value + + @MyProperty + def custom(self): + """ Get the attribute """ + return self.attr + + @custom.setter + def custom(self, value): + """ Set the attribute """ + self.attr = value + +PROP = PropertyTest() +PROP.test(40) # [not-callable] +PROP.custom() # [not-callable] + +# Safe from not-callable when using properties. + +class SafeProperty(object): + @property + def static(self): + return staticmethod + + @property + def klass(self): + return classmethod + + @property + def get_lambda(self): + return lambda: None + + @property + def other_function(self): + def function(arg): + return arg + return function + + @property + def dict_builtin(self): + return dict + + @property + def range_builtin(self): + return range + + @property + def instance(self): + class Empty(object): + def __call__(self): + return 42 + return Empty() + +PROP1 = SafeProperty() +PROP1.static(2) +PROP1.klass(2) +PROP1.get_lambda() +PROP1.other_function(4) +PROP1.dict_builtin() +PROP1.range_builtin(4) +PROP1.instance() diff --git a/pymode/libs/pylint/test/functional/not_callable.txt b/pymode/libs/pylint/test/functional/not_callable.txt new file mode 100644 index 00000000..4928c8c8 --- /dev/null +++ b/pymode/libs/pylint/test/functional/not_callable.txt @@ -0,0 +1,8 @@ +not-callable:5::REVISION is not callable +not-callable:23::INSTANCE is not callable +not-callable:25::LIST is not callable +not-callable:27::DICT is not callable +not-callable:29::TUPLE is not callable +not-callable:31::INT is not callable +not-callable:66::PROP.test is not callable +not-callable:67::PROP.custom is not callable \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/not_context_manager.py b/pymode/libs/pylint/test/functional/not_context_manager.py new file mode 100644 index 00000000..3d56b603 --- /dev/null +++ b/pymode/libs/pylint/test/functional/not_context_manager.py @@ -0,0 +1,135 @@ +"""Tests that onjects used in a with statement implement context manager protocol""" + +# pylint: disable=too-few-public-methods, invalid-name, import-error, missing-docstring +# pylint: disable=no-init,wrong-import-position +# Tests no messages for objects that implement the protocol +class Manager(object): + def __enter__(self): + pass + def __exit__(self, type_, value, traceback): + pass +with Manager(): + pass + +class AnotherManager(Manager): + pass +with AnotherManager(): + pass + + +# Tests message for class that doesn't implement the protocol +class NotAManager(object): + pass +with NotAManager(): #[not-context-manager] + pass + +# Tests contextlib.contextmanager usage is recognized as correct. +from contextlib import contextmanager +@contextmanager +def dec(): + yield +with dec(): # valid use + pass + + +# Tests a message is produced when a contextlib.contextmanager +# decorated function is used without being called. +with dec: # [not-context-manager] + pass + + +# Tests no messages about context manager protocol +# if the type can't be inferred. +from missing import Missing +with Missing(): + pass + +# Tests context managers as names. + +def penelopa(): + return 42 + +hopa = dec() +tropa = penelopa() + +with tropa: # [not-context-manager] + pass + +with hopa: + pass + + +# Tests that no messages are emitted for function calls +# which return managers + +def wrapper(): + return dec() + +with wrapper(): + pass + +# Tests for properties returning managers. + +class Property(object): + + @property + def ctx(self): + return dec() + + @property + def not_ctx(self): + return 42 + + +lala = Property() +with lala.ctx: + # Don't emit when the context manager is the + # result of accessing a property. + pass + +with lala.not_ctx: # [not-context-manager] + pass + + +class TestKnownBases(Missing): + pass + +with TestKnownBases(): + pass + +# Ignore mixins. +class ManagerMixin(object): + def test(self): + with self: + pass + +class FullContextManager(ManagerMixin): + def __enter__(self): + return self + def __exit__(self, *args): + pass + +# Test a false positive with returning a generator +# from a context manager. +def generator(): + yield 42 + +@contextmanager +def context_manager_returning_generator(): + return generator() + +with context_manager_returning_generator(): + pass + +FIRST = [context_manager_returning_generator()] +with FIRST[0]: + pass + +def other_indirect_func(): + return generator() + +def not_context_manager(): + return other_indirect_func() + +with not_context_manager(): # [not-context-manager] + pass diff --git a/pymode/libs/pylint/test/functional/not_context_manager.txt b/pymode/libs/pylint/test/functional/not_context_manager.txt new file mode 100644 index 00000000..7fad3af0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/not_context_manager.txt @@ -0,0 +1,5 @@ +not-context-manager:23::Context manager 'NotAManager' doesn't implement __enter__ and __exit__. +not-context-manager:37::Context manager 'dec' doesn't implement __enter__ and __exit__. +not-context-manager:55::Context manager 'int' doesn't implement __enter__ and __exit__. +not-context-manager:90::Context manager 'int' doesn't implement __enter__ and __exit__. +not-context-manager:134::Context manager 'generator' doesn't implement __enter__ and __exit__. diff --git a/pymode/libs/pylint/test/functional/not_in_loop.py b/pymode/libs/pylint/test/functional/not_in_loop.py new file mode 100644 index 00000000..e16fb20b --- /dev/null +++ b/pymode/libs/pylint/test/functional/not_in_loop.py @@ -0,0 +1,54 @@ +"""Test that not-in-loop is detected properly.""" +# pylint: disable=missing-docstring, invalid-name, too-few-public-methods +# pylint: disable=useless-else-on-loop, using-constant-test + +while True: + def ala(): + continue # [not-in-loop] + +while True: + pass +else: + continue # [not-in-loop] + +def lala(): + continue # [not-in-loop] + +while True: + class A(object): + continue # [not-in-loop] + +for _ in range(10): + pass +else: + continue # [not-in-loop] + +for _ in range(42): + pass +else: + break # [not-in-loop] + +if True: + continue # [not-in-loop] +else: + break # [not-in-loop] + +for _ in range(10): + for _ in range(20): + pass + else: + continue + +while True: + while True: + break + else: + break + break +else: + pass + +for _ in range(1): + continue +for _ in range(42): + break diff --git a/pymode/libs/pylint/test/functional/not_in_loop.txt b/pymode/libs/pylint/test/functional/not_in_loop.txt new file mode 100644 index 00000000..6730bfcc --- /dev/null +++ b/pymode/libs/pylint/test/functional/not_in_loop.txt @@ -0,0 +1,8 @@ +not-in-loop:7:ala:'continue' not properly in loop +not-in-loop:12::'continue' not properly in loop +not-in-loop:15:lala:'continue' not properly in loop +not-in-loop:19:A:'continue' not properly in loop +not-in-loop:24::'continue' not properly in loop +not-in-loop:29::'break' not properly in loop +not-in-loop:32::'continue' not properly in loop +not-in-loop:34::'break' not properly in loop \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/old_division_manually.py b/pymode/libs/pylint/test/functional/old_division_manually.py new file mode 100644 index 00000000..aab4bd4f --- /dev/null +++ b/pymode/libs/pylint/test/functional/old_division_manually.py @@ -0,0 +1,2 @@ +from __future__ import division +print 1 / 3 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/old_division_manually.rc b/pymode/libs/pylint/test/functional/old_division_manually.rc new file mode 100644 index 00000000..11dbb8d0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/old_division_manually.rc @@ -0,0 +1,6 @@ +[testoptions] +max_pyver=3.0 + +[Messages Control] +disable=all +enable=old-division \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/old_style_class_py27.py b/pymode/libs/pylint/test/functional/old_style_class_py27.py new file mode 100644 index 00000000..11493254 --- /dev/null +++ b/pymode/libs/pylint/test/functional/old_style_class_py27.py @@ -0,0 +1,18 @@ +""" Tests for old style classes. """ +# pylint: disable=no-init, too-few-public-methods, invalid-name, metaclass-assignment + +class Old: # [old-style-class] + """ old style class """ + +class Child(Old): + """ Old style class, but don't emit for it. """ + +class NotOldStyle2: + """ Because I have a metaclass at class level. """ + __metaclass__ = type + +# pylint: disable=redefined-builtin +__metaclass__ = type + +class NotOldStyle: + """ Because I have a metaclass at global level. """ diff --git a/pymode/libs/pylint/test/functional/old_style_class_py27.rc b/pymode/libs/pylint/test/functional/old_style_class_py27.rc new file mode 100644 index 00000000..a6502339 --- /dev/null +++ b/pymode/libs/pylint/test/functional/old_style_class_py27.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/old_style_class_py27.txt b/pymode/libs/pylint/test/functional/old_style_class_py27.txt new file mode 100644 index 00000000..c46a7b61 --- /dev/null +++ b/pymode/libs/pylint/test/functional/old_style_class_py27.txt @@ -0,0 +1,2 @@ +old-style-class:4:Old:Old-style class defined. +old-style-class:7:Child:Old-style class defined. diff --git a/pymode/libs/pylint/test/functional/pygtk_enum_crash.py b/pymode/libs/pylint/test/functional/pygtk_enum_crash.py new file mode 100644 index 00000000..471afe31 --- /dev/null +++ b/pymode/libs/pylint/test/functional/pygtk_enum_crash.py @@ -0,0 +1,11 @@ +# pylint: disable=C0121 +"""http://www.logilab.org/ticket/124337""" + +import gtk + +def print_some_constant(arg=gtk.BUTTONS_OK): + """crash because gtk.BUTTONS_OK, a gtk enum type, is returned by + astroid as a constant + """ + print arg + diff --git a/pymode/libs/pylint/test/functional/pygtk_enum_crash.rc b/pymode/libs/pylint/test/functional/pygtk_enum_crash.rc new file mode 100644 index 00000000..7a6c0afb --- /dev/null +++ b/pymode/libs/pylint/test/functional/pygtk_enum_crash.rc @@ -0,0 +1,2 @@ +[testoptions] +requires=gtk diff --git a/pymode/libs/pylint/test/functional/pygtk_import.py b/pymode/libs/pylint/test/functional/pygtk_import.py new file mode 100644 index 00000000..231885e8 --- /dev/null +++ b/pymode/libs/pylint/test/functional/pygtk_import.py @@ -0,0 +1,14 @@ +"""Import PyGTK.""" +#pylint: disable=too-few-public-methods,too-many-public-methods + +from gtk import VBox +import gtk + +class FooButton(gtk.Button): + """extend gtk.Button""" + def extend(self): + """hop""" + print self + +print gtk.Button +print VBox diff --git a/pymode/libs/pylint/test/functional/pygtk_import.rc b/pymode/libs/pylint/test/functional/pygtk_import.rc new file mode 100644 index 00000000..7a6c0afb --- /dev/null +++ b/pymode/libs/pylint/test/functional/pygtk_import.rc @@ -0,0 +1,2 @@ +[testoptions] +requires=gtk diff --git a/pymode/libs/pylint/test/functional/raising_non_exception_py3.py b/pymode/libs/pylint/test/functional/raising_non_exception_py3.py new file mode 100644 index 00000000..f7013e24 --- /dev/null +++ b/pymode/libs/pylint/test/functional/raising_non_exception_py3.py @@ -0,0 +1,13 @@ +"""The following code should emit a raising-non-exception. + +Previously, it didn't, due to a bug in the check for bad-exception-context, +which prevented further checking on the Raise node. +""" +# pylint: disable=import-error, too-few-public-methods + +from missing_module import missing + +class Exc(object): + """Not an actual exception.""" + +raise Exc from missing # [raising-non-exception] diff --git a/pymode/libs/pylint/test/functional/raising_non_exception_py3.rc b/pymode/libs/pylint/test/functional/raising_non_exception_py3.rc new file mode 100644 index 00000000..c093be20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/raising_non_exception_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/raising_non_exception_py3.txt b/pymode/libs/pylint/test/functional/raising_non_exception_py3.txt new file mode 100644 index 00000000..12c88628 --- /dev/null +++ b/pymode/libs/pylint/test/functional/raising_non_exception_py3.txt @@ -0,0 +1 @@ +raising-non-exception:13::Raising a new style class which doesn't inherit from BaseException \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/redefined_builtin.py b/pymode/libs/pylint/test/functional/redefined_builtin.py new file mode 100644 index 00000000..d6bca4b7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/redefined_builtin.py @@ -0,0 +1,10 @@ +"""Tests for redefining builtins.""" +from __future__ import print_function + +def function(): + """Redefined local.""" + type = 1 # [redefined-builtin] + print(type) + +# pylint:disable=invalid-name +map = {} # [redefined-builtin] diff --git a/pymode/libs/pylint/test/functional/redefined_builtin.txt b/pymode/libs/pylint/test/functional/redefined_builtin.txt new file mode 100644 index 00000000..fb11cd3a --- /dev/null +++ b/pymode/libs/pylint/test/functional/redefined_builtin.txt @@ -0,0 +1,2 @@ +redefined-builtin:6:function:Redefining built-in 'type' +redefined-builtin:10::Redefining built-in 'map' diff --git a/pymode/libs/pylint/test/functional/redefined_variable_type.py b/pymode/libs/pylint/test/functional/redefined_variable_type.py new file mode 100644 index 00000000..1c41a9d2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/redefined_variable_type.py @@ -0,0 +1,55 @@ +"""Checks variable types aren't redefined within a method or a function""" + +# pylint: disable=too-few-public-methods, missing-docstring, unused-variable + +_OK = True + +class MyClass(object): + + class Klass(object): + def __init__(self): + self.var2 = 'var' + + def __init__(self): + self.var = True + self.var1 = 2 + self.var2 = 1. + self.var1 = 2. # [redefined-variable-type] + self.a_str = "hello" + a_str = False + (a_str, b_str) = (1, 2) # no support for inference on tuple assignment + a_str = 2.0 if self.var else 1.0 # no support for inference on ifexpr + + def _getter(self): + return self.a_str + def _setter(self, val): + self.a_str = val + var2 = property(_getter, _setter) + + def some_method(self): + def func(): + var = 1 + test = 'bar' + var = 'baz' # [redefined-variable-type] + self.var = 1 # the rule checks for redefinitions in the scope of a function or method + test = 'foo' + myint = 2 + myint = False # [redefined-variable-type] + +_OK = "This is OK" # [redefined-variable-type] + +if _OK: + SOME_FLOAT = 1. + +def dummy_function(): + return 2 + +def other_function(): + instance = MyClass() + instance = True # [redefined-variable-type] + +SOME_FLOAT = dummy_function() # [redefined-variable-type] + +A_GLOB = None +A_GLOB = [1, 2, 3] + diff --git a/pymode/libs/pylint/test/functional/redefined_variable_type.txt b/pymode/libs/pylint/test/functional/redefined_variable_type.txt new file mode 100644 index 00000000..3646aeb9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/redefined_variable_type.txt @@ -0,0 +1,6 @@ +redefined-variable-type:17:MyClass.__init__:Redefinition of self.var1 type from int to float +redefined-variable-type:33:MyClass.some_method.func:Redefinition of var type from int to str +redefined-variable-type:37:MyClass.some_method:Redefinition of myint type from int to bool +redefined-variable-type:39::Redefinition of _OK type from bool to str +redefined-variable-type:49:other_function:Redefinition of instance type from functional.redefined_variable_type.MyClass to bool +redefined-variable-type:51::Redefinition of SOME_FLOAT type from float to int diff --git a/pymode/libs/pylint/test/functional/redundant_unittest_assert.py b/pymode/libs/pylint/test/functional/redundant_unittest_assert.py new file mode 100644 index 00000000..2eb73e3d --- /dev/null +++ b/pymode/libs/pylint/test/functional/redundant_unittest_assert.py @@ -0,0 +1,38 @@ +# pylint: disable=missing-docstring,too-few-public-methods +""" +http://www.logilab.org/ticket/355 +If you are using assertTrue or assertFalse and the first argument is a +constant(like a string), then the assert will always be true. Therefore, +it should emit a warning message. +""" + +import unittest + +@unittest.skip("don't run this") +class Tests(unittest.TestCase): + def test_something(self): + ''' Simple test ''' + some_var = 'It should be assertEqual' + # +1:[redundant-unittest-assert] + self.assertTrue('I meant assertEqual not assertTrue', some_var) + # +1:[redundant-unittest-assert] + self.assertFalse('I meant assertEqual not assertFalse', some_var) + # +1:[redundant-unittest-assert] + self.assertTrue(True, some_var) + # +1:[redundant-unittest-assert] + self.assertFalse(False, some_var) + # +1:[redundant-unittest-assert] + self.assertFalse(None, some_var) + # +1:[redundant-unittest-assert] + self.assertTrue(0, some_var) + + self.assertTrue('should be' in some_var, some_var) + self.assertTrue(some_var, some_var) + + +@unittest.skip("don't run this") +class RegressionWithArgs(unittest.TestCase): + '''Don't fail if the bound method doesn't have arguments.''' + + def test(self): + self.run() diff --git a/pymode/libs/pylint/test/functional/redundant_unittest_assert.txt b/pymode/libs/pylint/test/functional/redundant_unittest_assert.txt new file mode 100644 index 00000000..889d395c --- /dev/null +++ b/pymode/libs/pylint/test/functional/redundant_unittest_assert.txt @@ -0,0 +1,6 @@ +redundant-unittest-assert:17:Tests.test_something:"Redundant use of assertTrue with constant value 'I meant assertEqual not assertTrue'" +redundant-unittest-assert:19:Tests.test_something:"Redundant use of assertFalse with constant value 'I meant assertEqual not assertFalse'" +redundant-unittest-assert:21:Tests.test_something:"Redundant use of assertTrue with constant value True" +redundant-unittest-assert:23:Tests.test_something:"Redundant use of assertFalse with constant value False" +redundant-unittest-assert:25:Tests.test_something:"Redundant use of assertFalse with constant value None" +redundant-unittest-assert:27:Tests.test_something:"Redundant use of assertTrue with constant value 0" \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/reimported.py b/pymode/libs/pylint/test/functional/reimported.py new file mode 100644 index 00000000..10258321 --- /dev/null +++ b/pymode/libs/pylint/test/functional/reimported.py @@ -0,0 +1,7 @@ +# pylint: disable=missing-docstring,unused-import,import-error + +from time import sleep, sleep # [reimported] +from lala import missing, missing # [reimported] + +import missing1 +import missing1 # [reimported] diff --git a/pymode/libs/pylint/test/functional/reimported.txt b/pymode/libs/pylint/test/functional/reimported.txt new file mode 100644 index 00000000..685c83d3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/reimported.txt @@ -0,0 +1,3 @@ +reimported:3::"Reimport 'sleep' (imported line 3)" +reimported:4::"Reimport 'missing' (imported line 4)" +reimported:7::"Reimport 'missing1' (imported line 6)" \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/repeated_keyword.py b/pymode/libs/pylint/test/functional/repeated_keyword.py new file mode 100644 index 00000000..786c53ba --- /dev/null +++ b/pymode/libs/pylint/test/functional/repeated_keyword.py @@ -0,0 +1,13 @@ +"""Check that a keyword is not repeated in a function call + +This is somehow related to redundant-keyword, but it's not the same. +""" + +# pylint: disable=missing-docstring, invalid-name + +def test(a, b): + return a, b + +test(1, 24) +test(1, b=24, **{}) +test(1, b=24, **{'b': 24}) # [repeated-keyword] diff --git a/pymode/libs/pylint/test/functional/repeated_keyword.txt b/pymode/libs/pylint/test/functional/repeated_keyword.txt new file mode 100644 index 00000000..1344b15b --- /dev/null +++ b/pymode/libs/pylint/test/functional/repeated_keyword.txt @@ -0,0 +1 @@ +repeated-keyword:13::"Got multiple values for keyword argument 'b' in function call" \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/return_in_init.py b/pymode/libs/pylint/test/functional/return_in_init.py new file mode 100644 index 00000000..35976d20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/return_in_init.py @@ -0,0 +1,25 @@ +# pylint: disable=missing-docstring,too-few-public-methods + +class MyClass(object): + + def __init__(self): # [return-in-init] + return 1 + +class MyClass2(object): + """dummy class""" + + def __init__(self): + return + + +class MyClass3(object): + """dummy class""" + + def __init__(self): + return None + +class MyClass5(object): + """dummy class""" + + def __init__(self): + self.callable = lambda: (yield None) diff --git a/pymode/libs/pylint/test/functional/return_in_init.txt b/pymode/libs/pylint/test/functional/return_in_init.txt new file mode 100644 index 00000000..5e708d0d --- /dev/null +++ b/pymode/libs/pylint/test/functional/return_in_init.txt @@ -0,0 +1 @@ +return-in-init:5:MyClass.__init__:Explicit return in __init__ \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/return_outside_function.py b/pymode/libs/pylint/test/functional/return_outside_function.py new file mode 100644 index 00000000..449dafd6 --- /dev/null +++ b/pymode/libs/pylint/test/functional/return_outside_function.py @@ -0,0 +1,2 @@ +"""This is gramatically correct, but it's still a SyntaxError""" +return # [return-outside-function] diff --git a/pymode/libs/pylint/test/functional/return_outside_function.txt b/pymode/libs/pylint/test/functional/return_outside_function.txt new file mode 100644 index 00000000..0c9aa569 --- /dev/null +++ b/pymode/libs/pylint/test/functional/return_outside_function.txt @@ -0,0 +1 @@ +return-outside-function:2::Return outside function \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/simplifiable_if_statement.py b/pymode/libs/pylint/test/functional/simplifiable_if_statement.py new file mode 100644 index 00000000..48d6dbb5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/simplifiable_if_statement.py @@ -0,0 +1,120 @@ +"""Test that some if statement tests can be simplified.""" + +# pylint: disable=missing-docstring, invalid-name + + +def test_simplifiable_1(arg): + # Simple test that can be replaced by bool(arg) + if arg: # [simplifiable-if-statement] + return True + else: + return False + + +def test_simplifiable_2(arg, arg2): + # Can be reduced to bool(arg and not arg2) + if arg and not arg2: # [simplifiable-if-statement] + return True + else: + return False + + +def test_simplifiable_3(arg, arg2): + # Can be reduced to bool(arg and not arg2) + if arg and not arg2: # [simplifiable-if-statement] + var = True + else: + var = False + return var + + +def test_simplifiable_4(arg): + if arg: + var = True + else: + if arg == "arg1": # [simplifiable-if-statement] + return True + else: + return False + return var + + +def test_not_necessarily_simplifiable_1(arg, arg2): + # Can be reduced to bool(not arg and not arg2) or to + # `not all(N)`, which is a bit harder to understand + # than `any(N)` when var should be False. + if arg or arg2: + var = False + else: + var = True + return var + + +def test_not_necessarily_simplifiabile_2(arg): + # This could theoretically be reduced to `not arg or arg > 3` + # but the net result is that now the condition is harder to understand, + # because it requires understanding of an extra clause: + # * first, there is the negation of truthness with `not arg` + # * the second clause is `arg > 3`, which occurs when arg has a + # a truth value, but it implies that `arg > 3` is equivalent + # with `arg and arg > 3`, which means that the user must + # think about this assumption when evaluating `arg > 3`. + # The original form is easier to grasp. + if arg and arg <= 3: + return False + else: + return True + + +def test_not_simplifiable_3(arg): + if arg: + test_not_necessarily_simplifiabile_2(arg) + test_not_necessarily_simplifiable_1(arg, arg) + return False + else: + if arg < 3: + test_simplifiable_3(arg, 42) + return True + + +def test_not_simplifiable_4(arg): + # Not interested in multiple elifs + if arg == "any": + return True + elif test_not_simplifiable_3(arg) == arg: + return True + else: + return False + + +def test_not_simplifiable_5(arg): + # Different actions in each branch + if arg == "any": + return True + else: + var = 42 + return var + + +def test_not_simplifiable_6(arg): + # Different actions in each branch + if arg == "any": + var = 42 + else: + return True + return var + +def test_not_simplifiable_7(arg): + # Returning something different + if arg == "any": + return 4 + else: + return 5 + + +def test_not_simplifiable_8(arg): + # Only one of the branch returns something boolean + if arg == "any": + return True + else: + return 0 diff --git a/pymode/libs/pylint/test/functional/simplifiable_if_statement.txt b/pymode/libs/pylint/test/functional/simplifiable_if_statement.txt new file mode 100644 index 00000000..1c511090 --- /dev/null +++ b/pymode/libs/pylint/test/functional/simplifiable_if_statement.txt @@ -0,0 +1,4 @@ +simplifiable-if-statement:8:test_simplifiable_1:The if statement can be replaced with 'return bool(test)' +simplifiable-if-statement:16:test_simplifiable_2:The if statement can be replaced with 'return bool(test)' +simplifiable-if-statement:24:test_simplifiable_3:The if statement can be replaced with 'var = bool(test)' +simplifiable-if-statement:35:test_simplifiable_4:The if statement can be replaced with 'return bool(test)' diff --git a/pymode/libs/pylint/test/functional/singleton_comparison.py b/pymode/libs/pylint/test/functional/singleton_comparison.py new file mode 100644 index 00000000..59738f83 --- /dev/null +++ b/pymode/libs/pylint/test/functional/singleton_comparison.py @@ -0,0 +1,11 @@ +# pylint: disable=missing-docstring, invalid-name, misplaced-comparison-constant +x = 42 +a = x is None +b = x == None # [singleton-comparison] +c = x == True # [singleton-comparison] +d = x == False # [singleton-comparison] +e = True == True # [singleton-comparison] +f = x is 1 +g = 123 is "123" +h = None is x +i = None == x # [singleton-comparison] diff --git a/pymode/libs/pylint/test/functional/singleton_comparison.txt b/pymode/libs/pylint/test/functional/singleton_comparison.txt new file mode 100644 index 00000000..fd1fa988 --- /dev/null +++ b/pymode/libs/pylint/test/functional/singleton_comparison.txt @@ -0,0 +1,5 @@ +singleton-comparison:4::Comparison to None should be 'expr is None' +singleton-comparison:5::Comparison to True should be just 'expr' or 'expr is True' +singleton-comparison:6::Comparison to False should be 'not expr' or 'expr is False' +singleton-comparison:7::Comparison to True should be just 'expr' or 'expr is True' +singleton-comparison:11::Comparison to None should be 'expr is None' diff --git a/pymode/libs/pylint/test/functional/slots_checks.py b/pymode/libs/pylint/test/functional/slots_checks.py new file mode 100644 index 00000000..6cd818ca --- /dev/null +++ b/pymode/libs/pylint/test/functional/slots_checks.py @@ -0,0 +1,61 @@ +""" Checks that classes uses valid __slots__ """ + +# pylint: disable=too-few-public-methods, missing-docstring, no-absolute-import +# pylint: disable=using-constant-test +from collections import deque + +def func(): + if True: + return ("a", "b", "c") + else: + return [str(var) for var in range(3)] + + +class NotIterable(object): + def __iter_(self): + """ do nothing """ + +class Good(object): + __slots__ = () + +class SecondGood(object): + __slots__ = [] + +class ThirdGood(object): + __slots__ = ['a'] + +class FourthGood(object): + __slots__ = ('a%s' % i for i in range(10)) + +class FifthGood(object): + __slots__ = "a" + +class SixthGood(object): + __slots__ = deque(["a", "b", "c"]) + +class SeventhGood(object): + __slots__ = {"a": "b", "c": "d"} + +class Bad(object): + __slots__ = list + +class SecondBad(object): # [invalid-slots] + __slots__ = 1 + +class ThirdBad(object): + __slots__ = ('a', 2) # [invalid-slots-object] + +class FourthBad(object): # [invalid-slots] + __slots__ = NotIterable() + +class FifthBad(object): + __slots__ = ("a", "b", "") # [invalid-slots-object] + +class PotentiallyGood(object): + __slots__ = func() + +class PotentiallySecondGood(object): + __slots__ = ('a', deque.__name__) + +class PotentiallyThirdGood(object): + __slots__ = deque.__name__ diff --git a/pymode/libs/pylint/test/functional/slots_checks.txt b/pymode/libs/pylint/test/functional/slots_checks.txt new file mode 100644 index 00000000..7e90a4a7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/slots_checks.txt @@ -0,0 +1,4 @@ +invalid-slots:42:SecondBad:Invalid __slots__ object +invalid-slots-object:46:ThirdBad:Invalid object '2' in __slots__, must contain only non empty strings +invalid-slots:48:FourthBad:Invalid __slots__ object +invalid-slots-object:52:FifthBad:"Invalid object ""''"" in __slots__, must contain only non empty strings" diff --git a/pymode/libs/pylint/test/functional/socketerror_import.py b/pymode/libs/pylint/test/functional/socketerror_import.py new file mode 100644 index 00000000..df5de295 --- /dev/null +++ b/pymode/libs/pylint/test/functional/socketerror_import.py @@ -0,0 +1,6 @@ +"""ds""" +from __future__ import absolute_import, print_function +from socket import error +__revision__ = '$Id: socketerror_import.py,v 1.2 2005-12-28 14:58:22 syt Exp $' + +print(error) diff --git a/pymode/libs/pylint/test/functional/star_needs_assignment_target.py b/pymode/libs/pylint/test/functional/star_needs_assignment_target.py new file mode 100644 index 00000000..5421c226 --- /dev/null +++ b/pymode/libs/pylint/test/functional/star_needs_assignment_target.py @@ -0,0 +1,4 @@ +"""Test for a = *b""" + +FIRST = *[1, 2] # [star-needs-assignment-target] +*THIRD, FOURTH = [1, 2, 3,] diff --git a/pymode/libs/pylint/test/functional/star_needs_assignment_target.rc b/pymode/libs/pylint/test/functional/star_needs_assignment_target.rc new file mode 100644 index 00000000..c093be20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/star_needs_assignment_target.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/star_needs_assignment_target.txt b/pymode/libs/pylint/test/functional/star_needs_assignment_target.txt new file mode 100644 index 00000000..b66975c9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/star_needs_assignment_target.txt @@ -0,0 +1 @@ +star-needs-assignment-target:3::Can use starred expression only in assignment target diff --git a/pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.py b/pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.py new file mode 100644 index 00000000..58e43dba --- /dev/null +++ b/pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.py @@ -0,0 +1,15 @@ +""" +Test PEP 0448 -- Additional Unpacking Generalizations +https://www.python.org/dev/peps/pep-0448/ +""" + +# pylint: disable=superfluous-parens + +UNPACK_TUPLE = (*range(4), 4) +UNPACK_LIST = [*range(4), 4] +UNPACK_SET = {*range(4), 4} +UNPACK_DICT = {'a': 1, **{'b': '2'}} +UNPACK_DICT2 = {**UNPACK_DICT, "x": 1, "y": 2} +UNPACK_DICT3 = {**{'a': 1}, 'a': 2, **{'a': 3}} + +UNPACK_IN_COMP = {elem for elem in (*range(10))} # [star-needs-assignment-target] diff --git a/pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.rc b/pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.rc new file mode 100644 index 00000000..71de8b63 --- /dev/null +++ b/pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.5 diff --git a/pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.txt b/pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.txt new file mode 100644 index 00000000..07770523 --- /dev/null +++ b/pymode/libs/pylint/test/functional/star_needs_assignment_target_py35.txt @@ -0,0 +1 @@ +star-needs-assignment-target:15::Can use starred expression only in assignment target \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/statement_without_effect.py b/pymode/libs/pylint/test/functional/statement_without_effect.py new file mode 100644 index 00000000..b1aaadf8 --- /dev/null +++ b/pymode/libs/pylint/test/functional/statement_without_effect.py @@ -0,0 +1,65 @@ +"""Test for statements without effects.""" +# pylint: disable=too-few-public-methods + +# +1:[pointless-string-statement] +"""inline doc string should use a separated message""" + +__revision__ = '' + +__revision__ # [pointless-statement] + +__revision__ <= 1 # [pointless-statement] + +__revision__.lower() + +[i for i in __revision__] # [pointless-statement] + +# +1:[pointless-string-statement] +"""inline doc string should use a separated message""" + + +__revision__.lower(); # [unnecessary-semicolon] + +list() and tuple() # [expression-not-assigned] + +def to_be(): + """return 42""" + return "42" + +ANSWER = to_be() # ok +ANSWER == to_be() # [expression-not-assigned] + +to_be() or not to_be() # [expression-not-assigned] +to_be().title # [expression-not-assigned] + +GOOD_ATTRIBUTE_DOCSTRING = 42 +"""Module level attribute docstring is fine. """ + +class ClassLevelAttributeTest(object): + """ test attribute docstrings. """ + + good_attribute_docstring = 24 + """ class level attribute docstring is fine either. """ + second_good_attribute_docstring = 42 + # Comments are good. + + # empty lines are good, too. + """ Still a valid class level attribute docstring. """ + + def __init__(self): + self.attr = 42 + """ Good attribute docstring """ + attr = 24 + """ Still a good __init__ level attribute docstring. """ + val = 0 + for val in range(42): + val += attr + # +1:[pointless-string-statement] + """ Invalid attribute docstring """ + self.val = val + + def test(self): + """ invalid attribute docstrings here. """ + self.val = 42 + # +1:[pointless-string-statement] + """ this is an invalid attribute docstring. """ diff --git a/pymode/libs/pylint/test/functional/statement_without_effect.txt b/pymode/libs/pylint/test/functional/statement_without_effect.txt new file mode 100644 index 00000000..81fc51bc --- /dev/null +++ b/pymode/libs/pylint/test/functional/statement_without_effect.txt @@ -0,0 +1,60 @@ +pointless-string-statement:5::String statement has no effect +pointless-statement:6::"""Statement seems to have no effect +"" +" +pointless-statement:8::"""Statement seems to have no effect +"" +" +pointless-statement:9::Statement seems to have no effect +pointless-statement:11::Statement seems to have no effect +pointless-statement:12::"""Statement seems to have no effect +"" +" +pointless-statement:15::Statement seems to have no effect +pointless-string-statement:15::"""String statement has no effect +"" +" +unnecessary-semicolon:17::"""Unnecessary semicolon +"" +" +pointless-string-statement:18::String statement has no effect +unnecessary-semicolon:18::"""Unnecessary semicolon +"" +" +expression-not-assigned:19::"""Expression """"(list()) and (tuple())"""" is assigned to nothing +"" +" +expression-not-assigned:20::"""Expression """"(list()) and (tuple())"""" is assigned to nothing +"" +" +unnecessary-semicolon:21::Unnecessary semicolon +expression-not-assigned:23::"Expression ""(list()) and (tuple())"" is assigned to nothing" +expression-not-assigned:26::"""Expression """"ANSWER == to_be()"""" is assigned to nothing +"" +" +expression-not-assigned:27::"""Expression """"ANSWER == to_be()"""" is assigned to nothing +"" +" +expression-not-assigned:28::"""Expression """"(to_be()) or (not to_be())"""" is assigned to nothing +"" +" +expression-not-assigned:29::"""Expression """"(to_be()) or (not to_be())"""" is assigned to nothing +"" +" +expression-not-assigned:30::"Expression ""ANSWER == to_be()"" is assigned to nothing" +expression-not-assigned:32::"Expression ""(to_be()) or (not to_be())"" is assigned to nothing" +expression-not-assigned:33::"Expression ""to_be().title"" is assigned to nothing" +pointless-string-statement:54:ClassLevelAttributeTest.__init__:"""String statement has no effect +"" +" +pointless-string-statement:55:ClassLevelAttributeTest.__init__:"""String statement has no effect +"" +" +pointless-string-statement:58:ClassLevelAttributeTest.__init__:String statement has no effect +pointless-string-statement:61:ClassLevelAttributeTest.test:"""String statement has no effect +"" +" +pointless-string-statement:62:ClassLevelAttributeTest.test:"""String statement has no effect +"" +" +pointless-string-statement:65:ClassLevelAttributeTest.test:String statement has no effect diff --git a/pymode/libs/pylint/test/functional/string_formatting.py b/pymode/libs/pylint/test/functional/string_formatting.py new file mode 100644 index 00000000..95299a92 --- /dev/null +++ b/pymode/libs/pylint/test/functional/string_formatting.py @@ -0,0 +1,183 @@ +"""test for Python 3 string formatting error +""" +# pylint: disable=too-few-public-methods, import-error, unused-argument, line-too-long, no-absolute-import +import os +from missing import Missing + +__revision__ = 1 + +class Custom(object): + """ Has a __getattr__ """ + def __getattr__(self, _): + return self + +class Test(object): + """ test format attribute access """ + custom = Custom() + ids = [1, 2, 3, [4, 5, 6]] + +class Getitem(object): + """ test custom getitem for lookup access """ + def __getitem__(self, index): + return 42 + +class ReturnYes(object): + """ can't be properly infered """ + missing = Missing() + +def log(message, message_type="error"): + """ Test """ + return message + +def print_good(): + """ Good format strings """ + "{0} {1}".format(1, 2) + "{0!r:20}".format("Hello") + "{!r:20}".format("Hello") + "{a!r:20}".format(a="Hello") + "{pid}".format(pid=os.getpid()) + str("{}").format(2) + "{0.missing.length}".format(ReturnYes()) + "{1.missing.length}".format(ReturnYes()) + "{a.ids[3][1]}".format(a=Test()) + "{a[0][0]}".format(a=[[1]]) + "{[0][0]}".format({0: {0: 1}}) + "{a.test}".format(a=Custom()) + "{a.__len__}".format(a=[]) + "{a.ids.__len__}".format(a=Test()) + "{a[0]}".format(a=Getitem()) + "{a[0][0]}".format(a=[Getitem()]) + "{[0][0]}".format(["test"]) + # these are skipped + "{0} {1}".format(*[1, 2]) + "{a} {b}".format(**{'a': 1, 'b': 2}) + "{a}".format(a=Missing()) + +def pprint_bad(): + """Test string format """ + "{{}}".format(1) # [too-many-format-args] + "{} {".format() # [bad-format-string] + "{} }".format() # [bad-format-string] + "{0} {}".format(1, 2) # [format-combined-specification] + # +1: [missing-format-argument-key, unused-format-string-argument] + "{a} {b}".format(a=1, c=2) + "{} {a}".format(1, 2) # [missing-format-argument-key] + "{} {}".format(1) # [too-few-format-args] + "{} {}".format(1, 2, 3) # [too-many-format-args] + # +1: [missing-format-argument-key,missing-format-argument-key,missing-format-argument-key] + "{a} {b} {c}".format() + "{} {}".format(a=1, b=2) # [too-few-format-args] + # +1: [missing-format-argument-key, missing-format-argument-key] + "{a} {b}".format(1, 2) + "{0} {1} {a}".format(1, 2, 3) # [missing-format-argument-key] + # +1: [missing-format-attribute] + "{a.ids.__len__.length}".format(a=Test()) + "{a.ids[3][400]}".format(a=Test()) # [invalid-format-index] + "{a.ids[3]['string']}".format(a=Test()) # [invalid-format-index] + "{[0][1]}".format(["a"]) # [invalid-format-index] + "{[0][0]}".format(((1, ))) # [invalid-format-index] + # +1: [missing-format-argument-key, unused-format-string-argument] + "{b[0]}".format(a=23) + "{a[0]}".format(a=object) # [invalid-format-index] + log("{}".format(2, "info")) # [too-many-format-args] + "{0.missing}".format(2) # [missing-format-attribute] + "{0} {1} {2}".format(1, 2) # [too-few-format-args] + "{0} {1}".format(1, 2, 3) # [too-many-format-args] + "{0} {a}".format(a=4) # [too-few-format-args] + "{[0]} {}".format([4]) # [too-few-format-args] + "{[0]} {}".format([4], 5, 6) # [too-many-format-args] + +def good_issue288(*args, **kwargs): + """ Test that using kwargs does not emit a false + positive. + """ + 'Hello John Doe {0[0]}'.format(args) + 'Hello {0[name]}'.format(kwargs) + +def good_issue287(): + """ Test that the string format checker skips + format nodes which don't have a string as a parent + (but a subscript, name etc). + """ + name = 'qwerty' + ret = {'comment': ''} + ret['comment'] = 'MySQL grant {0} is set to be revoked' + ret['comment'] = ret['comment'].format(name) + return ret, name + +def nested_issue294(): + """ Test nested format fields. """ + '{0:>{1}}'.format(42, 24) + '{0:{a[1]}} {a}'.format(1, a=[1, 2]) + '{:>{}}'.format(42, 24) + '{0:>{1}}'.format(42) # [too-few-format-args] + '{0:>{1}}'.format(42, 24, 54) # [too-many-format-args] + '{0:{a[1]}}'.format(1) # [missing-format-argument-key] + '{0:{a.x}}'.format(1, a=2) # [missing-format-attribute] + +def issue310(): + """ Test a regression using duplicate manual position arguments. """ + '{0} {1} {0}'.format(1, 2) + '{0} {1} {0}'.format(1) # [too-few-format-args] + +def issue322(): + """ Test a regression using mixed manual position arguments + and attribute access arguments. + """ + '{0}{1[FOO]}'.format(123, {'FOO': 456}) + '{0}{1[FOO]}'.format(123, {'FOO': 456}, 321) # [too-many-format-args] + '{0}{1[FOO]}'.format(123) # [too-few-format-args] + +def issue338(): + """ + Check that using a namedtuple subclass doesn't crash when + trying to infer EmptyNodes (resulted after mocking the + members of namedtuples). + """ + from collections import namedtuple + + class Crash(namedtuple("C", "foo bar")): + """ Looking for attributes in __str__ will crash, + because EmptyNodes can't be infered. + """ + def __str__(self): + return "{0.foo}: {0.bar}".format(self) + return Crash + +def issue351(): + """ + Check that the format method can be assigned to a variable, ie: + """ + fmt = 'test {} {}'.format + fmt('arg1') # [too-few-format-args] + fmt('arg1', 'arg2') + fmt('arg1', 'arg2', 'arg3') # [too-many-format-args] + +def issue373(): + """ + Ignore any object coming from an argument. + """ + class SomeClass(object): + """ empty docstring. """ + def __init__(self, opts=None): + self.opts = opts + + def dunc(self, arg): + """Don't try to analyze this.""" + return "A{0}{1}".format(arg, self.opts) + + def func(self): + """Don't try to analyze the following string.""" + return 'AAA{0[iface]}BBB{0[port]}'.format(self.opts) + + return SomeClass + +def issue_463(): + """ + Mix positional arguments, `{0}`, with positional + arguments with attribute access, `{0.__x__}`. + """ + data = "{0.__class__.__name__}: {0}".format(42) + data2 = "{0[0]}: {0}".format([1]) + return (data, data2) + diff --git a/pymode/libs/pylint/test/functional/string_formatting.txt b/pymode/libs/pylint/test/functional/string_formatting.txt new file mode 100644 index 00000000..b2f18498 --- /dev/null +++ b/pymode/libs/pylint/test/functional/string_formatting.txt @@ -0,0 +1,40 @@ +too-many-format-args:58:pprint_bad:Too many arguments for format string +bad-format-string:59:pprint_bad:Invalid format string +bad-format-string:60:pprint_bad:Invalid format string +format-combined-specification:61:pprint_bad:Format string contains both automatic field numbering and manual field specification +missing-format-argument-key:63:pprint_bad:Missing keyword argument 'b' for format string +unused-format-string-argument:63:pprint_bad:Unused format argument 'c' +missing-format-argument-key:64:pprint_bad:Missing keyword argument 'a' for format string +too-few-format-args:65:pprint_bad:Not enough arguments for format string +too-many-format-args:66:pprint_bad:Too many arguments for format string +missing-format-argument-key:68:pprint_bad:Missing keyword argument 'a' for format string +missing-format-argument-key:68:pprint_bad:Missing keyword argument 'b' for format string +missing-format-argument-key:68:pprint_bad:Missing keyword argument 'c' for format string +too-few-format-args:69:pprint_bad:Not enough arguments for format string +missing-format-argument-key:71:pprint_bad:Missing keyword argument 'a' for format string +missing-format-argument-key:71:pprint_bad:Missing keyword argument 'b' for format string +missing-format-argument-key:72:pprint_bad:Missing keyword argument 'a' for format string +missing-format-attribute:74:pprint_bad:Missing format attribute 'length' in format specifier 'a.ids.__len__.length' +invalid-format-index:75:pprint_bad:Using invalid lookup key 400 in format specifier 'a.ids[3][400]' +invalid-format-index:76:pprint_bad:"Using invalid lookup key ""'string'"" in format specifier 'a.ids[3][""\'string\'""]'" +invalid-format-index:77:pprint_bad:Using invalid lookup key 1 in format specifier '0[0][1]' +invalid-format-index:78:pprint_bad:Using invalid lookup key 0 in format specifier '0[0][0]' +missing-format-argument-key:80:pprint_bad:Missing keyword argument 'b' for format string +unused-format-string-argument:80:pprint_bad:Unused format argument 'a' +invalid-format-index:81:pprint_bad:Using invalid lookup key 0 in format specifier 'a[0]' +too-many-format-args:82:pprint_bad:Too many arguments for format string +missing-format-attribute:83:pprint_bad:Missing format attribute 'missing' in format specifier '0.missing' +too-few-format-args:84:pprint_bad:Not enough arguments for format string +too-many-format-args:85:pprint_bad:Too many arguments for format string +too-few-format-args:86:pprint_bad:Not enough arguments for format string +too-few-format-args:87:pprint_bad:Not enough arguments for format string +too-many-format-args:88:pprint_bad:Too many arguments for format string +too-few-format-args:113:nested_issue294:Not enough arguments for format string +too-many-format-args:114:nested_issue294:Too many arguments for format string +missing-format-argument-key:115:nested_issue294:Missing keyword argument 'a' for format string +missing-format-attribute:116:nested_issue294:Missing format attribute 'x' in format specifier 'a.x' +too-few-format-args:121:issue310:Not enough arguments for format string +too-many-format-args:128:issue322:Too many arguments for format string +too-few-format-args:129:issue322:Not enough arguments for format string +too-few-format-args:152:issue351:Not enough arguments for format string +too-many-format-args:154:issue351:Too many arguments for format string diff --git a/pymode/libs/pylint/test/functional/string_formatting_disable.py b/pymode/libs/pylint/test/functional/string_formatting_disable.py new file mode 100644 index 00000000..096c8473 --- /dev/null +++ b/pymode/libs/pylint/test/functional/string_formatting_disable.py @@ -0,0 +1 @@ +"a {} {".format(1) # [bad-format-string] \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/string_formatting_disable.rc b/pymode/libs/pylint/test/functional/string_formatting_disable.rc new file mode 100644 index 00000000..0193339d --- /dev/null +++ b/pymode/libs/pylint/test/functional/string_formatting_disable.rc @@ -0,0 +1,3 @@ +[Messages Control] +disable=all +enable=bad-format-string diff --git a/pymode/libs/pylint/test/functional/string_formatting_disable.txt b/pymode/libs/pylint/test/functional/string_formatting_disable.txt new file mode 100644 index 00000000..2c8f6db5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/string_formatting_disable.txt @@ -0,0 +1 @@ +bad-format-string:1::Invalid format string \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/string_formatting_failed_inference.py b/pymode/libs/pylint/test/functional/string_formatting_failed_inference.py new file mode 100644 index 00000000..aa8d24ae --- /dev/null +++ b/pymode/libs/pylint/test/functional/string_formatting_failed_inference.py @@ -0,0 +1,4 @@ +""" Testing string format with a failed inference. This should not crash. """ + +import collections +"{dict[0]}".format(dict=collections.defaultdict(int)) diff --git a/pymode/libs/pylint/test/functional/string_formatting_py27.py b/pymode/libs/pylint/test/functional/string_formatting_py27.py new file mode 100644 index 00000000..d6aeae72 --- /dev/null +++ b/pymode/libs/pylint/test/functional/string_formatting_py27.py @@ -0,0 +1,23 @@ +"""test for Python 2 string formatting error +""" +from __future__ import unicode_literals +# pylint: disable=line-too-long +__revision__ = 1 + +def pprint_bad(): + """Test string format """ + "{{}}".format(1) # [too-many-format-args] + "{} {".format() # [bad-format-string] + "{} }".format() # [bad-format-string] + "{0} {}".format(1, 2) # [format-combined-specification] + # +1: [missing-format-argument-key, unused-format-string-argument] + "{a} {b}".format(a=1, c=2) + "{} {a}".format(1, 2) # [missing-format-argument-key] + "{} {}".format(1) # [too-few-format-args] + "{} {}".format(1, 2, 3) # [too-many-format-args] + # +1: [missing-format-argument-key,missing-format-argument-key,missing-format-argument-key] + "{a} {b} {c}".format() + "{} {}".format(a=1, b=2) # [too-few-format-args] + # +1: [missing-format-argument-key, missing-format-argument-key] + "{a} {b}".format(1, 2) + diff --git a/pymode/libs/pylint/test/functional/string_formatting_py27.rc b/pymode/libs/pylint/test/functional/string_formatting_py27.rc new file mode 100644 index 00000000..80170b77 --- /dev/null +++ b/pymode/libs/pylint/test/functional/string_formatting_py27.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=2.7 +max_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/string_formatting_py27.txt b/pymode/libs/pylint/test/functional/string_formatting_py27.txt new file mode 100644 index 00000000..47f21ded --- /dev/null +++ b/pymode/libs/pylint/test/functional/string_formatting_py27.txt @@ -0,0 +1,15 @@ +too-many-format-args:9:pprint_bad:Too many arguments for format string +bad-format-string:10:pprint_bad:Invalid format string +bad-format-string:11:pprint_bad:Invalid format string +format-combined-specification:12:pprint_bad:Format string contains both automatic field numbering and manual field specification +missing-format-argument-key:14:pprint_bad:Missing keyword argument u'b' for format string +unused-format-string-argument:14:pprint_bad:Unused format argument 'c' +missing-format-argument-key:15:pprint_bad:Missing keyword argument u'a' for format string +too-few-format-args:16:pprint_bad:Not enough arguments for format string +too-many-format-args:17:pprint_bad:Too many arguments for format string +missing-format-argument-key:19:pprint_bad:Missing keyword argument u'a' for format string +missing-format-argument-key:19:pprint_bad:Missing keyword argument u'b' for format string +missing-format-argument-key:19:pprint_bad:Missing keyword argument u'c' for format string +too-few-format-args:20:pprint_bad:Not enough arguments for format string +missing-format-argument-key:22:pprint_bad:Missing keyword argument u'a' for format string +missing-format-argument-key:22:pprint_bad:Missing keyword argument u'b' for format string diff --git a/pymode/libs/pylint/test/functional/super_checks.py b/pymode/libs/pylint/test/functional/super_checks.py new file mode 100644 index 00000000..2959d002 --- /dev/null +++ b/pymode/libs/pylint/test/functional/super_checks.py @@ -0,0 +1,114 @@ +# pylint: disable=too-few-public-methods,import-error, no-absolute-import,missing-docstring, wrong-import-position,invalid-name +"""check use of super""" + +from unknown import Missing + +class Aaaa: # <3.0:[old-style-class] + """old style""" + def hop(self): # <3.0:[super-on-old-class] + """hop""" + super(Aaaa, self).hop() # >=3.0:[no-member] + + def __init__(self): # <3.0:[super-on-old-class] + super(Aaaa, self).__init__() + +class NewAaaa(object): + """old style""" + def hop(self): + """hop""" + super(NewAaaa, self).hop() # [no-member] + + def __init__(self): + super(Aaaa, self).__init__() # [bad-super-call] + +class Py3kAaaa(NewAaaa): + """new style""" + def __init__(self): + super().__init__() # <3.0:[missing-super-argument] + +class Py3kWrongSuper(Py3kAaaa): + """new style""" + def __init__(self): + super(NewAaaa, self).__init__() # [bad-super-call] + +class WrongNameRegression(Py3kAaaa): + """ test a regression with the message """ + def __init__(self): + super(Missing, self).__init__() # [bad-super-call] + +class Getattr(object): + """ crash """ + name = NewAaaa + +class CrashSuper(object): + """ test a crash with this checker """ + def __init__(self): + super(Getattr.name, self).__init__() # [bad-super-call] + +class Empty(object): + """Just an empty class.""" + +class SuperDifferentScope(object): + """Don'emit bad-super-call when the super call is in another scope. + For reference, see https://bitbucket.org/logilab/pylint/issue/403. + """ + @staticmethod + def test(): + """Test that a bad-super-call is not emitted for this case.""" + class FalsePositive(Empty): + """The following super is in another scope than `test`.""" + def __init__(self, arg): + super(FalsePositive, self).__init__(arg) + super(object, 1).__init__() # [bad-super-call] + + +class UnknownBases(Missing): + """Don't emit if we don't know all the bases.""" + def __init__(self): + super(UnknownBases, self).__init__() + super(UnknownBases, self).test() + super(Missing, self).test() # [bad-super-call] + + +# Test that we are detecting proper super errors. + +class BaseClass(object): + + not_a_method = 42 + + def function(self, param): + return param + self.not_a_method + + def __getattr__(self, attr): + return attr + + +class InvalidSuperChecks(BaseClass): + + def __init__(self): + super(InvalidSuperChecks, self).not_a_method() # [not-callable] + super(InvalidSuperChecks, self).attribute_error() # [no-member] + super(InvalidSuperChecks, self).function(42) + super(InvalidSuperChecks, self).function() # [no-value-for-parameter] + super(InvalidSuperChecks, self).function(42, 24, 24) # [too-many-function-args] + # +1: [unexpected-keyword-arg,no-value-for-parameter] + super(InvalidSuperChecks, self).function(lala=42) + # Even though BaseClass has a __getattr__, that won't + # be called. + super(InvalidSuperChecks, self).attribute_error() # [no-member] + + + +# Regression for PyCQA/pylint/issues/773 +import subprocess + +# The problem was related to astroid not filtering statements +# at scope level properly, basically not doing strong updates. +try: + TimeoutExpired = subprocess.TimeoutExpired +except AttributeError: + class TimeoutExpired(subprocess.CalledProcessError): + def __init__(self): + returncode = -1 + self.timeout = -1 + super(TimeoutExpired, self).__init__(returncode) diff --git a/pymode/libs/pylint/test/functional/super_checks.txt b/pymode/libs/pylint/test/functional/super_checks.txt new file mode 100644 index 00000000..ea1d67be --- /dev/null +++ b/pymode/libs/pylint/test/functional/super_checks.txt @@ -0,0 +1,19 @@ +old-style-class:6:Aaaa:Old-style class defined. +super-on-old-class:8:Aaaa.hop:Use of super on an old style class +no-member:10:Aaaa.hop:Super of 'Aaaa' has no 'hop' member:INFERENCE +super-on-old-class:12:Aaaa.__init__:Use of super on an old style class +no-member:19:NewAaaa.hop:Super of 'NewAaaa' has no 'hop' member:INFERENCE +bad-super-call:22:NewAaaa.__init__:Bad first argument 'Aaaa' given to super() +missing-super-argument:27:Py3kAaaa.__init__:Missing argument to super() +bad-super-call:32:Py3kWrongSuper.__init__:Bad first argument 'NewAaaa' given to super() +bad-super-call:37:WrongNameRegression.__init__:Bad first argument 'Missing' given to super() +bad-super-call:46:CrashSuper.__init__:Bad first argument 'NewAaaa' given to super() +bad-super-call:62:SuperDifferentScope.test:Bad first argument 'object' given to super() +bad-super-call:70:UnknownBases.__init__:Bad first argument 'Missing' given to super() +not-callable:89:InvalidSuperChecks.__init__:super(InvalidSuperChecks, self).not_a_method is not callable +no-member:90:InvalidSuperChecks.__init__:Super of 'InvalidSuperChecks' has no 'attribute_error' member:INFERENCE +no-value-for-parameter:92:InvalidSuperChecks.__init__:No value for argument 'param' in method call +too-many-function-args:93:InvalidSuperChecks.__init__:Too many positional arguments for method call +no-value-for-parameter:95:InvalidSuperChecks.__init__:No value for argument 'param' in method call +unexpected-keyword-arg:95:InvalidSuperChecks.__init__:Unexpected keyword argument 'lala' in method call +no-member:98:InvalidSuperChecks.__init__:Super of 'InvalidSuperChecks' has no 'attribute_error' member:INFERENCE \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/superfluous_parens.py b/pymode/libs/pylint/test/functional/superfluous_parens.py new file mode 100644 index 00000000..13418cd7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/superfluous_parens.py @@ -0,0 +1,19 @@ +"""Test the superfluous-parens warning.""" +from __future__ import print_function +# pylint: disable=unneeded-not +i = 3 +if (i == 5): # [superfluous-parens] + pass +if not (i == 5): # [superfluous-parens] + pass +if not (3 or 5): + pass +for (x) in (1, 2, 3): # [superfluous-parens] + print(x) +if (1) in (1, 2, 3): # [superfluous-parens] + pass +if (1, 2) in (1, 2, 3): + pass +DICT = {'a': 1, 'b': 2} +del(DICT['b']) # [superfluous-parens] +del DICT['a'] diff --git a/pymode/libs/pylint/test/functional/superfluous_parens.txt b/pymode/libs/pylint/test/functional/superfluous_parens.txt new file mode 100644 index 00000000..57e1019c --- /dev/null +++ b/pymode/libs/pylint/test/functional/superfluous_parens.txt @@ -0,0 +1,5 @@ +superfluous-parens:5::Unnecessary parens after 'if' keyword +superfluous-parens:7::Unnecessary parens after 'not' keyword +superfluous-parens:11::Unnecessary parens after 'for' keyword +superfluous-parens:13::Unnecessary parens after 'if' keyword +superfluous-parens:18::Unnecessary parens after 'del' keyword diff --git a/pymode/libs/pylint/test/functional/suspicious_str_strip_call.py b/pymode/libs/pylint/test/functional/suspicious_str_strip_call.py new file mode 100644 index 00000000..e859f25f --- /dev/null +++ b/pymode/libs/pylint/test/functional/suspicious_str_strip_call.py @@ -0,0 +1,9 @@ +"""Suspicious str.strip calls.""" +__revision__ = 1 + +''.strip('yo') +''.strip() + +u''.strip('http://') # [bad-str-strip-call] +u''.lstrip('http://') # [bad-str-strip-call] +b''.rstrip('http://') # [bad-str-strip-call] diff --git a/pymode/libs/pylint/test/functional/suspicious_str_strip_call.rc b/pymode/libs/pylint/test/functional/suspicious_str_strip_call.rc new file mode 100644 index 00000000..a6502339 --- /dev/null +++ b/pymode/libs/pylint/test/functional/suspicious_str_strip_call.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/suspicious_str_strip_call.txt b/pymode/libs/pylint/test/functional/suspicious_str_strip_call.txt new file mode 100644 index 00000000..ad714cc6 --- /dev/null +++ b/pymode/libs/pylint/test/functional/suspicious_str_strip_call.txt @@ -0,0 +1,3 @@ +bad-str-strip-call:7::Suspicious argument in unicode.strip call +bad-str-strip-call:8::Suspicious argument in unicode.lstrip call +bad-str-strip-call:9::Suspicious argument in str.rstrip call diff --git a/pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.py b/pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.py new file mode 100644 index 00000000..e859f25f --- /dev/null +++ b/pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.py @@ -0,0 +1,9 @@ +"""Suspicious str.strip calls.""" +__revision__ = 1 + +''.strip('yo') +''.strip() + +u''.strip('http://') # [bad-str-strip-call] +u''.lstrip('http://') # [bad-str-strip-call] +b''.rstrip('http://') # [bad-str-strip-call] diff --git a/pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.rc b/pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.rc new file mode 100644 index 00000000..c093be20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.txt b/pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.txt new file mode 100644 index 00000000..81f32cf4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/suspicious_str_strip_call_py3.txt @@ -0,0 +1,3 @@ +bad-str-strip-call:7::Suspicious argument in str.strip call +bad-str-strip-call:8::Suspicious argument in str.lstrip call +bad-str-strip-call:9::Suspicious argument in bytes.rstrip call diff --git a/pymode/libs/pylint/test/functional/syntax_error.py b/pymode/libs/pylint/test/functional/syntax_error.py new file mode 100644 index 00000000..c93df6b0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/syntax_error.py @@ -0,0 +1 @@ +def toto # [syntax-error] diff --git a/pymode/libs/pylint/test/functional/syntax_error.rc b/pymode/libs/pylint/test/functional/syntax_error.rc new file mode 100644 index 00000000..d86678f7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/syntax_error.rc @@ -0,0 +1,2 @@ +[testoptions] +except_implementations=Jython diff --git a/pymode/libs/pylint/test/functional/syntax_error.txt b/pymode/libs/pylint/test/functional/syntax_error.txt new file mode 100644 index 00000000..b57bc72b --- /dev/null +++ b/pymode/libs/pylint/test/functional/syntax_error.txt @@ -0,0 +1 @@ +syntax-error:1::invalid syntax \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/syntax_error_jython.py b/pymode/libs/pylint/test/functional/syntax_error_jython.py new file mode 100644 index 00000000..c93df6b0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/syntax_error_jython.py @@ -0,0 +1 @@ +def toto # [syntax-error] diff --git a/pymode/libs/pylint/test/functional/syntax_error_jython.rc b/pymode/libs/pylint/test/functional/syntax_error_jython.rc new file mode 100644 index 00000000..29bd3fac --- /dev/null +++ b/pymode/libs/pylint/test/functional/syntax_error_jython.rc @@ -0,0 +1,2 @@ +[testoptions] +except_implementations=CPython, PyPy diff --git a/pymode/libs/pylint/test/functional/syntax_error_jython.txt b/pymode/libs/pylint/test/functional/syntax_error_jython.txt new file mode 100644 index 00000000..e532aece --- /dev/null +++ b/pymode/libs/pylint/test/functional/syntax_error_jython.txt @@ -0,0 +1 @@ +syntax-error:1::"mismatched input '\n\n\n\n' expecting LPAREN" \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/tokenize_error.py b/pymode/libs/pylint/test/functional/tokenize_error.py new file mode 100644 index 00000000..323950d9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/tokenize_error.py @@ -0,0 +1,6 @@ +"""A module that is accepted by Python but rejected by tokenize. + +The problem is the trailing line continuation at the end of the line, +which produces a TokenError.""" +# +2: [syntax-error] +""\ diff --git a/pymode/libs/pylint/test/functional/tokenize_error.rc b/pymode/libs/pylint/test/functional/tokenize_error.rc new file mode 100644 index 00000000..eb52949d --- /dev/null +++ b/pymode/libs/pylint/test/functional/tokenize_error.rc @@ -0,0 +1,2 @@ +[testoptions] +except_implementations=Jython \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/tokenize_error.txt b/pymode/libs/pylint/test/functional/tokenize_error.txt new file mode 100644 index 00000000..4e65f712 --- /dev/null +++ b/pymode/libs/pylint/test/functional/tokenize_error.txt @@ -0,0 +1 @@ +syntax-error:7::EOF in multi-line statement diff --git a/pymode/libs/pylint/test/functional/tokenize_error_jython.py b/pymode/libs/pylint/test/functional/tokenize_error_jython.py new file mode 100644 index 00000000..205f5fc6 --- /dev/null +++ b/pymode/libs/pylint/test/functional/tokenize_error_jython.py @@ -0,0 +1,7 @@ +# [syntax-error] +"""A module that is accepted by Python but rejected by tokenize. + +The problem is the trailing line continuation at the end of the line, +which produces a TokenError.""" + +""\ diff --git a/pymode/libs/pylint/test/functional/tokenize_error_jython.rc b/pymode/libs/pylint/test/functional/tokenize_error_jython.rc new file mode 100644 index 00000000..79ffac09 --- /dev/null +++ b/pymode/libs/pylint/test/functional/tokenize_error_jython.rc @@ -0,0 +1,2 @@ +[testoptions] +except_implementations=CPython,PyPy,IronPython \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/tokenize_error_jython.txt b/pymode/libs/pylint/test/functional/tokenize_error_jython.txt new file mode 100644 index 00000000..82e662eb --- /dev/null +++ b/pymode/libs/pylint/test/functional/tokenize_error_jython.txt @@ -0,0 +1 @@ +syntax-error:1::unexpected character after line continuation character \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/too_few_public_methods.py b/pymode/libs/pylint/test/functional/too_few_public_methods.py new file mode 100644 index 00000000..9b3c4b2d --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_few_public_methods.py @@ -0,0 +1,29 @@ +# pylint: disable=missing-docstring +from __future__ import print_function + +class Aaaa(object): # [too-few-public-methods] + + def __init__(self): + pass + + def meth1(self): + print(self) + + def _dontcount(self): + print(self) + + +# Don't emit for these cases. +class Klass(object): + """docstring""" + + def meth1(self): + """first""" + + def meth2(self): + """second""" + + +class EnoughPublicMethods(Klass): + """We shouldn't emit too-few-public-methods for this.""" + diff --git a/pymode/libs/pylint/test/functional/too_few_public_methods.txt b/pymode/libs/pylint/test/functional/too_few_public_methods.txt new file mode 100644 index 00000000..be0e73ef --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_few_public_methods.txt @@ -0,0 +1 @@ +too-few-public-methods:4:Aaaa:Too few public methods (1/2) diff --git a/pymode/libs/pylint/test/functional/too_many_ancestors.py b/pymode/libs/pylint/test/functional/too_many_ancestors.py new file mode 100644 index 00000000..e0f7be23 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_ancestors.py @@ -0,0 +1,25 @@ +# pylint: disable=missing-docstring, too-few-public-methods + +class Aaaa(object): + pass +class Bbbb(object): + pass +class Cccc(object): + pass +class Dddd(object): + pass +class Eeee(object): + pass +class Ffff(object): + pass +class Gggg(object): + pass +class Hhhh(object): + pass + +class Iiii(Aaaa, Bbbb, Cccc, Dddd, Eeee, Ffff, Gggg, Hhhh): # [too-many-ancestors] + pass + +class Jjjj(Iiii): # [too-many-ancestors] + pass + diff --git a/pymode/libs/pylint/test/functional/too_many_ancestors.txt b/pymode/libs/pylint/test/functional/too_many_ancestors.txt new file mode 100644 index 00000000..e9f0841c --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_ancestors.txt @@ -0,0 +1,2 @@ +too-many-ancestors:20:Iiii:Too many ancestors (9/7) +too-many-ancestors:23:Jjjj:Too many ancestors (10/7) \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/too_many_arguments.py b/pymode/libs/pylint/test/functional/too_many_arguments.py new file mode 100644 index 00000000..99f4e78f --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_arguments.py @@ -0,0 +1,4 @@ +# pylint: disable=missing-docstring + +def stupid_function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9): # [too-many-arguments] + return arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 diff --git a/pymode/libs/pylint/test/functional/too_many_arguments.txt b/pymode/libs/pylint/test/functional/too_many_arguments.txt new file mode 100644 index 00000000..8f99a462 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_arguments.txt @@ -0,0 +1 @@ +too-many-arguments:3:stupid_function:Too many arguments (9/5) diff --git a/pymode/libs/pylint/test/functional/too_many_boolean_expressions.py b/pymode/libs/pylint/test/functional/too_many_boolean_expressions.py new file mode 100644 index 00000000..b3dc2387 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_boolean_expressions.py @@ -0,0 +1,20 @@ +"""Checks for if statements containing too many boolean expressions""" + +# pylint: disable=invalid-name + +x = y = z = 5 +if x > -5 and x < 5 and y > -5 and y < 5 and z > -5 and z < 5: # [too-many-boolean-expressions] + pass +elif True and False and 1 and 2 and 3: + pass +elif True and False and 1 and 2 and 3 and 4 and 5: # [too-many-boolean-expressions] + pass +elif True and (True and True) and (x == 5 or True or True): # [too-many-boolean-expressions] + pass +elif True and (True or (x > -5 and x < 5 and (z > -5 or z < 5))): # [too-many-boolean-expressions] + pass +elif True == True == True == True == True == True: + pass + +if True and False and 1 and 2 and 3: + pass diff --git a/pymode/libs/pylint/test/functional/too_many_boolean_expressions.txt b/pymode/libs/pylint/test/functional/too_many_boolean_expressions.txt new file mode 100644 index 00000000..0bb0086c --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_boolean_expressions.txt @@ -0,0 +1,4 @@ +too-many-boolean-expressions:6::Too many boolean expressions in if statement (6/5) +too-many-boolean-expressions:10::Too many boolean expressions in if statement (7/5) +too-many-boolean-expressions:12::Too many boolean expressions in if statement (6/5) +too-many-boolean-expressions:14::Too many boolean expressions in if statement (6/5) diff --git a/pymode/libs/pylint/test/functional/too_many_branches.py b/pymode/libs/pylint/test/functional/too_many_branches.py new file mode 100644 index 00000000..7b0b068a --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_branches.py @@ -0,0 +1,69 @@ +""" Test for too many branches. """ +# pylint: disable=using-constant-test +def wrong(): # [too-many-branches] + """ Has too many branches. """ + if 1: + pass + elif 1: + pass + elif 1: + pass + elif 1: + pass + elif 1: + pass + elif 1: + pass + try: + pass + finally: + pass + if 2: + pass + while True: + pass + if 1: + pass + elif 2: + pass + elif 3: + pass + +def good(): + """ Too many branches only if we take + into consideration the nested functions. + """ + def nested_1(): + """ empty """ + if 1: + pass + elif 2: + pass + elif 3: + pass + elif 4: + pass + + nested_1() + try: + pass + finally: + pass + try: + pass + finally: + pass + if 1: + pass + elif 2: + pass + elif 3: + pass + elif 4: + pass + elif 5: + pass + elif 6: + pass + elif 7: + pass diff --git a/pymode/libs/pylint/test/functional/too_many_branches.txt b/pymode/libs/pylint/test/functional/too_many_branches.txt new file mode 100644 index 00000000..fbc82ccc --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_branches.txt @@ -0,0 +1 @@ +too-many-branches:3:wrong:Too many branches (13/12) diff --git a/pymode/libs/pylint/test/functional/too_many_instance_attributes.py b/pymode/libs/pylint/test/functional/too_many_instance_attributes.py new file mode 100644 index 00000000..b77efdb7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_instance_attributes.py @@ -0,0 +1,26 @@ +# pylint: disable=missing-docstring, too-few-public-methods + +class Aaaa(object): # [too-many-instance-attributes] + + 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/pymode/libs/pylint/test/functional/too_many_instance_attributes.txt b/pymode/libs/pylint/test/functional/too_many_instance_attributes.txt new file mode 100644 index 00000000..652ccea3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_instance_attributes.txt @@ -0,0 +1 @@ +too-many-instance-attributes:3:Aaaa:Too many instance attributes (21/7) diff --git a/pymode/libs/pylint/test/functional/too_many_lines.py b/pymode/libs/pylint/test/functional/too_many_lines.py new file mode 100644 index 00000000..78e568c2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_lines.py @@ -0,0 +1,1015 @@ +# pylint: disable=missing-docstring +# -1: [too-many-lines] +__revision__ = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ZERFZAER = 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +HEHEHE = 2 diff --git a/pymode/libs/pylint/test/functional/too_many_lines.txt b/pymode/libs/pylint/test/functional/too_many_lines.txt new file mode 100644 index 00000000..0374f0c0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_lines.txt @@ -0,0 +1 @@ +too-many-lines:1::Too many lines in module (1015/1000) \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/too_many_lines_disabled.py b/pymode/libs/pylint/test/functional/too_many_lines_disabled.py new file mode 100644 index 00000000..1b0866db --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_lines_disabled.py @@ -0,0 +1,1018 @@ +"""Test that disabling too-many-lines on any line works.""" + + + +# pylint: disable=too-many-lines +__revision__ = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ZERFZAER = 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +HEHEHE = 2 diff --git a/pymode/libs/pylint/test/functional/too_many_locals.py b/pymode/libs/pylint/test/functional/too_many_locals.py new file mode 100644 index 00000000..ac38a9e0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_locals.py @@ -0,0 +1,60 @@ +# pylint: disable=missing-docstring +from __future__ import print_function + +def function(arg1, arg2, arg3, arg4, arg5): # [too-many-locals] + 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) + + +def too_many_locals_function(): # [too-many-locals] + """pylint will complains about too many local variables""" + args0 = 0 + args1 = args0 * 1 + args2 = args1 * 2 + args3 = args2 * 3 + args4 = args3 * 4 + args5 = args4 * 5 + args6 = args5 * 6 + args7 = args6 * 7 + args8 = args7 * 8 + args9 = args8 * 9 + args10 = args9 * 10 + args11 = args10 * 11 + args12 = args11 * 12 + args13 = args12 * 13 + args14 = args13 * 14 + args15 = args14 * 15 + return args15 + +def too_many_arguments_function(arga, argu, argi, arge, argt, args): # [too-many-arguments] + """pylint will complains about too many arguments.""" + arga = argu + arga += argi + arga += arge + arga += argt + arga += args + return arga + +def ignored_arguments_function(arga, argu, argi, + _arge=0, _argt=1, _args=None): + """pylint will ignore _arge, _argt, _args. + + Consequently pylint will only coun 13 arguments. + """ + arg0 = 0 + arg1 = arg0 * 1 + arga + arg2 = arg1 * 2 + argu + arg3 = arg2 * 3 + argi + arg4 = arg3 * 4 + _arge + arg5 = arg4 * 5 + _argt + arg6 = arg5 * 6 + arg7 = arg6 * 7 + arg8 = arg7 * 8 + arg9 = arg8 * 9 + arg9 += arg0 + if _args: + arg9 += sum(_args) + return arg9 diff --git a/pymode/libs/pylint/test/functional/too_many_locals.txt b/pymode/libs/pylint/test/functional/too_many_locals.txt new file mode 100644 index 00000000..4a7d19d4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_locals.txt @@ -0,0 +1,3 @@ +too-many-locals:4:function:Too many local variables (16/15) +too-many-locals:12:too_many_locals_function:Too many local variables (16/15) +too-many-arguments:32:too_many_arguments_function:Too many arguments (6/5) \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/too_many_nested_blocks.py b/pymode/libs/pylint/test/functional/too_many_nested_blocks.py new file mode 100644 index 00000000..47dbf441 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_nested_blocks.py @@ -0,0 +1,95 @@ +"""Checks the maximum block level is smaller than 6 in function definitions""" + +#pylint: disable=using-constant-test, missing-docstring, too-many-return-statements + +def my_function(): + if 1: # [too-many-nested-blocks] + for i in range(10): + if i == 2: + while True: + try: + if True: + i += 1 + except IOError: + pass + + if 1: + for i in range(10): + if i == 2: + while True: + try: + i += 1 + except IOError: + pass + + def nested_func(): + if True: + for i in range(10): + while True: + if True: + if True: + yield i + + nested_func() + +def more_complex_function(): + attr1 = attr2 = attr3 = [1, 2, 3] + if attr1: + for i in attr1: + if attr2: + return i + else: + return 'duh' + elif attr2: + for i in attr2: + if attr2: + return i + else: + return 'duh' + else: + for i in range(15): + if attr3: + return i + else: + return 'doh' + return None + +def elif_function(): + arg = None + 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 + +def else_if_function(): + arg = None + if arg == 1: # [too-many-nested-blocks] + return 1 + else: + if arg == 2: + return 2 + else: + if arg == 3: + return 3 + else: + if arg == 4: + return 4 + else: + if arg == 5: + return 5 + else: + if arg == 6: + return 6 + else: + if arg == 7: + return 7 diff --git a/pymode/libs/pylint/test/functional/too_many_nested_blocks.txt b/pymode/libs/pylint/test/functional/too_many_nested_blocks.txt new file mode 100644 index 00000000..dcee9d2a --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_nested_blocks.txt @@ -0,0 +1,2 @@ +too-many-nested-blocks:6:my_function:Too many nested blocks (6/5) +too-many-nested-blocks:76:else_if_function:Too many nested blocks (7/5) diff --git a/pymode/libs/pylint/test/functional/too_many_public_methods.py b/pymode/libs/pylint/test/functional/too_many_public_methods.py new file mode 100644 index 00000000..df6134f3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_public_methods.py @@ -0,0 +1,79 @@ +# pylint: disable=missing-docstring + +class Aaaa(object): # [too-many-public-methods] + + def __init__(self): + pass + + def meth1(self): + """hehehe""" + + def meth2(self): + """hehehe""" + + def meth3(self): + """hehehe""" + + def meth4(self): + """hehehe""" + + def meth5(self): + """hehehe""" + + def meth6(self): + """hehehe""" + + def meth7(self): + """hehehe""" + + def meth8(self): + """hehehe""" + + def meth9(self): + """hehehe""" + + def meth10(self): + """hehehe""" + + def meth11(self): + """hehehe""" + + def meth12(self): + """hehehe""" + + def meth13(self): + """hehehe""" + + def meth14(self): + """hehehe""" + + def meth15(self): + """hehehe""" + + def meth16(self): + """hehehe""" + + def meth17(self): + """hehehe""" + + def meth18(self): + """hehehe""" + + def meth19(self): + """hehehe""" + + def meth20(self): + """hehehe""" + + def meth21(self): + """hehehe""" + + def _dontcount(self): + """not public""" + +class BBB(Aaaa): + """Don't emit for methods defined in the parent.""" + def meth1(self): + """trop""" + def meth2(self): + """tzop""" diff --git a/pymode/libs/pylint/test/functional/too_many_public_methods.txt b/pymode/libs/pylint/test/functional/too_many_public_methods.txt new file mode 100644 index 00000000..1b5a2501 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_public_methods.txt @@ -0,0 +1 @@ +too-many-public-methods:3:Aaaa:Too many public methods (21/20) diff --git a/pymode/libs/pylint/test/functional/too_many_return_statements.py b/pymode/libs/pylint/test/functional/too_many_return_statements.py new file mode 100644 index 00000000..7ae61a9c --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_return_statements.py @@ -0,0 +1,24 @@ +# pylint: disable=missing-docstring + +def stupid_function(arg): # [too-many-return-statements] + 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/pymode/libs/pylint/test/functional/too_many_return_statements.txt b/pymode/libs/pylint/test/functional/too_many_return_statements.txt new file mode 100644 index 00000000..4f65db22 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_return_statements.txt @@ -0,0 +1 @@ +too-many-return-statements:3:stupid_function:Too many return statements (11/6) \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/too_many_star_expressions.py b/pymode/libs/pylint/test/functional/too_many_star_expressions.py new file mode 100644 index 00000000..a6c21af5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_star_expressions.py @@ -0,0 +1,4 @@ +"""Test for too-many-star-expressions.""" + +*FIRST, *SECOND = [1, 2, 3] # [too-many-star-expressions] +*FIRST_1, SECOND = (1, 2, 3) diff --git a/pymode/libs/pylint/test/functional/too_many_star_expressions.rc b/pymode/libs/pylint/test/functional/too_many_star_expressions.rc new file mode 100644 index 00000000..c093be20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_star_expressions.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/too_many_star_expressions.txt b/pymode/libs/pylint/test/functional/too_many_star_expressions.txt new file mode 100644 index 00000000..c9b8b4f8 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_star_expressions.txt @@ -0,0 +1 @@ +too-many-star-expressions:3::More than one starred expression in assignment \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/too_many_statements.py b/pymode/libs/pylint/test/functional/too_many_statements.py new file mode 100644 index 00000000..305db807 --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_statements.py @@ -0,0 +1,60 @@ +# pylint: disable=missing-docstring + +from __future__ import print_function + +def stupid_function(arg): # [too-many-statements] + if arg == 1: + print(1) + elif arg == 2: + print(1) + elif arg == 3: + print(1) + elif arg == 4: + print(1) + elif arg == 5: + print(1) + elif arg == 6: + print(1) + elif arg == 7: + print(1) + elif arg == 8: + print(1) + elif arg == 9: + print(1) + elif arg == 10: + print(1) + elif arg < 1: + print(1) + print(1) + arg = 0 + for _ in range(arg): + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) + print(1) diff --git a/pymode/libs/pylint/test/functional/too_many_statements.txt b/pymode/libs/pylint/test/functional/too_many_statements.txt new file mode 100644 index 00000000..255e97ee --- /dev/null +++ b/pymode/libs/pylint/test/functional/too_many_statements.txt @@ -0,0 +1 @@ +too-many-statements:5:stupid_function:Too many statements (55/50) diff --git a/pymode/libs/pylint/test/functional/trailing_whitespaces.py b/pymode/libs/pylint/test/functional/trailing_whitespaces.py new file mode 100644 index 00000000..48de809c --- /dev/null +++ b/pymode/libs/pylint/test/functional/trailing_whitespaces.py @@ -0,0 +1,11 @@ +"""Regression test for trailing-whitespace (C0303).""" +# pylint: disable=mixed-line-endings +from __future__ import print_function + +# +1: [trailing-whitespace] +print('some trailing whitespace') +# +1: [trailing-whitespace] +print('trailing whitespace does not count towards the line length limit') +print('windows line ends are ok') +# +1: [trailing-whitespace] +print('but trailing whitespace on win is not') diff --git a/pymode/libs/pylint/test/functional/trailing_whitespaces.txt b/pymode/libs/pylint/test/functional/trailing_whitespaces.txt new file mode 100644 index 00000000..38fc2c32 --- /dev/null +++ b/pymode/libs/pylint/test/functional/trailing_whitespaces.txt @@ -0,0 +1,3 @@ +trailing-whitespace:6::Trailing whitespace +trailing-whitespace:8::Trailing whitespace +trailing-whitespace:11::Trailing whitespace diff --git a/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking.py b/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking.py new file mode 100644 index 00000000..98f56408 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking.py @@ -0,0 +1,108 @@ +"""Check possible unbalanced tuple unpacking """ +from __future__ import absolute_import +from functional.unpacking import unpack + +# pylint: disable=using-constant-test + +def do_stuff(): + """This is not right.""" + first, second = 1, 2, 3 # [unbalanced-tuple-unpacking] + return first + second + +def do_stuff1(): + """This is not right.""" + first, second = [1, 2, 3] # [unbalanced-tuple-unpacking] + return first + second + +def do_stuff2(): + """This is not right.""" + (first, second) = 1, 2, 3 # [unbalanced-tuple-unpacking] + return first + second + +def do_stuff3(): + """This is not right.""" + first, second = range(100) + return first + second + +def do_stuff4(): + """ This is right """ + first, second = 1, 2 + return first + second + +def do_stuff5(): + """ This is also right """ + first, second = (1, 2) + return first + second + +def do_stuff6(): + """ This is right """ + (first, second) = (1, 2) + return first + second + +def temp(): + """ This is not weird """ + if True: + return [1, 2] + return [2, 3, 4] + +def do_stuff7(): + """ This is not right, but we're not sure """ + first, second = temp() + return first + second + +def temp2(): + """ This is weird, but correct """ + if True: + return (1, 2) + else: + if True: + return (2, 3) + return (4, 5) + +def do_stuff8(): + """ This is correct """ + first, second = temp2() + return first + second + +def do_stuff9(): + """ This is not correct """ + first, second = unpack() # [unbalanced-tuple-unpacking] + return first + second + +class UnbalancedUnpacking(object): + """ Test unbalanced tuple unpacking in instance attributes. """ + # pylint: disable=attribute-defined-outside-init, invalid-name, too-few-public-methods + def test(self): + """ unpacking in instance attributes """ + # we're not sure if temp() returns two or three values + # so we shouldn't emit an error + self.a, self.b = temp() + self.a, self.b = temp2() + self.a, self.b = unpack() # [unbalanced-tuple-unpacking] + + +def issue329(*args): + """ Don't emit unbalanced tuple unpacking if the + rhs of the assignment is a variable-length argument, + because we don't know the actual length of the tuple. + """ + first, second, third = args + return first, second, third + + +def test_decimal(): + """Test a false positive with decimal.Decimal.as_tuple + + See astroid https://bitbucket.org/logilab/astroid/issues/92/ + """ + from decimal import Decimal + dec = Decimal(2) + first, second, third = dec.as_tuple() + return first, second, third + + +def test_issue_559(): + """Test that we don't have a false positive wrt to issue #559.""" + from ctypes import c_int + root_x, root_y, win_x, win_y = [c_int()] * 4 + return root_x, root_y, win_x, win_y diff --git a/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking.txt b/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking.txt new file mode 100644 index 00000000..e904209e --- /dev/null +++ b/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking.txt @@ -0,0 +1,6 @@ +unbalanced-tuple-unpacking:9:do_stuff:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:14:do_stuff1:"Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:19:do_stuff2:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:24:do_stuff3:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:69:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.unpacking: left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:81:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.unpacking: left side has 2 label(s), right side has 3 value(s)" diff --git a/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking_py30.py b/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking_py30.py new file mode 100644 index 00000000..68f5fb79 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking_py30.py @@ -0,0 +1,11 @@ +""" Test that using starred nodes in unpacking +does not trigger a false positive on Python 3. +""" + +__revision__ = 1 + +def test(): + """ Test that starred expressions don't give false positives. """ + first, second, *last = (1, 2, 3, 4) + *last, = (1, 2) + return (first, second, last) diff --git a/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking_py30.rc b/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking_py30.rc new file mode 100644 index 00000000..8c6eb56c --- /dev/null +++ b/pymode/libs/pylint/test/functional/unbalanced_tuple_unpacking_py30.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/undefined_variable.py b/pymode/libs/pylint/test/functional/undefined_variable.py new file mode 100644 index 00000000..94969e28 --- /dev/null +++ b/pymode/libs/pylint/test/functional/undefined_variable.py @@ -0,0 +1,177 @@ +# pylint: disable=missing-docstring, multiple-statements, redefined-variable-type +# pylint: disable=too-few-public-methods, no-init, no-self-use, old-style-class,bare-except,broad-except +from __future__ import print_function +DEFINED = 1 + +if DEFINED != 1: + if DEFINED in (unknown, DEFINED): # [undefined-variable] + DEFINED += 1 + + +def in_method(var): + """method doc""" + var = nomoreknown # [undefined-variable] + assert var + +DEFINED = {DEFINED:__revision__} # [undefined-variable] +# +1:[undefined-variable] +DEFINED[__revision__] = OTHER = 'move this is astroid test' + +OTHER += '$' + +def bad_default(var, default=unknown2): # [undefined-variable] + """function with defaut arg's value set to an unexistant name""" + print(var, default) + print(xxxx) # [undefined-variable] + augvar += 1 # [undefined-variable] + del vardel # [undefined-variable] + +LMBD = lambda x, y=doesnotexist: x+y # [undefined-variable] +LMBD2 = lambda x, y: x+z # [undefined-variable] + +try: + POUET # don't catch me +except NameError: + POUET = 'something' + +try: + POUETT # don't catch me +except Exception: # pylint:disable = broad-except + POUETT = 'something' + +try: + POUETTT # don't catch me +except: # pylint:disable = bare-except + POUETTT = 'something' + +print(POUET, POUETT, POUETTT) + + +try: + PLOUF # [used-before-assignment] +except ValueError: + PLOUF = 'something' + +print(PLOUF) + +def if_branch_test(something): + """hop""" + if something == 0: + if xxx == 1: # [used-before-assignment] + pass + else: + print(xxx) + xxx = 3 + + +def decorator(arg): + """Decorator with one argument.""" + return lambda: list(arg) + + +@decorator(arg=[i * 2 for i in range(15)]) +def func1(): + """A function with a decorator that contains a listcomp.""" + +@decorator(arg=(i * 2 for i in range(15))) +def func2(): + """A function with a decorator that contains a genexpr.""" + +@decorator(lambda x: x > 0) +def main(): + """A function with a decorator that contains a lambda.""" + +# Test shared scope. + +def test_arguments(arg=TestClass): # [used-before-assignment] + """ TestClass isn't defined yet. """ + return arg + +class TestClass(Ancestor): # [used-before-assignment] + """ contains another class, which uses an undefined ancestor. """ + + class MissingAncestor(Ancestor1): # [used-before-assignment] + """ no op """ + + def test1(self): + """ It should trigger here, because the two classes + have the same scope. + """ + class UsingBeforeDefinition(Empty): # [used-before-assignment] + """ uses Empty before definition """ + class Empty(object): + """ no op """ + return UsingBeforeDefinition + + def test(self): + """ Ancestor isn't defined yet, but we don't care. """ + class MissingAncestor1(Ancestor): + """ no op """ + return MissingAncestor1 + +class Self(object): + """ Detect when using the same name inside the class scope. """ + obj = Self # [undefined-variable] + +class Self1(object): + """ No error should be raised here. """ + + def test(self): + """ empty """ + return Self1 + + +class Ancestor(object): + """ No op """ + +class Ancestor1(object): + """ No op """ + +NANA = BAT # [undefined-variable] +del BAT + + +class KeywordArgument(object): + """Test keyword arguments.""" + + enable = True + def test(self, is_enabled=enable): + """do nothing.""" + + def test1(self, is_enabled=enabled): # [used-before-assignment] + """enabled is undefined at this point, but it is used before assignment.""" + + def test2(self, is_disabled=disabled): # [undefined-variable] + """disabled is undefined""" + + enabled = True + + func = lambda arg=arg: arg * arg # [undefined-variable] + + arg2 = 0 + func2 = lambda arg2=arg2: arg2 * arg2 + +# Don't emit if the code is protected by NameError +try: + unicode_1 +except NameError: + pass + +try: + unicode_2 # [undefined-variable] +except Exception: + pass + +try: + unicode_3 # [undefined-variable] +except: + pass + +try: + unicode_4 # [undefined-variable] +except ValueError: + pass + +# See https://bitbucket.org/logilab/pylint/issue/111/ +try: raise IOError(1, "a") +except IOError as err: print(err) diff --git a/pymode/libs/pylint/test/functional/undefined_variable.txt b/pymode/libs/pylint/test/functional/undefined_variable.txt new file mode 100644 index 00000000..98c6bbed --- /dev/null +++ b/pymode/libs/pylint/test/functional/undefined_variable.txt @@ -0,0 +1,24 @@ +undefined-variable:7::Undefined variable 'unknown' +undefined-variable:13:in_method:Undefined variable 'nomoreknown' +undefined-variable:16::Undefined variable '__revision__' +undefined-variable:18::Undefined variable '__revision__' +undefined-variable:22:bad_default:Undefined variable 'unknown2' +undefined-variable:25:bad_default:Undefined variable 'xxxx' +undefined-variable:26:bad_default:Undefined variable 'augvar' +undefined-variable:27:bad_default:Undefined variable 'vardel' +undefined-variable:29::Undefined variable 'doesnotexist' +undefined-variable:30::Undefined variable 'z' +used-before-assignment:51::Using variable 'PLOUF' before assignment +used-before-assignment:60:if_branch_test:Using variable 'xxx' before assignment +used-before-assignment:86:test_arguments:Using variable 'TestClass' before assignment +used-before-assignment:90:TestClass:Using variable 'Ancestor' before assignment +used-before-assignment:93:TestClass.MissingAncestor:Using variable 'Ancestor1' before assignment +used-before-assignment:100:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment +undefined-variable:114:Self:Undefined variable 'Self' +undefined-variable:130::Undefined variable 'BAT' +used-before-assignment:141:KeywordArgument.test1:Using variable 'enabled' before assignment +undefined-variable:144:KeywordArgument.test2:Undefined variable 'disabled' +undefined-variable:149:KeywordArgument.:Undefined variable 'arg' +undefined-variable:161::Undefined variable 'unicode_2' +undefined-variable:166::Undefined variable 'unicode_3' +undefined-variable:171::Undefined variable 'unicode_4' \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/undefined_variable_py30.py b/pymode/libs/pylint/test/functional/undefined_variable_py30.py new file mode 100644 index 00000000..3695cf14 --- /dev/null +++ b/pymode/libs/pylint/test/functional/undefined_variable_py30.py @@ -0,0 +1,78 @@ +"""Test warnings about access to undefined variables +for various Python 3 constructs. """ +# pylint: disable=too-few-public-methods, no-init, no-self-use +# pylint: disable=wrong-import-position +class Undefined: + """ test various annotation problems. """ + + def test(self)->Undefined: # [undefined-variable] + """ used Undefined, which is Undefined in this scope. """ + + Undefined = True + + def test1(self)->Undefined: + """ This Undefined exists at local scope. """ + + def test2(self): + """ This should not emit. """ + def func()->Undefined: + """ empty """ + return + return func + + +class Undefined1: + """ Other annotation problems. """ + + Undef = 42 + ABC = 42 + + class InnerScope: + """ Test inner scope definition. """ + + def test_undefined(self)->Undef: # [undefined-variable] + """ Looking at a higher scope is impossible. """ + + def test1(self)->ABC: # [undefined-variable] + """ Triggers undefined-variable. """ + + +class FalsePositive342(object): + # pylint: disable=line-too-long + """ Fix some false positives found in + https://bitbucket.org/logilab/pylint/issue/342/spurious-undefined-variable-for-class + """ + + top = 42 + + def test_good(self, bac: top): + """ top is defined at this moment. """ + + def test_bad(self, bac: trop): # [undefined-variable] + """ trop is undefined at this moment. """ + + def test_bad1(self, *args: trop1): # [undefined-variable] + """ trop1 is undefined at this moment. """ + + def test_bad2(self, **bac: trop2): # [undefined-variable] + """ trop2 is undefined at this moment. """ + +from abc import ABCMeta + +class Bad(metaclass=ABCMet): # [undefined-variable] + """ Notice the typo """ + +class SecondBad(metaclass=ab.ABCMeta): # [undefined-variable] + """ Notice the `ab` module. """ + +class Good(metaclass=int): + """ int is not a proper metaclass, but it is defined. """ + +class SecondGood(metaclass=Good): + """ empty """ + +class ThirdGood(metaclass=ABCMeta): + """ empty """ + +class FourthGood(ThirdGood): + """ This should not trigger anything. """ diff --git a/pymode/libs/pylint/test/functional/undefined_variable_py30.rc b/pymode/libs/pylint/test/functional/undefined_variable_py30.rc new file mode 100644 index 00000000..c093be20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/undefined_variable_py30.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/undefined_variable_py30.txt b/pymode/libs/pylint/test/functional/undefined_variable_py30.txt new file mode 100644 index 00000000..28f67d35 --- /dev/null +++ b/pymode/libs/pylint/test/functional/undefined_variable_py30.txt @@ -0,0 +1,8 @@ +undefined-variable:8:Undefined.test:Undefined variable 'Undefined' +undefined-variable:33:Undefined1.InnerScope.test_undefined:Undefined variable 'Undef' +undefined-variable:36:Undefined1.InnerScope.test1:Undefined variable 'ABC' +undefined-variable:51:FalsePositive342.test_bad:Undefined variable 'trop' +undefined-variable:54:FalsePositive342.test_bad1:Undefined variable 'trop1' +undefined-variable:57:FalsePositive342.test_bad2:Undefined variable 'trop2' +undefined-variable:62:Bad:Undefined variable 'ABCMet' +undefined-variable:65:SecondBad:Undefined variable 'ab.ABCMeta' \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/unexpected_special_method_signature.py b/pymode/libs/pylint/test/functional/unexpected_special_method_signature.py new file mode 100644 index 00000000..41f02e53 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unexpected_special_method_signature.py @@ -0,0 +1,120 @@ +"""Test for special methods implemented incorrectly.""" + +# pylint: disable=missing-docstring, unused-argument, too-few-public-methods +# pylint: disable=invalid-name,too-many-arguments,bad-staticmethod-argument + +class Invalid(object): + + def __enter__(self, other): # [unexpected-special-method-signature] + pass + + def __del__(self, other): # [unexpected-special-method-signature] + pass + + def __format__(self, other, other2): # [unexpected-special-method-signature] + pass + + def __setattr__(self): # [unexpected-special-method-signature] + pass + + def __round__(self, invalid, args): # [unexpected-special-method-signature] + pass + + def __deepcopy__(self, memo, other): # [unexpected-special-method-signature] + pass + + def __iter__(): # [no-method-argument] + pass + + @staticmethod + def __getattr__(self, nanana): # [unexpected-special-method-signature] + pass + + +class FirstBadContextManager(object): + def __enter__(self): + return self + def __exit__(self, exc_type): # [unexpected-special-method-signature] + pass + +class SecondBadContextManager(object): + def __enter__(self): + return self + def __exit__(self, exc_type, value, tb, stack): # [unexpected-special-method-signature] + pass + +class ThirdBadContextManager(object): + def __enter__(self): + return self + + # +1: [unexpected-special-method-signature] + def __exit__(self, exc_type, value, tb, stack, *args): + pass + + +class Async(object): + + def __aiter__(self, extra): # [unexpected-special-method-signature] + pass + def __anext__(self, extra, argument): # [unexpected-special-method-signature] + pass + def __await__(self, param): # [unexpected-special-method-signature] + pass + def __aenter__(self, first): # [unexpected-special-method-signature] + pass + def __aexit__(self): # [unexpected-special-method-signature] + pass + + +class Valid(object): + + def __new__(cls, test, multiple, args): + pass + + def __init__(self, this, can, have, multiple, args, as_well): + pass + + def __call__(self, also, trv, for_this): + pass + + def __round__(self, n): + pass + + def __index__(self, n=42): + """Expects 0 args, but we are taking in account arguments with defaults.""" + + def __deepcopy__(self, memo): + pass + + def __format__(self, format_specification=''): + pass + + def __copy__(self, this=None, is_not=None, necessary=None): + pass + + @staticmethod + def __enter__(): + pass + + @staticmethod + def __getitem__(index): + pass + + +class FirstGoodContextManager(object): + def __enter__(self): + return self + def __exit__(self, exc_type, value, tb): + pass + +class SecondGoodContextManager(object): + def __enter__(self): + return self + def __exit__(self, exc_type=None, value=None, tb=None): + pass + +class ThirdGoodContextManager(object): + def __enter__(self): + return self + def __exit__(self, exc_type, *args): + pass diff --git a/pymode/libs/pylint/test/functional/unexpected_special_method_signature.txt b/pymode/libs/pylint/test/functional/unexpected_special_method_signature.txt new file mode 100644 index 00000000..4fb52df6 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unexpected_special_method_signature.txt @@ -0,0 +1,16 @@ +unexpected-special-method-signature:8:Invalid.__enter__:The special method '__enter__' expects 0 param(s), 1 was given +unexpected-special-method-signature:11:Invalid.__del__:The special method '__del__' expects 0 param(s), 1 was given +unexpected-special-method-signature:14:Invalid.__format__:The special method '__format__' expects 1 param(s), 2 were given +unexpected-special-method-signature:17:Invalid.__setattr__:The special method '__setattr__' expects 2 param(s), 0 was given +unexpected-special-method-signature:20:Invalid.__round__:The special method '__round__' expects between 0 or 1 param(s), 2 were given +unexpected-special-method-signature:23:Invalid.__deepcopy__:The special method '__deepcopy__' expects 1 param(s), 2 were given +no-method-argument:26:Invalid.__iter__:Method has no argument +unexpected-special-method-signature:30:Invalid.__getattr__:The special method '__getattr__' expects 1 param(s), 2 were given +unexpected-special-method-signature:37:FirstBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 1 was given +unexpected-special-method-signature:43:SecondBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 4 were given +unexpected-special-method-signature:51:ThirdBadContextManager.__exit__:The special method '__exit__' expects 3 param(s), 4 were given +unexpected-special-method-signature:57:Async.__aiter__:The special method '__aiter__' expects 0 param(s), 1 was given +unexpected-special-method-signature:59:Async.__anext__:The special method '__anext__' expects 0 param(s), 2 were given +unexpected-special-method-signature:61:Async.__await__:The special method '__await__' expects 0 param(s), 1 was given +unexpected-special-method-signature:63:Async.__aenter__:The special method '__aenter__' expects 0 param(s), 1 was given +unexpected-special-method-signature:65:Async.__aexit__:The special method '__aexit__' expects 3 param(s), 0 was given \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/ungrouped_imports.py b/pymode/libs/pylint/test/functional/ungrouped_imports.py new file mode 100644 index 00000000..2204c803 --- /dev/null +++ b/pymode/libs/pylint/test/functional/ungrouped_imports.py @@ -0,0 +1,20 @@ +"""Checks import order rule""" +# pylint: disable=unused-import,relative-import,wrong-import-order,using-constant-test +# pylint: disable=import-error +import six +import logging.config +import os.path +from astroid import are_exclusive +import logging # [ungrouped-imports] +import unused_import +try: + import os # [ungrouped-imports] +except ImportError: + pass +from os import pardir +import scipy +from os import sep +import astroid # [ungrouped-imports] +if True: + import logging.handlers # [ungrouped-imports] +from os.path import join # [ungrouped-imports] diff --git a/pymode/libs/pylint/test/functional/ungrouped_imports.txt b/pymode/libs/pylint/test/functional/ungrouped_imports.txt new file mode 100644 index 00000000..c29bb181 --- /dev/null +++ b/pymode/libs/pylint/test/functional/ungrouped_imports.txt @@ -0,0 +1,5 @@ +ungrouped-imports:8::Imports from package logging are not grouped +ungrouped-imports:11::Imports from package os are not grouped +ungrouped-imports:17::Imports from package astroid are not grouped +ungrouped-imports:19::Imports from package logging are not grouped +ungrouped-imports:20::Imports from package os are not grouped diff --git a/pymode/libs/pylint/test/functional/unidiomatic_typecheck.py b/pymode/libs/pylint/test/functional/unidiomatic_typecheck.py new file mode 100644 index 00000000..008456b1 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unidiomatic_typecheck.py @@ -0,0 +1,76 @@ +"""Warnings for using type(x) == Y or type(x) is Y instead of isinstance(x, Y).""" +# pylint: disable=missing-docstring,expression-not-assigned,redefined-builtin,invalid-name + +def simple_positives(): + type(42) is int # [unidiomatic-typecheck] + type(42) is not int # [unidiomatic-typecheck] + type(42) == int # [unidiomatic-typecheck] + type(42) != int # [unidiomatic-typecheck] + type(42) in [int] # [unidiomatic-typecheck] + type(42) not in [int] # [unidiomatic-typecheck] + +def simple_inference_positives(): + alias = type + alias(42) is int # [unidiomatic-typecheck] + alias(42) is not int # [unidiomatic-typecheck] + alias(42) == int # [unidiomatic-typecheck] + alias(42) != int # [unidiomatic-typecheck] + alias(42) in [int] # [unidiomatic-typecheck] + alias(42) not in [int] # [unidiomatic-typecheck] + +def type_creation_negatives(): + type('Q', (object,), dict(a=1)) is int + type('Q', (object,), dict(a=1)) is not int + type('Q', (object,), dict(a=1)) == int + type('Q', (object,), dict(a=1)) != int + type('Q', (object,), dict(a=1)) in [int] + type('Q', (object,), dict(a=1)) not in [int] + +def invalid_type_call_negatives(**kwargs): + type(bad=7) is int + type(bad=7) is not int + type(bad=7) == int + type(bad=7) != int + type(bad=7) in [int] + type(bad=7) not in [int] + type('bad', 7) is int + type('bad', 7) is not int + type('bad', 7) == int + type('bad', 7) != int + type('bad', 7) in [int] + type('bad', 7) not in [int] + type(**kwargs) is int + type(**kwargs) is not int + type(**kwargs) == int + type(**kwargs) != int + type(**kwargs) in [int] + type(**kwargs) not in [int] + +def local_var_shadowing_inference_negatives(): + type = lambda dummy: 7 + type(42) is int + type(42) is not int + type(42) == int + type(42) != int + type(42) in [int] + type(42) not in [int] + +def parameter_shadowing_inference_negatives(type): + type(42) is int + type(42) is not int + type(42) == int + type(42) != int + type(42) in [int] + type(42) not in [int] + +def deliberate_subclass_check_negatives(b): + type(42) is type(b) + type(42) is not type(b) + +def type_of_literals_positives(a): + type(a) is type([]) # [unidiomatic-typecheck] + type(a) is not type([]) # [unidiomatic-typecheck] + type(a) is type({}) # [unidiomatic-typecheck] + type(a) is not type({}) # [unidiomatic-typecheck] + type(a) is type("") # [unidiomatic-typecheck] + type(a) is not type("") # [unidiomatic-typecheck] diff --git a/pymode/libs/pylint/test/functional/unidiomatic_typecheck.txt b/pymode/libs/pylint/test/functional/unidiomatic_typecheck.txt new file mode 100644 index 00000000..23c0f6f2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unidiomatic_typecheck.txt @@ -0,0 +1,18 @@ +unidiomatic-typecheck:5:simple_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:6:simple_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:7:simple_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:8:simple_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:9:simple_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:10:simple_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:14:simple_inference_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:15:simple_inference_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:16:simple_inference_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:17:simple_inference_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:18:simple_inference_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:19:simple_inference_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:71:type_of_literals_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:72:type_of_literals_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:73:type_of_literals_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:74:type_of_literals_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:75:type_of_literals_positives:Using type() instead of isinstance() for a typecheck. +unidiomatic-typecheck:76:type_of_literals_positives:Using type() instead of isinstance() for a typecheck. \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/uninferable_all_object.py b/pymode/libs/pylint/test/functional/uninferable_all_object.py new file mode 100644 index 00000000..3e565f9e --- /dev/null +++ b/pymode/libs/pylint/test/functional/uninferable_all_object.py @@ -0,0 +1,9 @@ +"""Test that non-inferable __all__ variables do not make Pylint crash.""" + +__all__ = sorted([ + 'Dummy', + 'NonExistant', + 'path', + 'func', + 'inner', + 'InnerKlass']) diff --git a/pymode/libs/pylint/test/functional/unknown_encoding_jython.py b/pymode/libs/pylint/test/functional/unknown_encoding_jython.py new file mode 100644 index 00000000..a28cfed0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unknown_encoding_jython.py @@ -0,0 +1,7 @@ +# [syntax-error] +# -*- coding: IBO-8859-1 -*- +""" check correct unknown encoding declaration +""" + +__revision__ = '×™×™×™×™' + diff --git a/pymode/libs/pylint/test/functional/unknown_encoding_jython.rc b/pymode/libs/pylint/test/functional/unknown_encoding_jython.rc new file mode 100644 index 00000000..788d377b --- /dev/null +++ b/pymode/libs/pylint/test/functional/unknown_encoding_jython.rc @@ -0,0 +1,2 @@ +[testoptions] +except_implementations=PyPy,CPython,IronPython \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/unknown_encoding_jython.txt b/pymode/libs/pylint/test/functional/unknown_encoding_jython.txt new file mode 100644 index 00000000..b7ff720d --- /dev/null +++ b/pymode/libs/pylint/test/functional/unknown_encoding_jython.txt @@ -0,0 +1 @@ +syntax-error:1::"Unknown encoding: IBO-8859-1" diff --git a/pymode/libs/pylint/test/functional/unknown_encoding_py29.py b/pymode/libs/pylint/test/functional/unknown_encoding_py29.py new file mode 100644 index 00000000..aeb2be60 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unknown_encoding_py29.py @@ -0,0 +1,7 @@ +# [syntax-error] +# -*- coding: IBO-8859-1 -*- +""" check correct unknown encoding declaration +""" + +__revision__ = '×™×™×™×™' + diff --git a/pymode/libs/pylint/test/functional/unknown_encoding_py29.rc b/pymode/libs/pylint/test/functional/unknown_encoding_py29.rc new file mode 100644 index 00000000..51d35b4d --- /dev/null +++ b/pymode/libs/pylint/test/functional/unknown_encoding_py29.rc @@ -0,0 +1,3 @@ +[testoptions] +max_pyver=3.0 +except_implementations=PyPy,Jython \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/unknown_encoding_py29.txt b/pymode/libs/pylint/test/functional/unknown_encoding_py29.txt new file mode 100644 index 00000000..588b3bf3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unknown_encoding_py29.txt @@ -0,0 +1 @@ +syntax-error:1::"unknown encoding: IBO-8859-1" diff --git a/pymode/libs/pylint/test/functional/unknown_encoding_pypy.py b/pymode/libs/pylint/test/functional/unknown_encoding_pypy.py new file mode 100644 index 00000000..2ee5f694 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unknown_encoding_pypy.py @@ -0,0 +1,7 @@ +# +1: [syntax-error] +# -*- coding: IBO-8859-1 -*- +""" check correct unknown encoding declaration +""" + +__revision__ = '×™×™×™×™' + diff --git a/pymode/libs/pylint/test/functional/unknown_encoding_pypy.rc b/pymode/libs/pylint/test/functional/unknown_encoding_pypy.rc new file mode 100644 index 00000000..5bc0b70c --- /dev/null +++ b/pymode/libs/pylint/test/functional/unknown_encoding_pypy.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=3.0 +except_implementations=CPython, Jython, IronPython \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/unknown_encoding_pypy.txt b/pymode/libs/pylint/test/functional/unknown_encoding_pypy.txt new file mode 100644 index 00000000..e4c18053 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unknown_encoding_pypy.txt @@ -0,0 +1 @@ +syntax-error:1::"Unknown encoding: ibo-8859-1" \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/unnecessary_lambda.py b/pymode/libs/pylint/test/functional/unnecessary_lambda.py new file mode 100644 index 00000000..46171091 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unnecessary_lambda.py @@ -0,0 +1,51 @@ +# pylint: disable=undefined-variable +"""test suspicious lambda expressions +""" + +__revision__ = '' + +# Some simple examples of the most commonly encountered forms. +# +1: [unnecessary-lambda] +_ = lambda: list() # replaceable with "list" +# +1: [unnecessary-lambda] +_ = lambda x: hash(x) # replaceable with "hash" +# +1: [unnecessary-lambda] +_ = lambda x, y: min(x, y) # replaceable with "min" + +# A function that can take any arguments given to it. +_ANYARGS = lambda *args, **kwargs: 'completely arbitrary return value' + +# Some more complex forms of unnecessary lambda expressions. +# +1: [unnecessary-lambda] +_ = lambda *args: _ANYARGS(*args) +# +1: [unnecessary-lambda] +_ = lambda **kwargs: _ANYARGS(**kwargs) +# +1: [unnecessary-lambda] +_ = lambda *args, **kwargs: _ANYARGS(*args, **kwargs) +# +1: [unnecessary-lambda] +_ = lambda x, y, z, *args, **kwargs: _ANYARGS(x, y, z, *args, **kwargs) + +# Lambdas that are *not* unnecessary and should *not* trigger warnings. +_ = lambda x: x +_ = lambda x: x() +_ = lambda x=4: hash(x) +_ = lambda x, y: list(range(y, x)) +_ = lambda x: list(range(5, x)) +_ = lambda x, y: list(range(x, 5)) +_ = lambda x, y, z: x.y(z) +_ = lambda: 5 +_ = lambda **kwargs: _ANYARGS() +_ = lambda **kwargs: _ANYARGS(**dict([('three', 3)])) +_ = lambda **kwargs: _ANYARGS(**{'three': 3}) +_ = lambda dict_arg, **kwargs: _ANYARGS(kwargs, **dict_arg) +_ = lambda *args: _ANYARGS() +_ = lambda *args: _ANYARGS(*list([3, 4])) +_ = lambda *args: _ANYARGS(*[3, 4]) +_ = lambda list_arg, *args: _ANYARGS(args, *list_arg) +_ = lambda: _ANYARGS(*[3]) +_ = lambda: _ANYARGS(**{'three': 3}) +_ = lambda: _ANYARGS(*[3], **{'three': 3}) +_ = lambda: _ANYARGS(func=42) + +# Don't warn about this. +_ = lambda: code().analysis() diff --git a/pymode/libs/pylint/test/functional/unnecessary_lambda.txt b/pymode/libs/pylint/test/functional/unnecessary_lambda.txt new file mode 100644 index 00000000..de13882a --- /dev/null +++ b/pymode/libs/pylint/test/functional/unnecessary_lambda.txt @@ -0,0 +1,7 @@ +unnecessary-lambda:9::Lambda may not be necessary +unnecessary-lambda:11::Lambda may not be necessary +unnecessary-lambda:13::Lambda may not be necessary +unnecessary-lambda:20::Lambda may not be necessary +unnecessary-lambda:22::Lambda may not be necessary +unnecessary-lambda:24::Lambda may not be necessary +unnecessary-lambda:26::Lambda may not be necessary diff --git a/pymode/libs/pylint/test/functional/unnecessary_pass.py b/pymode/libs/pylint/test/functional/unnecessary_pass.py new file mode 100644 index 00000000..5bf7e8ba --- /dev/null +++ b/pymode/libs/pylint/test/functional/unnecessary_pass.py @@ -0,0 +1,7 @@ +# pylint: disable=missing-docstring + +try: + A = 2 +except ValueError: + A = 24 + pass # [unnecessary-pass] diff --git a/pymode/libs/pylint/test/functional/unnecessary_pass.txt b/pymode/libs/pylint/test/functional/unnecessary_pass.txt new file mode 100644 index 00000000..e3a74488 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unnecessary_pass.txt @@ -0,0 +1 @@ +unnecessary-pass:7::Unnecessary pass statement diff --git a/pymode/libs/pylint/test/functional/unneeded_not.py b/pymode/libs/pylint/test/functional/unneeded_not.py new file mode 100644 index 00000000..b95adc93 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unneeded_not.py @@ -0,0 +1,64 @@ +"""Check exceeding negations in boolean expressions trigger warnings""" + +# pylint: disable=singleton-comparison,too-many-branches,too-few-public-methods,undefined-variable + +def unneeded_not(): + """This is not ok + """ + bool_var = True + someint = 2 + if not not bool_var: # [unneeded-not] + pass + if not someint == 1: # [unneeded-not] + pass + if not someint != 1: # [unneeded-not] + pass + if not someint < 1: # [unneeded-not] + pass + if not someint > 1: # [unneeded-not] + pass + if not someint <= 1: # [unneeded-not] + pass + if not someint >= 1: # [unneeded-not] + pass + if not not someint: # [unneeded-not] + pass + if not bool_var == True: # [unneeded-not] + pass + if not bool_var == False: # [unneeded-not] + pass + if not bool_var != True: # [unneeded-not] + pass + if not True == True: # [unneeded-not] + pass + if not 2 in [3, 4]: # [unneeded-not] + pass + if not someint is 'test': # [unneeded-not] + pass + + +def tolerated_statements(): + """This is ok""" + bool_var = True + someint = 2 + if not(bool_var == False and someint == 1): + pass + if 2 not in [3, 4]: + pass + if not someint == bool_var == 2: + pass + if not 2 <= someint < 3 < 4: + pass + if not set('bar') <= set('foobaz'): + pass + if not set(something) <= 3: + pass + if not frozenset(something) <= 3: + pass + + +class Klass(object): + """This is also ok""" + def __ne__(self, other): + return not self == other + diff --git a/pymode/libs/pylint/test/functional/unneeded_not.txt b/pymode/libs/pylint/test/functional/unneeded_not.txt new file mode 100644 index 00000000..13b35129 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unneeded_not.txt @@ -0,0 +1,14 @@ +unneeded-not:10:unneeded_not:Consider changing "not not bool_var" to "bool_var" +unneeded-not:12:unneeded_not:Consider changing "not someint == 1" to "someint != 1" +unneeded-not:14:unneeded_not:Consider changing "not someint != 1" to "someint == 1" +unneeded-not:16:unneeded_not:Consider changing "not someint < 1" to "someint >= 1" +unneeded-not:18:unneeded_not:Consider changing "not someint > 1" to "someint <= 1" +unneeded-not:20:unneeded_not:Consider changing "not someint <= 1" to "someint > 1" +unneeded-not:22:unneeded_not:Consider changing "not someint >= 1" to "someint < 1" +unneeded-not:24:unneeded_not:Consider changing "not not someint" to "someint" +unneeded-not:26:unneeded_not:Consider changing "not bool_var == True" to "bool_var != True" +unneeded-not:28:unneeded_not:Consider changing "not bool_var == False" to "bool_var != False" +unneeded-not:30:unneeded_not:Consider changing "not bool_var != True" to "bool_var == True" +unneeded-not:32:unneeded_not:Consider changing "not True == True" to "True != True" +unneeded-not:34:unneeded_not:Consider changing "not 2 in [3, 4]" to "2 not in [3, 4]" +unneeded-not:36:unneeded_not:Consider changing "not someint is 'test'" to "someint is not 'test'" diff --git a/pymode/libs/pylint/test/functional/unpacked_exceptions.py b/pymode/libs/pylint/test/functional/unpacked_exceptions.py new file mode 100644 index 00000000..ec3599c3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unpacked_exceptions.py @@ -0,0 +1,11 @@ +"""Test for redefine-in-handler, overwriting names in exception handlers.""" + +def new_style(): + """Some exceptions can be unpacked.""" + try: + pass + except IOError, (errno, message): # [unpacking-in-except] + print errno, message # pylint: disable=print-statement + # +1: [redefine-in-handler,redefine-in-handler,unpacking-in-except] + except IOError, (new_style, tuple): # pylint: disable=duplicate-except + print new_style, tuple # pylint: disable=print-statement diff --git a/pymode/libs/pylint/test/functional/unpacked_exceptions.rc b/pymode/libs/pylint/test/functional/unpacked_exceptions.rc new file mode 100644 index 00000000..9540bc95 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unpacked_exceptions.rc @@ -0,0 +1,5 @@ +[testoptions] +max_pyver=3.0 + +[Messages Control] +enable=python3 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/unpacked_exceptions.txt b/pymode/libs/pylint/test/functional/unpacked_exceptions.txt new file mode 100644 index 00000000..1a1b0342 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unpacked_exceptions.txt @@ -0,0 +1,4 @@ +unpacking-in-except:7:new_style:Implicit unpacking of exceptions is not supported in Python 3 +redefine-in-handler:10:new_style:Redefining name 'new_style' from outer scope (line 3) in exception handler +redefine-in-handler:10:new_style:Redefining name 'tuple' from builtins in exception handler +unpacking-in-except:10:new_style:Implicit unpacking of exceptions is not supported in Python 3 diff --git a/pymode/libs/pylint/test/functional/unpacking.py b/pymode/libs/pylint/test/functional/unpacking.py new file mode 100644 index 00000000..59d9abbe --- /dev/null +++ b/pymode/libs/pylint/test/functional/unpacking.py @@ -0,0 +1,11 @@ +""" Code for checking the display of the module +for unbalanced-tuple-unpacking and unpacking-non-sequence +""" + +def unpack(): + """ Return something""" + return (1, 2, 3) + +def nonseq(): + """ Return non sequence """ + return 1 diff --git a/pymode/libs/pylint/test/functional/unpacking_generalizations.py b/pymode/libs/pylint/test/functional/unpacking_generalizations.py new file mode 100644 index 00000000..1c5fb16b --- /dev/null +++ b/pymode/libs/pylint/test/functional/unpacking_generalizations.py @@ -0,0 +1,29 @@ +"""Various tests for unpacking generalizations added in Python 3.5""" + +# pylint: disable=missing-docstring, invalid-name + +def func_variadic_args(*args): + return args + + +def func_variadic_positional_args(a, b, *args): + return a, b, args + +def func_positional_args(a, b, c, d): + return a, b, c, d + + +func_variadic_args(*(2, 3), *(3, 4), *(4, 5)) +func_variadic_args(1, 2, *(2, 3), 2, 3, *(4, 5)) +func_variadic_positional_args(1, 2, *(4, 5), *(5, 6)) +func_variadic_positional_args(*(2, 3), *(4, 5), *(5, 6)) +func_variadic_positional_args(*(2, 3)) +func_variadic_positional_args(*(2, 3, 4)) +func_variadic_positional_args(1, 2, 3, *(3, 4)) + +func_positional_args(*(2, 3, 4), *(2, 3)) # [too-many-function-args] +func_positional_args(*(1, 2), 3) # [no-value-for-parameter] +func_positional_args(1, *(2, ), 3, *(4, 5)) # [too-many-function-args] +func_positional_args(1, 2, c=24, d=32, **{'d': 32}) # [repeated-keyword] +# +1: [repeated-keyword,repeated-keyword] +func_positional_args(1, 2, c=24, **{'c': 34, 'd': 33}, **{'d': 24}) diff --git a/pymode/libs/pylint/test/functional/unpacking_generalizations.rc b/pymode/libs/pylint/test/functional/unpacking_generalizations.rc new file mode 100644 index 00000000..03004f2c --- /dev/null +++ b/pymode/libs/pylint/test/functional/unpacking_generalizations.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.5 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/unpacking_generalizations.txt b/pymode/libs/pylint/test/functional/unpacking_generalizations.txt new file mode 100644 index 00000000..d92fcc91 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unpacking_generalizations.txt @@ -0,0 +1,6 @@ +too-many-function-args:24::Too many positional arguments for function call +no-value-for-parameter:25::"No value for argument 'd' in function call" +too-many-function-args:26::Too many positional arguments for function call +repeated-keyword:27::Got multiple values for keyword argument 'd' in function call +repeated-keyword:29::Got multiple values for keyword argument 'c' in function call +repeated-keyword:29::Got multiple values for keyword argument 'd' in function call \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/unpacking_non_sequence.py b/pymode/libs/pylint/test/functional/unpacking_non_sequence.py new file mode 100644 index 00000000..c03b63dd --- /dev/null +++ b/pymode/libs/pylint/test/functional/unpacking_non_sequence.py @@ -0,0 +1,119 @@ +"""Check unpacking non-sequences in assignments. """ + +# pylint: disable=too-few-public-methods, invalid-name, attribute-defined-outside-init, unused-variable, no-absolute-import +# pylint: disable=using-constant-test, no-init +from os import rename as nonseq_func +from six import with_metaclass +from functional.unpacking import nonseq + +__revision__ = 0 + +# Working + +class Seq(object): + """ sequence """ + def __init__(self): + self.items = range(2) + + def __getitem__(self, item): + return self.items[item] + + def __len__(self): + return len(self.items) + +class Iter(object): + """ Iterator """ + def __iter__(self): + for number in range(2): + yield number + +def good_unpacking(): + """ returns should be unpackable """ + if True: + return [1, 2] + else: + return (3, 4) + +def good_unpacking2(): + """ returns should be unpackable """ + return good_unpacking() + +class MetaIter(type): + "metaclass that makes classes that use it iterables" + def __iter__(cls): + return iter((1, 2)) + +class IterClass(with_metaclass(MetaIter)): + "class that is iterable (and unpackable)" + +class AbstrClass(object): + "abstract class" + pair = None + + def setup_pair(self): + "abstract method" + raise NotImplementedError + + def __init__(self): + "error should not be emitted because setup_pair is abstract" + self.setup_pair() + x, y = self.pair + +a, b = [1, 2] +a, b = (1, 2) +a, b = set([1, 2]) +a, b = {1: 2, 2: 3} +a, b = "xy" +a, b = Seq() +a, b = Iter() +a, b = (number for number in range(2)) +a, b = good_unpacking() +a, b = good_unpacking2() +a, b = IterClass + +# Not working +class NonSeq(object): + """ does nothing """ + +a, b = NonSeq() # [unpacking-non-sequence] +a, b = ValueError # [unpacking-non-sequence] +a, b = None # [unpacking-non-sequence] +a, b = 1 # [unpacking-non-sequence] +a, b = nonseq # [unpacking-non-sequence] +a, b = nonseq() # [unpacking-non-sequence] +a, b = nonseq_func # [unpacking-non-sequence] + +class ClassUnpacking(object): + """ Check unpacking as instance attributes. """ + + def test(self): + """ test unpacking in instance attributes. """ + + self.a, self.b = 1, 2 + self.a, self.b = {1: 2, 2: 3} + self.a, self.b = "xy" + self.a, c = "xy" + c, self.a = good_unpacking() + self.a, self.b = Iter() + + self.a, self.b = NonSeq() # [unpacking-non-sequence] + self.a, self.b = ValueError # [unpacking-non-sequence] + self.a, c = nonseq_func # [unpacking-non-sequence] + +class TestBase(object): + 'base class with `test` method implementation' + @staticmethod + def test(data): + 'default implementation' + return data + +class Test(TestBase): + 'child class that overrides `test` method' + def __init__(self): + # no error should be emitted here as `test` is overriden in this class + (self.aaa, self.bbb, self.ccc) = self.test(None) + + @staticmethod + def test(data): + 'overridden implementation' + return (1, 2, 3) diff --git a/pymode/libs/pylint/test/functional/unpacking_non_sequence.txt b/pymode/libs/pylint/test/functional/unpacking_non_sequence.txt new file mode 100644 index 00000000..0a21fab4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unpacking_non_sequence.txt @@ -0,0 +1,10 @@ +unpacking-non-sequence:78::Attempting to unpack a non-sequence defined at line 75 +unpacking-non-sequence:79::Attempting to unpack a non-sequence +unpacking-non-sequence:80::Attempting to unpack a non-sequence None +unpacking-non-sequence:81::Attempting to unpack a non-sequence 1 +unpacking-non-sequence:82::Attempting to unpack a non-sequence defined at line 9 of functional.unpacking +unpacking-non-sequence:83::Attempting to unpack a non-sequence defined at line 11 of functional.unpacking +unpacking-non-sequence:84::Attempting to unpack a non-sequence +unpacking-non-sequence:99:ClassUnpacking.test:Attempting to unpack a non-sequence defined at line 75 +unpacking-non-sequence:100:ClassUnpacking.test:Attempting to unpack a non-sequence +unpacking-non-sequence:101:ClassUnpacking.test:Attempting to unpack a non-sequence diff --git a/pymode/libs/pylint/test/functional/unreachable.py b/pymode/libs/pylint/test/functional/unreachable.py new file mode 100644 index 00000000..b03dec88 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unreachable.py @@ -0,0 +1,22 @@ +# pylint: disable=missing-docstring + +from __future__ import print_function + +def func1(): + return 1 + print('unreachable') # [unreachable] + +def func2(): + while 1: + break + print('unreachable') # [unreachable] + +def func3(): + for i in (1, 2, 3): + print(i) + continue + print('unreachable') # [unreachable] + +def func4(): + raise Exception + return 1 / 0 # [unreachable] diff --git a/pymode/libs/pylint/test/functional/unreachable.txt b/pymode/libs/pylint/test/functional/unreachable.txt new file mode 100644 index 00000000..adf05264 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unreachable.txt @@ -0,0 +1,4 @@ +unreachable:7:func1:Unreachable code +unreachable:12:func2:Unreachable code +unreachable:18:func3:Unreachable code +unreachable:22:func4:Unreachable code \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/unrecognized_inline_option.py b/pymode/libs/pylint/test/functional/unrecognized_inline_option.py new file mode 100644 index 00000000..3163b1ea --- /dev/null +++ b/pymode/libs/pylint/test/functional/unrecognized_inline_option.py @@ -0,0 +1,3 @@ +# +1: [unrecognized-inline-option] +# pylint:bouboule=1 +"""Check unknown option""" diff --git a/pymode/libs/pylint/test/functional/unrecognized_inline_option.txt b/pymode/libs/pylint/test/functional/unrecognized_inline_option.txt new file mode 100644 index 00000000..922cc927 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unrecognized_inline_option.txt @@ -0,0 +1 @@ +unrecognized-inline-option:2::"Unrecognized file option 'bouboule'" diff --git a/pymode/libs/pylint/test/functional/unsubscriptable_value.py b/pymode/libs/pylint/test/functional/unsubscriptable_value.py new file mode 100644 index 00000000..4f1848a5 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unsubscriptable_value.py @@ -0,0 +1,115 @@ +""" +Checks that value used in a subscript supports subscription +(i.e. defines __getitem__ method). +""" +# pylint: disable=missing-docstring,pointless-statement,expression-not-assigned,wrong-import-position +# pylint: disable=too-few-public-methods,import-error,invalid-name,wrong-import-order +import six + +# primitives +numbers = [1, 2, 3] +numbers[0] +"123"[0] +u"123"[0] +b"123"[0] +bytearray(b"123")[0] +dict(a=1, b=2)['a'] +(1, 2, 3)[0] + +# list/dict comprehensions are fine +[x for x in range(10)][0] +{x: 10 - x for x in range(10)}[0] + + +# instances +class NonSubscriptable(object): + pass + +class Subscriptable(object): + def __getitem__(self, key): + return key + key + +NonSubscriptable()[0] # [unsubscriptable-object] +NonSubscriptable[0] # [unsubscriptable-object] +Subscriptable()[0] +Subscriptable[0] # [unsubscriptable-object] + +# generators are not subscriptable +def powers_of_two(): + k = 0 + while k < 10: + yield 2 ** k + k += 1 + +powers_of_two()[0] # [unsubscriptable-object] +powers_of_two[0] # [unsubscriptable-object] + + +# check that primitive non subscriptable types are catched +True[0] # [unsubscriptable-object] +None[0] # [unsubscriptable-object] +8.5[0] # [unsubscriptable-object] +10[0] # [unsubscriptable-object] + +# sets are not subscriptable +{x ** 2 for x in range(10)}[0] # [unsubscriptable-object] +set(numbers)[0] # [unsubscriptable-object] +frozenset(numbers)[0] # [unsubscriptable-object] + +# skip instances with unknown base classes +from some_missing_module import LibSubscriptable + +class MaybeSubscriptable(LibSubscriptable): + pass + +MaybeSubscriptable()[0] + +# subscriptable classes (through metaclasses) + +class MetaSubscriptable(type): + def __getitem__(cls, key): + return key + key + +class SubscriptableClass(six.with_metaclass(MetaSubscriptable, object)): + pass + +SubscriptableClass[0] +SubscriptableClass()[0] # [unsubscriptable-object] + +# functions are not subscriptable +def test(*args, **kwargs): + return args, kwargs + +test()[0] +test[0] # [unsubscriptable-object] + +# deque +from collections import deque +deq = deque(maxlen=10) +deq.append(42) +deq[0] + + +class AbstractClass(object): + + def __init__(self): + self.ala = {i for i in range(10)} + self.bala = [i for i in range(10)] + self.portocala = None + + def test_unsubscriptable(self): + self.bala[0] + self.portocala[0] + + +class ClassMixin(object): + + def __init__(self): + self.ala = {i for i in range(10)} + self.bala = [i for i in range(10)] + self.portocala = None + + def test_unsubscriptable(self): + self.bala[0] + self.portocala[0] + \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/unsubscriptable_value.txt b/pymode/libs/pylint/test/functional/unsubscriptable_value.txt new file mode 100644 index 00000000..64ea3c79 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unsubscriptable_value.txt @@ -0,0 +1,14 @@ +unsubscriptable-object:32::Value 'NonSubscriptable()' is unsubscriptable +unsubscriptable-object:33::Value 'NonSubscriptable' is unsubscriptable +unsubscriptable-object:35::Value 'Subscriptable' is unsubscriptable +unsubscriptable-object:44::Value 'powers_of_two()' is unsubscriptable +unsubscriptable-object:45::Value 'powers_of_two' is unsubscriptable +unsubscriptable-object:49::Value 'True' is unsubscriptable +unsubscriptable-object:50::Value 'None' is unsubscriptable +unsubscriptable-object:51::Value '8.5' is unsubscriptable +unsubscriptable-object:52::Value '10' is unsubscriptable +unsubscriptable-object:55::Value '{(x) ** (2) for x in range(10)}' is unsubscriptable +unsubscriptable-object:56::Value 'set(numbers)' is unsubscriptable +unsubscriptable-object:57::Value 'frozenset(numbers)' is unsubscriptable +unsubscriptable-object:77::Value 'SubscriptableClass()' is unsubscriptable +unsubscriptable-object:84::Value 'test' is unsubscriptable diff --git a/pymode/libs/pylint/test/functional/unsubscriptable_value_35.py b/pymode/libs/pylint/test/functional/unsubscriptable_value_35.py new file mode 100644 index 00000000..c92b826b --- /dev/null +++ b/pymode/libs/pylint/test/functional/unsubscriptable_value_35.py @@ -0,0 +1,5 @@ +# pylint: disable=missing-docstring, invalid-name + +import typing + +val = typing.Sequence[int] diff --git a/pymode/libs/pylint/test/functional/unsubscriptable_value_35.rc b/pymode/libs/pylint/test/functional/unsubscriptable_value_35.rc new file mode 100644 index 00000000..71de8b63 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unsubscriptable_value_35.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.5 diff --git a/pymode/libs/pylint/test/functional/unsupported_binary_operation.py b/pymode/libs/pylint/test/functional/unsupported_binary_operation.py new file mode 100644 index 00000000..2ecf0555 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unsupported_binary_operation.py @@ -0,0 +1,59 @@ +"""Test for unsupported-binary-operation.""" +# pylint: disable=missing-docstring,too-few-public-methods,pointless-statement +# pylint: disable=expression-not-assigned, invalid-name + + +import collections + + +1 + "a" # [unsupported-binary-operation] +1 - [] # [unsupported-binary-operation] +1 * {} # [unsupported-binary-operation] +1 / collections # [unsupported-binary-operation] +1 ** (lambda x: x) # [unsupported-binary-operation] +{} * {} # [unsupported-binary-operation] +{} - {} # [unsupported-binary-operation] +{} | {} # [unsupported-binary-operation] +{} >> {} # [unsupported-binary-operation] +[] + () # [unsupported-binary-operation] +() + [] # [unsupported-binary-operation] +[] * 2.0 # [unsupported-binary-operation] +() * 2.0 # [unsupported-binary-operation] +2.0 >> 2.0 # [unsupported-binary-operation] +class A(object): + pass +class B(object): + pass +A() + B() # [unsupported-binary-operation] +class A1(object): + def __add__(self, other): + return NotImplemented + +A1() + A1() # [unsupported-binary-operation] + +class A2(object): + def __add__(self, other): + return NotImplemented +class B2(object): + def __radd__(self, other): + return NotImplemented +A2() + B2() # [unsupported-binary-operation] + +class Parent(object): + pass +class Child(Parent): + def __add__(self, other): + return NotImplemented +Child() + Parent() # [unsupported-binary-operation] +class A3(object): + def __add__(self, other): + return NotImplemented +class B3(A3): + def __radd__(self, other): + return NotImplemented +A3() + B3() # [unsupported-binary-operation] +# Augmented +FFF = 1 +FFF += A() # [unsupported-binary-operation] +TTT = 1 +TTT += [] # [unsupported-binary-operation] diff --git a/pymode/libs/pylint/test/functional/unsupported_binary_operation.rc b/pymode/libs/pylint/test/functional/unsupported_binary_operation.rc new file mode 100644 index 00000000..401ccfe2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unsupported_binary_operation.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=4.0 diff --git a/pymode/libs/pylint/test/functional/unsupported_binary_operation.txt b/pymode/libs/pylint/test/functional/unsupported_binary_operation.txt new file mode 100644 index 00000000..d94365d0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unsupported_binary_operation.txt @@ -0,0 +1,21 @@ +unsupported-binary-operation:9::"unsupported operand type(s) for +: 'int' and 'str'" +unsupported-binary-operation:10::"unsupported operand type(s) for -: 'int' and 'list'" +unsupported-binary-operation:11::"unsupported operand type(s) for *: 'int' and 'dict'" +unsupported-binary-operation:12::"unsupported operand type(s) for /: 'int' and 'module'" +unsupported-binary-operation:13::"unsupported operand type(s) for **: 'int' and 'function'" +unsupported-binary-operation:14::"unsupported operand type(s) for *: 'dict' and 'dict'" +unsupported-binary-operation:15::"unsupported operand type(s) for -: 'dict' and 'dict'" +unsupported-binary-operation:16::"unsupported operand type(s) for |: 'dict' and 'dict'" +unsupported-binary-operation:17::"unsupported operand type(s) for >>: 'dict' and 'dict'" +unsupported-binary-operation:18::"unsupported operand type(s) for +: 'list' and 'tuple'" +unsupported-binary-operation:19::"unsupported operand type(s) for +: 'tuple' and 'list'" +unsupported-binary-operation:20::"unsupported operand type(s) for *: 'list' and 'float'" +unsupported-binary-operation:21::"unsupported operand type(s) for *: 'tuple' and 'float'" +unsupported-binary-operation:22::"unsupported operand type(s) for >>: 'float' and 'float'" +unsupported-binary-operation:27::"unsupported operand type(s) for +: 'A' and 'B'" +unsupported-binary-operation:32::"unsupported operand type(s) for +: 'A1' and 'A1'" +unsupported-binary-operation:40::"unsupported operand type(s) for +: 'A2' and 'B2'" +unsupported-binary-operation:47::"unsupported operand type(s) for +: 'Child' and 'Parent'" +unsupported-binary-operation:54::"unsupported operand type(s) for +: 'A3' and 'B3'" +unsupported-binary-operation:57::"unsupported operand type(s) for +=: 'int' and 'A'" +unsupported-binary-operation:59::"unsupported operand type(s) for +=: 'int' and 'list'" \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/unused_import.py b/pymode/libs/pylint/test/functional/unused_import.py new file mode 100644 index 00000000..d1a4c6b1 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unused_import.py @@ -0,0 +1,18 @@ +"""unused import""" +# pylint: disable=undefined-all-variable, import-error, no-absolute-import, too-few-public-methods, missing-docstring,wrong-import-position +import xml.etree # [unused-import] +import xml.sax # [unused-import] +import os.path as test # [unused-import] +from sys import argv as test2 # [unused-import] +from sys import flags # [unused-import] +# +1:[unused-import,unused-import] +from collections import deque, OrderedDict, Counter +DATA = Counter() + +from fake import SomeName, SomeOtherName # [unused-import] +class SomeClass(object): + SomeName = SomeName # https://bitbucket.org/logilab/pylint/issue/475 + SomeOtherName = 1 + SomeOtherName = SomeOtherName + +from never import __all__ diff --git a/pymode/libs/pylint/test/functional/unused_import.txt b/pymode/libs/pylint/test/functional/unused_import.txt new file mode 100644 index 00000000..9d6497f9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/unused_import.txt @@ -0,0 +1,8 @@ +unused-import:3::Unused import xml.etree +unused-import:4::Unused import xml.sax +unused-import:5::Unused os.path imported as test +unused-import:6::Unused argv imported from sys as test2 +unused-import:7::Unused flags imported from sys +unused-import:9::Unused OrderedDict imported from collections +unused-import:9::Unused deque imported from collections +unused-import:12::Unused SomeOtherName imported from fake diff --git a/pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.py b/pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.py new file mode 100644 index 00000000..ef6e1521 --- /dev/null +++ b/pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.py @@ -0,0 +1,47 @@ +"""Check for nonlocal and used-before-assignment""" +# pylint: disable=missing-docstring, unused-variable, no-init, too-few-public-methods + +__revision__ = 0 + +def test_ok(): + """ uses nonlocal """ + cnt = 1 + def wrap(): + nonlocal cnt + cnt = cnt + 1 + wrap() + +def test_fail(): + """ doesn't use nonlocal """ + cnt = 1 + def wrap(): + cnt = cnt + 1 # [used-before-assignment] + wrap() + +def test_fail2(): + """ use nonlocal, but for other variable """ + cnt = 1 + count = 1 + def wrap(): + nonlocal count + cnt = cnt + 1 # [used-before-assignment] + wrap() + +def test_fail3(arg: test_fail4): # [used-before-assignment] + """ Depends on `test_fail4`, in argument annotation. """ + return arg +# +1: [used-before-assignment, used-before-assignment] +def test_fail4(*args: test_fail5, **kwargs: undefined): + """ Depends on `test_fail5` and `undefined` in + variable and named arguments annotations. + """ + return args, kwargs + +def test_fail5()->undefined1: # [used-before-assignment] + """ Depends on `undefined1` in function return annotation. """ + +def undefined(): + """ no op """ + +def undefined1(): + """ no op """ diff --git a/pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.rc b/pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.rc new file mode 100644 index 00000000..c093be20 --- /dev/null +++ b/pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.txt b/pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.txt new file mode 100644 index 00000000..36e635e0 --- /dev/null +++ b/pymode/libs/pylint/test/functional/used_before_assignment_nonlocal.txt @@ -0,0 +1,6 @@ +used-before-assignment:18:test_fail.wrap:Using variable 'cnt' before assignment +used-before-assignment:27:test_fail2.wrap:Using variable 'cnt' before assignment +used-before-assignment:30:test_fail3:Using variable 'test_fail4' before assignment +used-before-assignment:34:test_fail4:Using variable 'test_fail5' before assignment +used-before-assignment:34:test_fail4:Using variable 'undefined' before assignment +used-before-assignment:40:test_fail5:Using variable 'undefined1' before assignment \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/useless_else_on_loop.py b/pymode/libs/pylint/test/functional/useless_else_on_loop.py new file mode 100644 index 00000000..2fcde8b3 --- /dev/null +++ b/pymode/libs/pylint/test/functional/useless_else_on_loop.py @@ -0,0 +1,55 @@ +"""Check for else branches on loops with break an return only.""" +from __future__ import print_function +__revision__ = 0 + +def test_return_for(): + """else + return is not accetable.""" + for i in range(10): + if i % 2: + return i + else: # [useless-else-on-loop] + print('math is broken') + +def test_return_while(): + """else + return is not accetable.""" + while True: + return 1 + else: # [useless-else-on-loop] + print('math is broken') + + +while True: + def short_fun(): + """A function with a loop.""" + for _ in range(10): + break +else: # [useless-else-on-loop] + print('or else!') + + +while True: + while False: + break +else: # [useless-else-on-loop] + print('or else!') + +for j in range(10): + pass +else: # [useless-else-on-loop] + print('fat chance') + for j in range(10): + break + +def test_return_for2(): + """no false positive for break in else + + https://bitbucket.org/logilab/pylint/issue/117/useless-else-on-loop-false-positives + """ + for i in range(10): + for i in range(i): + if i % 2: + break + else: + break + else: + print('great math') diff --git a/pymode/libs/pylint/test/functional/useless_else_on_loop.txt b/pymode/libs/pylint/test/functional/useless_else_on_loop.txt new file mode 100644 index 00000000..93309b6e --- /dev/null +++ b/pymode/libs/pylint/test/functional/useless_else_on_loop.txt @@ -0,0 +1,5 @@ +useless-else-on-loop:10:test_return_for:Else clause on loop without a break statement +useless-else-on-loop:17:test_return_while:Else clause on loop without a break statement +useless-else-on-loop:26::Else clause on loop without a break statement +useless-else-on-loop:33::Else clause on loop without a break statement +useless-else-on-loop:38::Else clause on loop without a break statement diff --git a/pymode/libs/pylint/test/functional/using_constant_test.py b/pymode/libs/pylint/test/functional/using_constant_test.py new file mode 100644 index 00000000..fcaae722 --- /dev/null +++ b/pymode/libs/pylint/test/functional/using_constant_test.py @@ -0,0 +1,140 @@ +"""Verify if constant tests are used inside if statements.""" +# pylint: disable=invalid-name, missing-docstring,too-few-public-methods +# pylint: disable=no-init,expression-not-assigned + + +import collections + + +def function(): + yield + + +class Class(object): + + def method(self): + pass + + +instance = Class() + +if collections: # [using-constant-test] + pass + +# GenExpr +if (node for node in range(10)): # [using-constant-test] + pass + +if lambda: None: # [using-constant-test] + pass + +if function: # [using-constant-test] + pass + +if Class: # [using-constant-test] + pass + +if 2: # [using-constant-test] + pass + +if True: # [using-constant-test] + pass + +if '': # [using-constant-test] + pass + +if b'': # [using-constant-test] + pass + +if 2.0: # [using-constant-test] + pass + +if {}: # [using-constant-test] + pass + +if {1, 2, 3}: # [using-constant-test] + pass + +if (1, 2, 3): # [using-constant-test] + pass + +if (): # [using-constant-test] + pass + +# Generator +generator = function() +if generator: # [using-constant-test] + pass + +if 1 if 2 else 3: # [using-constant-test] + pass + +def test_comprehensions(): + [data for data in range(100) if len] # [using-constant-test] + [data for data in range(100) if 1] # [using-constant-test] + (data for data in range(100) if len) # [using-constant-test] + (data for data in range(100) if 1) # [using-constant-test] + {data for data in range(100) if len} # [using-constant-test] + {data: 1 for data in range(100) if len} # [using-constant-test] + + + +# For these, we require to do inference, even though the result can be a +# constant value. For some of them, we could determine that the test +# is constant, such as 2 + 3, but the components of the BinOp +# can be anything else (2 + somefunccall). + +name = 42 +if name: + pass + +# UnboundMethod / Function +if Class.method: + pass + +# BoundMethod +if instance.method: + pass + +if 3 + 4: + pass + +if 3 and 4: + pass + +if not 3: + pass + +if instance.method(): + pass + +# pylint: disable=misplaced-comparison-constant +if 2 < 3: + pass + +if tuple((1, 2, 3)): + pass + +if dict(): + pass + +if tuple(): + pass + +if [1, 2, 3][:1]: + pass + +def test(*args): + if args: + return 42 + +def test_good_comprehension_checks(): + [data for data in range(100)] + [data for data in range(100) if data] + [data for data in range(100) if len(data)] + (data for data in range(100) if data) + (data for data in range(100) if len(data)) + {data for data in range(100) if data} + {data for data in range(100) if len(data)} + {data: 1 for data in range(100) if data} + {data: 1 for data in range(100)} diff --git a/pymode/libs/pylint/test/functional/using_constant_test.txt b/pymode/libs/pylint/test/functional/using_constant_test.txt new file mode 100644 index 00000000..b1c54847 --- /dev/null +++ b/pymode/libs/pylint/test/functional/using_constant_test.txt @@ -0,0 +1,22 @@ +using-constant-test:21::Using a conditional statement with a constant value +using-constant-test:25::Using a conditional statement with a constant value +using-constant-test:28::Using a conditional statement with a constant value +using-constant-test:31::Using a conditional statement with a constant value +using-constant-test:34::Using a conditional statement with a constant value +using-constant-test:37::Using a conditional statement with a constant value +using-constant-test:40::Using a conditional statement with a constant value +using-constant-test:43::Using a conditional statement with a constant value +using-constant-test:46::Using a conditional statement with a constant value +using-constant-test:49::Using a conditional statement with a constant value +using-constant-test:52::Using a conditional statement with a constant value +using-constant-test:55::Using a conditional statement with a constant value +using-constant-test:58::Using a conditional statement with a constant value +using-constant-test:61::Using a conditional statement with a constant value +using-constant-test:66::Using a conditional statement with a constant value +using-constant-test:69::Using a conditional statement with a constant value +using-constant-test:73:test_comprehensions:Using a conditional statement with a constant value +using-constant-test:74:test_comprehensions:Using a conditional statement with a constant value +using-constant-test:75:test_comprehensions:Using a conditional statement with a constant value +using-constant-test:76:test_comprehensions:Using a conditional statement with a constant value +using-constant-test:77:test_comprehensions:Using a conditional statement with a constant value +using-constant-test:78:test_comprehensions:Using a conditional statement with a constant value diff --git a/pymode/libs/pylint/test/functional/wildcard_import.py b/pymode/libs/pylint/test/functional/wildcard_import.py new file mode 100644 index 00000000..66ae63d9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/wildcard_import.py @@ -0,0 +1,5 @@ +# pylint: disable=no-absolute-import,missing-docstring,import-error,unused-wildcard-import +from indirect1 import * # [wildcard-import] +# This is an unresolved import which still generates the wildcard-import +# warning. +from unknown.package import * # [wildcard-import] diff --git a/pymode/libs/pylint/test/functional/wildcard_import.txt b/pymode/libs/pylint/test/functional/wildcard_import.txt new file mode 100644 index 00000000..643d818b --- /dev/null +++ b/pymode/libs/pylint/test/functional/wildcard_import.txt @@ -0,0 +1,2 @@ +wildcard-import:2::Wildcard import indirect1 +wildcard-import:5::Wildcard import unknown.package diff --git a/pymode/libs/pylint/test/functional/with_used_before_assign.py b/pymode/libs/pylint/test/functional/with_used_before_assign.py new file mode 100644 index 00000000..64a475af --- /dev/null +++ b/pymode/libs/pylint/test/functional/with_used_before_assign.py @@ -0,0 +1,11 @@ +''' +Regression test for +https://bitbucket.org/logilab/pylint/issue/128/attributeerror-when-parsing +''' +from __future__ import with_statement + +def do_nothing(): + """ empty """ + with open("") as ctx.obj: # [undefined-variable] + context.do() # [used-before-assignment] + context = None diff --git a/pymode/libs/pylint/test/functional/with_used_before_assign.txt b/pymode/libs/pylint/test/functional/with_used_before_assign.txt new file mode 100644 index 00000000..783ae732 --- /dev/null +++ b/pymode/libs/pylint/test/functional/with_used_before_assign.txt @@ -0,0 +1,2 @@ +undefined-variable:9:do_nothing:Undefined variable 'ctx' +used-before-assignment:10:do_nothing:Using variable 'context' before assignment diff --git a/pymode/libs/pylint/test/functional/with_using_generator.py b/pymode/libs/pylint/test/functional/with_using_generator.py new file mode 100644 index 00000000..25c6b377 --- /dev/null +++ b/pymode/libs/pylint/test/functional/with_using_generator.py @@ -0,0 +1,14 @@ +""" Testing with statements that use generators. This should not crash. """ + +class Base(object): + """ Base class. """ + val = 0 + + def gen(self): + """ A generator. """ + yield self.val + + def fun(self): + """ With statement using a generator. """ + with self.gen(): # [not-context-manager] + pass diff --git a/pymode/libs/pylint/test/functional/with_using_generator.txt b/pymode/libs/pylint/test/functional/with_using_generator.txt new file mode 100644 index 00000000..276b05c2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/with_using_generator.txt @@ -0,0 +1 @@ +not-context-manager:13:Base.fun:Context manager 'generator' doesn't implement __enter__ and __exit__. diff --git a/pymode/libs/pylint/test/functional/wrong_import_order.py b/pymode/libs/pylint/test/functional/wrong_import_order.py new file mode 100644 index 00000000..3086a926 --- /dev/null +++ b/pymode/libs/pylint/test/functional/wrong_import_order.py @@ -0,0 +1,15 @@ +"""Checks import order rule""" +# pylint: disable=unused-import,relative-import,ungrouped-imports,import-error +try: + from six.moves import configparser +except ImportError: + import configparser + +import six +import os.path # [wrong-import-order] +from astroid import are_exclusive +import sys # [wrong-import-order] +import datetime # [wrong-import-order] +import unused_import +import totally_missing +import astroid diff --git a/pymode/libs/pylint/test/functional/wrong_import_order.txt b/pymode/libs/pylint/test/functional/wrong_import_order.txt new file mode 100644 index 00000000..01a16a18 --- /dev/null +++ b/pymode/libs/pylint/test/functional/wrong_import_order.txt @@ -0,0 +1,3 @@ +wrong-import-order:9::standard import "import os.path" comes before "from six.moves import configparser" +wrong-import-order:11::standard import "import sys" comes before "from six.moves import configparser" +wrong-import-order:12::standard import "import datetime" comes before "from six.moves import configparser" diff --git a/pymode/libs/pylint/test/functional/wrong_import_position.py b/pymode/libs/pylint/test/functional/wrong_import_position.py new file mode 100644 index 00000000..269d7573 --- /dev/null +++ b/pymode/libs/pylint/test/functional/wrong_import_position.py @@ -0,0 +1,33 @@ +"""Checks import order rule""" +# pylint: disable=unused-import,relative-import,ungrouped-imports,wrong-import-order,using-constant-test +# pylint: disable=import-error, too-few-public-methods, missing-docstring +import os.path + +if True: + from astroid import are_exclusive +try: + import sys +except ImportError: + class Myclass(object): + """docstring""" + +if sys.version_info[0] == 3: + from collections import OrderedDict +else: + class OrderedDict(object): + """Nothing to see here.""" + def some_func(self): + pass + +import six + +CONSTANT = True + +import datetime # [wrong-import-position] + +VAR = 0 +for i in range(10): + VAR += i + +import scipy # [wrong-import-position] +import astroid # [wrong-import-position] diff --git a/pymode/libs/pylint/test/functional/wrong_import_position.txt b/pymode/libs/pylint/test/functional/wrong_import_position.txt new file mode 100644 index 00000000..5cde17db --- /dev/null +++ b/pymode/libs/pylint/test/functional/wrong_import_position.txt @@ -0,0 +1,3 @@ +wrong-import-position:26::Import "import datetime" should be placed at the top of the module +wrong-import-position:32::Import "import scipy" should be placed at the top of the module +wrong-import-position:33::Import "import astroid" should be placed at the top of the module diff --git a/pymode/libs/pylint/test/functional/yield_from_iterable_py33.py b/pymode/libs/pylint/test/functional/yield_from_iterable_py33.py new file mode 100644 index 00000000..7803936d --- /dev/null +++ b/pymode/libs/pylint/test/functional/yield_from_iterable_py33.py @@ -0,0 +1,7 @@ +""" +Check that `yield from`-statement takes an iterable. +""" +# pylint: disable=missing-docstring + +def to_ten(): + yield from 10 # [not-an-iterable] diff --git a/pymode/libs/pylint/test/functional/yield_from_iterable_py33.rc b/pymode/libs/pylint/test/functional/yield_from_iterable_py33.rc new file mode 100644 index 00000000..3330edda --- /dev/null +++ b/pymode/libs/pylint/test/functional/yield_from_iterable_py33.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.3 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/yield_from_iterable_py33.txt b/pymode/libs/pylint/test/functional/yield_from_iterable_py33.txt new file mode 100644 index 00000000..906ee93a --- /dev/null +++ b/pymode/libs/pylint/test/functional/yield_from_iterable_py33.txt @@ -0,0 +1 @@ +not-an-iterable:7:to_ten:Non-iterable value 10 is used in an iterating context diff --git a/pymode/libs/pylint/test/functional/yield_from_outside_func.py b/pymode/libs/pylint/test/functional/yield_from_outside_func.py new file mode 100644 index 00000000..e2db90c9 --- /dev/null +++ b/pymode/libs/pylint/test/functional/yield_from_outside_func.py @@ -0,0 +1,4 @@ +"""This is gramatically correct, but it's still a SyntaxError""" +yield from [1, 2] # [yield-outside-function] + +LAMBDA_WITH_YIELD = lambda: (yield from [1, 2]) diff --git a/pymode/libs/pylint/test/functional/yield_from_outside_func.rc b/pymode/libs/pylint/test/functional/yield_from_outside_func.rc new file mode 100644 index 00000000..28c99bc2 --- /dev/null +++ b/pymode/libs/pylint/test/functional/yield_from_outside_func.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.3 diff --git a/pymode/libs/pylint/test/functional/yield_from_outside_func.txt b/pymode/libs/pylint/test/functional/yield_from_outside_func.txt new file mode 100644 index 00000000..fa3a7fc4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/yield_from_outside_func.txt @@ -0,0 +1 @@ +yield-outside-function:2::Yield outside function diff --git a/pymode/libs/pylint/test/functional/yield_inside_async_function.py b/pymode/libs/pylint/test/functional/yield_inside_async_function.py new file mode 100644 index 00000000..33585bf7 --- /dev/null +++ b/pymode/libs/pylint/test/functional/yield_inside_async_function.py @@ -0,0 +1,12 @@ +"""Test that `yield` or `yield from` can't be used inside an async function.""" +# pylint: disable=missing-docstring, unused-variable + +async def good_coro(): + def _inner(): + yield 42 + yield from [1, 2, 3] + + +async def bad_coro(): + yield 42 # [yield-inside-async-function] + yield from [1, 2, 3] # [yield-inside-async-function] diff --git a/pymode/libs/pylint/test/functional/yield_inside_async_function.rc b/pymode/libs/pylint/test/functional/yield_inside_async_function.rc new file mode 100644 index 00000000..03004f2c --- /dev/null +++ b/pymode/libs/pylint/test/functional/yield_inside_async_function.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.5 \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/yield_inside_async_function.txt b/pymode/libs/pylint/test/functional/yield_inside_async_function.txt new file mode 100644 index 00000000..ae97cae4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/yield_inside_async_function.txt @@ -0,0 +1,2 @@ +yield-inside-async-function:11:bad_coro:Yield inside async function +yield-inside-async-function:12:bad_coro:Yield inside async function \ No newline at end of file diff --git a/pymode/libs/pylint/test/functional/yield_outside_func.py b/pymode/libs/pylint/test/functional/yield_outside_func.py new file mode 100644 index 00000000..5824bc0c --- /dev/null +++ b/pymode/libs/pylint/test/functional/yield_outside_func.py @@ -0,0 +1,4 @@ +"""This is gramatically correct, but it's still a SyntaxError""" +yield 1 # [yield-outside-function] + +LAMBDA_WITH_YIELD = lambda: (yield) diff --git a/pymode/libs/pylint/test/functional/yield_outside_func.txt b/pymode/libs/pylint/test/functional/yield_outside_func.txt new file mode 100644 index 00000000..fa3a7fc4 --- /dev/null +++ b/pymode/libs/pylint/test/functional/yield_outside_func.txt @@ -0,0 +1 @@ +yield-outside-function:2::Yield outside function diff --git a/pymode/libs/pylint/test/input/__init__.py b/pymode/libs/pylint/test/input/__init__.py new file mode 100644 index 00000000..60e92b76 --- /dev/null +++ b/pymode/libs/pylint/test/input/__init__.py @@ -0,0 +1 @@ +"""test""" diff --git a/pymode/libs/pylint/test/input/func_3k_removed_stuff_py_30.py b/pymode/libs/pylint/test/input/func_3k_removed_stuff_py_30.py new file mode 100644 index 00000000..54ad935b --- /dev/null +++ b/pymode/libs/pylint/test/input/func_3k_removed_stuff_py_30.py @@ -0,0 +1,13 @@ +"""test relative import""" +# pylint: disable=no-absolute-import +from __future__ import print_function +import func_w0401 +__revision__ = filter(None, map(str, (1, 2, 3))) + + +def function(): + """something""" + print(func_w0401) + unic = u"unicode" + low = unic.looower + return low diff --git a/pymode/libs/pylint/test/input/func_bad_assigment_to_exception_var.py b/pymode/libs/pylint/test/input/func_bad_assigment_to_exception_var.py new file mode 100644 index 00000000..93d06445 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_bad_assigment_to_exception_var.py @@ -0,0 +1,30 @@ +# pylint:disable=C0103, print-statement, no-absolute-import +"""ho ho ho""" +from __future__ import print_function +import sys +__revision__ = 'toto' + +e = 1 +e2 = 'yo' +e3 = None +try: + raise e +except Exception as ex: + print(ex) + _, _, tb = sys.exc_info() + + + +def func(): + """bla bla bla""" + raise e3 + +def reraise(): + """reraise a catched exception instance""" + try: + raise Exception() + except Exception as exc: + print(exc) + raise exc + +raise e3 diff --git a/pymode/libs/pylint/test/input/func_bad_cont_dictcomp_py27.py b/pymode/libs/pylint/test/input/func_bad_cont_dictcomp_py27.py new file mode 100644 index 00000000..a5527107 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_bad_cont_dictcomp_py27.py @@ -0,0 +1,38 @@ +"""Bad continuations in dictionary comprehensions.""" + +__revision__ = 0 + +# Dictionary comprehensions should not require extra indentation when breaking +# before the 'for', which is not part of the value +C1 = {'key{}'.format(x): 'value{}'.format(x) + for x in range(3)} + +C2 = {'key{}'.format(x): 'value{}'.format(x) for x in + range(3)} + +# Dictionary comprehensions with multiple loops broken in different places +C3 = {x*y: (x, y) for x in range(3) for y in range(3)} + +C4 = {x*y: (x, y) + for x in range(3) for y in range(3)} + +C5 = {x*y: (x, y) for x + in range(3) for y in range(3)} + +C6 = {x*y: (x, y) for x in range(3) + for y in range(3)} + +C7 = {key: + key ** 2 + for key in range(10)} + +C8 = { + key: key ** 2 + for key in range(10)} + +# Misaligned cases for dict comprehensions +C9 = {'key{}'.format(x): 'value{}'.format(x) + for x in range(3)} # [bad-continuation] + +C9 = {'key{}'.format(x): 'value{}'.format(x) + for x in range(3)} # [bad-continuation] diff --git a/pymode/libs/pylint/test/input/func_block_disable_msg.py b/pymode/libs/pylint/test/input/func_block_disable_msg.py new file mode 100644 index 00000000..5ed690eb --- /dev/null +++ b/pymode/libs/pylint/test/input/func_block_disable_msg.py @@ -0,0 +1,1026 @@ +# pylint: disable=C0302,bare-except,print-statement +"""pylint option block-disable""" +from __future__ import print_function + +class Foo(object): + """block-disable test""" + + def __init__(self): + self._test = "42" + + def meth1(self, arg): + """this issues a message""" + print(self) + + def meth2(self, arg): + """and this one not""" + # pylint: disable=W0613 + print(self._test\ + + "foo") + + def meth3(self): + """test one line disabling""" + # no error + print(self.bla) # pylint: disable=E1101 + # error + print(self.blop) + + def meth4(self): + """test re-enabling""" + # pylint: disable=E1101 + # no error + print(self.bla) + print(self.blop) + # pylint: enable=E1101 + # error + print(self.blip) + + def meth5(self): + """test IF sub-block re-enabling""" + # pylint: disable=E1101 + # no error + print(self.bla) + if self.blop: + # pylint: enable=E1101 + # error + print(self.blip) + else: + # no error + print(self.blip) + # no error + print(self.blip) + + def meth6(self): + """test TRY/EXCEPT sub-block re-enabling""" + # pylint: disable=E1101 + # no error + print(self.bla) + try: + # pylint: enable=E1101 + # error + print(self.blip) + except UndefinedName: # pylint: disable=E0602 + # no error + print(self.blip) + # no error + print(self.blip) + + def meth7(self): + """test one line block opening disabling""" + if self.blop: # pylint: disable=E1101 + # error + print(self.blip) + else: + # error + print(self.blip) + # error + print(self.blip) + + + def meth8(self): + """test late disabling""" + # error + print(self.blip) + # pylint: disable=E1101 + # no error + print(self.bla) + print(self.blop) + + def meth9(self): + """test re-enabling right after a block with whitespace""" + eris = 5 + + if eris: # pylint: disable=using-constant-test + print("In block") + + # pylint: disable=E1101 + # no error + print(self.bla) + print(self.blu) + # pylint: enable=E1101 + # error + print(self.blip) + + def meth10(self): + """Test double disable""" + # pylint: disable=E1101 + # no error + print(self.bla) + # pylint: disable=E1101 + print(self.blu) + + +class ClassLevelMessage(object): + """shouldn't display to much attributes/not enough methods messages + """ + # pylint: disable=R0902,R0903 + + def __init__(self): + self.attr1 = 1 + self.attr2 = 1 + self.attr3 = 1 + self.attr4 = 1 + self.attr5 = 1 + self.attr6 = 1 + self.attr7 = 1 + self.attr8 = 1 + self.attr9 = 1 + self.attr0 = 1 + + def too_complex_but_thats_ok(self, attr1, attr2): + """THIS Method has too much branches and returns but i don't care + """ + # pylint: disable=R0912,R0911 + try: + attr3 = attr1+attr2 + except ValueError: + attr3 = None + except: + return 'duh', self + if attr1: + for i in attr1: + if attr2: + return i + else: + return 'duh' + elif attr2: + for i in attr2: + if attr2: + return i + else: + return 'duh' + else: + for i in range(15): + if attr3: + return i + else: + return 'doh' + return None + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +print('hop, too many lines but i don\'t care') diff --git a/pymode/libs/pylint/test/input/func_break_or_return_in_try_finally.py b/pymode/libs/pylint/test/input/func_break_or_return_in_try_finally.py new file mode 100644 index 00000000..f880926b --- /dev/null +++ b/pymode/libs/pylint/test/input/func_break_or_return_in_try_finally.py @@ -0,0 +1,44 @@ +'Exeptions may be silently swallowed' +from __future__ import print_function +__revision__ = None +# pylint: disable=using-constant-test +def insidious_break_and_return(): + """I found you !""" + for i in range(0, -5, -1): + my_var = 0 + print(i) + try: + my_var += 1.0/i + if i < -3: + break # :D + else: + return my_var # :D + finally: + if i > -2: + break # :( + else: + return my_var # :( + return None + +def break_and_return(): + """I found you !""" + for i in range(0, -5, -1): + my_var = 0 + if i: + break # :D + try: + my_var += 1.0/i + finally: + for i in range(2): + if True: + break # :D + else: + def strange(): + """why not ?""" + if True: + return my_var # :D + strange() + if i: + break # :D + else: + return # :D diff --git a/pymode/libs/pylint/test/input/func_bug113231.py b/pymode/libs/pylint/test/input/func_bug113231.py new file mode 100644 index 00000000..70602e21 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_bug113231.py @@ -0,0 +1,24 @@ +# pylint: disable=E1101 +# pylint: disable=C0103 +# pylint: disable=R0903 +"""test bugfix for #113231 in logging checker +""" +from __future__ import absolute_import +# Muck up the names in an effort to confuse... +import logging as renamed_logging + +__revision__ = '' + +class Logger(object): + """Fake logger""" + pass + +logger = renamed_logging.getLogger(__name__) +fake_logger = Logger() + +# Statements that should be flagged: +renamed_logging.warning('%s, %s' % (4, 5)) +logger.warning('%s' % 5) + +# Statements that should not be flagged: +fake_logger.warn('%s' % 5) diff --git a/pymode/libs/pylint/test/input/func_disable_linebased.py b/pymode/libs/pylint/test/input/func_disable_linebased.py new file mode 100644 index 00000000..b94c2e31 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_disable_linebased.py @@ -0,0 +1,14 @@ +# This is a very very very very very very very very very very very very very very very very very very very very very long line. +# pylint: disable=line-too-long +"""Make sure enable/disable pragmas work for messages that are applied to lines and not syntax nodes. + +A disable pragma for a message that applies to nodes is applied to the whole +block if it comes before the first statement (excluding the docstring). For +line-based messages, this behavior needs to be altered to really only apply to +the enclosed lines. +""" +# pylint: enable=line-too-long + +from __future__ import print_function + +print('This is a very long line which the linter will warn about, now that line-too-long has been enabled again.') diff --git a/pymode/libs/pylint/test/input/func_dotted_ancestor.py b/pymode/libs/pylint/test/input/func_dotted_ancestor.py new file mode 100644 index 00000000..ff328580 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_dotted_ancestor.py @@ -0,0 +1,11 @@ +"""bla""" +# pylint: disable=no-absolute-import + +from input import func_w0233 + +__revision__ = 'yo' + +class Aaaa(func_w0233.AAAA): + """test dotted name in ancestors""" + def __init__(self): + func_w0233.AAAA.__init__(self) diff --git a/pymode/libs/pylint/test/input/func_e0012.py b/pymode/libs/pylint/test/input/func_e0012.py new file mode 100644 index 00000000..b50c9e5a --- /dev/null +++ b/pymode/libs/pylint/test/input/func_e0012.py @@ -0,0 +1,5 @@ +# pylint:enable=W04044 +"""check unknown option +""" +__revision__ = 1 + diff --git a/pymode/libs/pylint/test/input/func_e0203.py b/pymode/libs/pylint/test/input/func_e0203.py new file mode 100644 index 00000000..d86f4798 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_e0203.py @@ -0,0 +1,19 @@ +"""check for method without self as first argument +""" +from __future__ import print_function +__revision__ = 0 + + +class Abcd(object): + """dummy class""" + def __init__(self): + pass + + def abcd(yoo): + """another test""" + + abcd = classmethod(abcd) + + def edf(self): + """justo ne more method""" + print('yapudju in', self) diff --git a/pymode/libs/pylint/test/input/func_e0204.py b/pymode/libs/pylint/test/input/func_e0204.py new file mode 100644 index 00000000..7ab9cff2 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_e0204.py @@ -0,0 +1,19 @@ +"""check for method without self as first argument +""" +from __future__ import print_function +__revision__ = 0 + + +class Abcd(object): + """dummy class""" + + def __init__(truc): + """method without self""" + print(1) + + def abdc(yoo): + """another test""" + print(yoo) + def edf(self): + """just another method""" + print('yapudju in', self) diff --git a/pymode/libs/pylint/test/input/func_e0601.py b/pymode/libs/pylint/test/input/func_e0601.py new file mode 100644 index 00000000..122ba019 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_e0601.py @@ -0,0 +1,9 @@ +"""test local variable used before assignment +""" +from __future__ import print_function +__revision__ = 0 + +def function(): + """dummy""" + print(aaaa) + aaaa = 1 diff --git a/pymode/libs/pylint/test/input/func_e0604.py b/pymode/libs/pylint/test/input/func_e0604.py new file mode 100644 index 00000000..d077f31a --- /dev/null +++ b/pymode/libs/pylint/test/input/func_e0604.py @@ -0,0 +1,13 @@ +"""Test for invalid objects in a module's __all__ variable. + +""" +# pylint: disable=R0903,R0201,W0612 + +__revision__ = 0 + +def some_function(): + """Just a function.""" + pass + + +__all__ = [some_function] diff --git a/pymode/libs/pylint/test/input/func_e12xx.py b/pymode/libs/pylint/test/input/func_e12xx.py new file mode 100644 index 00000000..74984d99 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_e12xx.py @@ -0,0 +1,28 @@ +# pylint: disable=E1101, no-absolute-import +"""Test checking of log format strings +""" + +import logging + +__revision__ = '' + + +def pprint(): + """Test string format in logging statements. + """ + # These should all emit lint errors: + logging.info(0, '') # 1205 + logging.info('', '') # 1205 + logging.info('%s%', '') # 1201 + logging.info('%s%s', '') # 1206 + logging.info('%s%y', '', '') # 1200 + logging.info('%s%s', '', '', '') # 1205 + + # These should be okay: + logging.info(1) + logging.info(True) + logging.info('') + logging.info('%s%') + logging.info('%s', '') + logging.info('%s%%', '') + logging.info('%s%s', '', '') diff --git a/pymode/libs/pylint/test/input/func_e13xx.py b/pymode/libs/pylint/test/input/func_e13xx.py new file mode 100644 index 00000000..227311cb --- /dev/null +++ b/pymode/libs/pylint/test/input/func_e13xx.py @@ -0,0 +1,21 @@ +"""test string format error +""" +# pylint: disable=print-statement,unsupported-binary-operation +from __future__ import print_function + +PARG_1 = PARG_2 = PARG_3 = 1 + +def pprint(): + """Test string format + """ + print("%s %s" % {'PARG_1': 1, 'PARG_2': 2}) # E1306 + print("%s" % (PARG_1, PARG_2)) # E1305 + print("%(PARG_1)d %d" % {'PARG_1': 1, 'PARG_2': 2}) # E1302 + print("%(PARG_1)d %(PARG_2)d" % {'PARG_1': 1}) # E1304 + print("%(PARG_1)d %(PARG_2)d" % {'PARG_1': 1, 'PARG_2':2, 'PARG_3':3}) #W1301 + print("%(PARG_1)d %(PARG_2)d" % {'PARG_1': 1, 2:3}) # W1300 E1304 + print("%(PARG_1)d %(PARG_2)d" % (2, 3)) # 1303 + print("%(PARG_1)d %(PARG_2)d" % [2, 3]) # 1303 + print("%2z" % PARG_1) + print("strange format %2" % PARG_2) + print("works in 3 %a" % 1) diff --git a/pymode/libs/pylint/test/input/func_excess_escapes.py b/pymode/libs/pylint/test/input/func_excess_escapes.py new file mode 100644 index 00000000..12ac8622 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_excess_escapes.py @@ -0,0 +1,30 @@ +# pylint:disable=W0105, W0511, misplaced-comparison-constant +"""Stray backslash escapes may be missing a raw-string prefix.""" + +__revision__ = '$Id$' + +# Bad escape sequences, which probably don't do what you expect. +A = "\[\]\\" +assert '\/' == '\\/' +ESCAPE_BACKSLASH = '\`' + +# Valid escape sequences. +NEWLINE = "\n" +OLD_ESCAPES = '\a\b\f\n\t\r\v' +HEX = '\xad\x0a\x0d' +FALSE_OCTAL = '\o123\o000' # Not octal in Python +OCTAL = '\123\000' +NOT_OCTAL = '\888\999' +NUL = '\0' +UNICODE = u'\u1234' +HIGH_UNICODE = u'\U0000abcd' +QUOTES = '\'\"' +LITERAL_NEWLINE = '\ +' +ESCAPE_UNICODE = "\\\\n" + +# Bad docstring +"""Even in a docstring + +You shouldn't have ambiguous text like: C:\Program Files\alpha +""" diff --git a/pymode/libs/pylint/test/input/func_first_arg.py b/pymode/libs/pylint/test/input/func_first_arg.py new file mode 100644 index 00000000..efff51da --- /dev/null +++ b/pymode/libs/pylint/test/input/func_first_arg.py @@ -0,0 +1,42 @@ +# pylint: disable=C0111, W0232 +"""check for methods first arguments +""" + +__revision__ = 0 + + +class Obj(object): + # C0202, classmethod + def __new__(something): + pass + + # C0202, classmethod + def class1(cls): + pass + class1 = classmethod(class1) + + def class2(other): + pass + class2 = classmethod(class2) + + +class Meta(type): + # C0204, metaclass __new__ + def __new__(other, name, bases, dct): + pass + + # C0203, metaclass method + def method1(cls): + pass + + def method2(other): + pass + + # C0205, metaclass classmethod + def class1(mcs): + pass + class1 = classmethod(class1) + + def class2(other): + pass + class2 = classmethod(class2) diff --git a/pymode/libs/pylint/test/input/func_i0011.py b/pymode/libs/pylint/test/input/func_i0011.py new file mode 100644 index 00000000..2b2f445c --- /dev/null +++ b/pymode/libs/pylint/test/input/func_i0011.py @@ -0,0 +1,5 @@ +# pylint:disable=W0404 +"""check warning on local disabling +""" +__revision__ = 1 + diff --git a/pymode/libs/pylint/test/input/func_i0012.py b/pymode/libs/pylint/test/input/func_i0012.py new file mode 100644 index 00000000..aa646c6a --- /dev/null +++ b/pymode/libs/pylint/test/input/func_i0012.py @@ -0,0 +1,5 @@ +# pylint:enable=W0404 +"""check warning on local enabling +""" +__revision__ = 1 + diff --git a/pymode/libs/pylint/test/input/func_i0013.py b/pymode/libs/pylint/test/input/func_i0013.py new file mode 100644 index 00000000..0a3f8336 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_i0013.py @@ -0,0 +1,8 @@ +# pylint: skip-file +"""disable-all is usable as an inline option""" + +# no warning should be issued +try: + import this +except: + pass diff --git a/pymode/libs/pylint/test/input/func_i0014.py b/pymode/libs/pylint/test/input/func_i0014.py new file mode 100644 index 00000000..63ff37c9 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_i0014.py @@ -0,0 +1,8 @@ +# pylint: disable-all +"""disable-all is usable as an inline option""" + +# no warning should be issued +try: + import this +except: + pass diff --git a/pymode/libs/pylint/test/input/func_i0020.py b/pymode/libs/pylint/test/input/func_i0020.py new file mode 100644 index 00000000..8c0fe9f5 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_i0020.py @@ -0,0 +1,8 @@ +"""Test for reporting of suppressed messages.""" + +__revision__ = 0 + +def suppressed(): + """A function with an unused variable.""" + # pylint: disable=W0612 + var = 0 diff --git a/pymode/libs/pylint/test/input/func_i0022.py b/pymode/libs/pylint/test/input/func_i0022.py new file mode 100644 index 00000000..f5b308f7 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_i0022.py @@ -0,0 +1,22 @@ +"""Deprecated suppression style.""" + +__revision__ = None + +a = 1 # pylint: disable=invalid-name +b = 1 # pylint: disable-msg=invalid-name + +# pylint: disable=invalid-name +c = 1 +# pylint: enable=invalid-name + +# pylint: disable-msg=invalid-name +d = 1 +# pylint: enable-msg=invalid-name + +# pylint: disable-msg=C0103 +e = 1 +# pylint: enable-msg=C0103 + +# pylint: disable=C0103 +f = 1 +# pylint: enable=C0103 diff --git a/pymode/libs/pylint/test/input/func_init_vars.py b/pymode/libs/pylint/test/input/func_init_vars.py new file mode 100644 index 00000000..473d1ccb --- /dev/null +++ b/pymode/libs/pylint/test/input/func_init_vars.py @@ -0,0 +1,45 @@ +"""Checks that class variables are seen as inherited ! +""" +# pylint: disable=too-few-public-methods +from __future__ import print_function + + +class MyClass(object): + """Inherits from nothing + """ + + def __init__(self): + self.var = {} + + def met(self): + """Checks that base_var is seen as defined outside '__init__' + """ + self.var[1] = 'one' + self.base_var = 'one' + print(self.base_var, self.var) + + def met2(self): + """dummy method""" + print(self) +class MySubClass(MyClass): + """Inherits from MyClass + """ + class_attr = 1 + + def __init__(self): + MyClass.__init__(self) + self.var2 = 2 + print(self.__doc__) + print(self.__dict__) + print(self.__class__) + + def met2(self): + """Checks that var is seen as defined outside '__init__' + """ + self.var[1] = 'one' + self.var2 += 1 + print(self.class_attr) + +if __name__ == '__main__': + OBJ = MyClass() + OBJ.met() diff --git a/pymode/libs/pylint/test/input/func_kwoa_py30.py b/pymode/libs/pylint/test/input/func_kwoa_py30.py new file mode 100644 index 00000000..a5fe07c3 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_kwoa_py30.py @@ -0,0 +1,12 @@ +# pylint: disable=C0102 +'''A little testscript for PEP 3102 and pylint''' +def function(*, foo): + '''A function for testing''' + print(foo) + +function(foo=1) + +foo = 1 +function(foo) + +function(1) diff --git a/pymode/libs/pylint/test/input/func_logging_not_lazy_with_logger.py b/pymode/libs/pylint/test/input/func_logging_not_lazy_with_logger.py new file mode 100644 index 00000000..973a5c79 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_logging_not_lazy_with_logger.py @@ -0,0 +1,13 @@ +"""Logging warnings using a logger class.""" +from __future__ import absolute_import +import logging + +__revision__ = '' + +LOG = logging.getLogger("domain") +LOG.debug("%s" % "junk") +LOG.log(logging.DEBUG, "%s" % "junk") +LOG2 = LOG.debug +LOG2("%s" % "junk") + +logging.getLogger("domain").debug("%s" % "junk") diff --git a/pymode/libs/pylint/test/input/func_loopvar_in_dict_comp_py27.py b/pymode/libs/pylint/test/input/func_loopvar_in_dict_comp_py27.py new file mode 100644 index 00000000..312eee72 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_loopvar_in_dict_comp_py27.py @@ -0,0 +1,8 @@ +"""Tests for loopvar-in-closure.""" + +__revision__ = 0 + + +def bad_case(): + """Loop variable from dict comprehension.""" + return {x: lambda: x for x in range(10)} diff --git a/pymode/libs/pylint/test/input/func_module___dict__.py b/pymode/libs/pylint/test/input/func_module___dict__.py new file mode 100644 index 00000000..54514224 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_module___dict__.py @@ -0,0 +1,9 @@ +"""http://www.logilab.org/ticket/6949.""" +from __future__ import print_function +__revision__ = None + +print(__dict__ is not None) + +__dict__ = {} + +print(__dict__ is not None) diff --git a/pymode/libs/pylint/test/input/func_nameerror_on_string_substitution.py b/pymode/libs/pylint/test/input/func_nameerror_on_string_substitution.py new file mode 100644 index 00000000..be7b5c82 --- /dev/null +++ b/pymode/libs/pylint/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/pymode/libs/pylint/test/input/func_no_dummy_redefined.py b/pymode/libs/pylint/test/input/func_no_dummy_redefined.py new file mode 100644 index 00000000..a5c0aea4 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_no_dummy_redefined.py @@ -0,0 +1,14 @@ +"""Make sure warnings about redefinitions do not trigger for dummy variables.""" +from __future__ import print_function + + +_, INTERESTING = 'a=b'.split('=') + +value = 10 + + +def clobbering(): + """Clobbers a dummy name from the outer scope.""" + value = 9 + for _ in range(7): + print(value) diff --git a/pymode/libs/pylint/test/input/func_noerror___init___return_from_inner_function.py b/pymode/libs/pylint/test/input/func_noerror___init___return_from_inner_function.py new file mode 100644 index 00000000..397b0fcc --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror___init___return_from_inner_function.py @@ -0,0 +1,13 @@ +# pylint: disable=R0903 +"""#10075""" + +__revision__ = 1 + +class Aaa(object): + """docstring""" + def __init__(self): + def inner_function(arg): + """inner docstring""" + return arg + 4 + self.func = inner_function + diff --git a/pymode/libs/pylint/test/input/func_noerror_access_attr_before_def_false_positive.py b/pymode/libs/pylint/test/input/func_noerror_access_attr_before_def_false_positive.py new file mode 100644 index 00000000..7bc554b8 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_access_attr_before_def_false_positive.py @@ -0,0 +1,98 @@ +#pylint: disable=C0103,R0904,R0903,W0201,old-style-class,no-absolute-import +""" +This module demonstrates a possible problem of pyLint with calling __init__ s +from inherited classes. +Initializations done there are not considered, which results in Error E0203 for +self.cookedq. +""" + +from __future__ import print_function + +import telnetlib + +class SeeTelnet(telnetlib.Telnet): + """ + Extension of telnetlib. + """ + + def __init__(self, host=None, port=0): + """ + Constructor. + When called without arguments, create an unconnected instance. + With a hostname argument, it connects the instance; a port + number is optional. + Parameter: + - host: IP address of the host + - port: Port number + """ + telnetlib.Telnet.__init__(self, host, port) + + def readUntilArray(self, matches, _=None): + """ + Read until a given string is encountered or until timeout. + ... + """ + self.process_rawq() + maxLength = 0 + for match in matches: + if len(match) > maxLength: + maxLength = len(match) + +class Base(object): + """bla bla""" + dougloup_papa = None + + def __init__(self): + self._var = False + +class Derived(Base): + """derived blabla""" + dougloup_moi = None + def Work(self): + """do something""" + # E0203 - Access to member '_var' before its definition + if self._var: + print("True") + else: + print("False") + self._var = True + + # E0203 - Access to member 'dougloup_papa' before its definition + if self.dougloup_papa: + print('dougloup !') + self.dougloup_papa = True + # E0203 - Access to member 'dougloup_moi' before its definition + if self.dougloup_moi: + print('dougloup !') + self.dougloup_moi = True + + +class QoSALConnection(object): + """blabla""" + + _the_instance = None + + def __new__(cls): + if cls._the_instance is None: + cls._the_instance = object.__new__(cls) + return cls._the_instance + + def __init__(self): + pass + +class DefinedOutsideInit(object): + """use_attr is seen as the method defining attr because its in + first position + """ + def __init__(self): + self.reset() + + def use_attr(self): + """use and set members""" + if self.attr: + print('hop') + self.attr = 10 + + def reset(self): + """reset members""" + self.attr = 4 diff --git a/pymode/libs/pylint/test/input/func_noerror_base_init_vars.py b/pymode/libs/pylint/test/input/func_noerror_base_init_vars.py new file mode 100644 index 00000000..3c25fc91 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_base_init_vars.py @@ -0,0 +1,35 @@ +# pylint:disable=R0201, print-statement, too-few-public-methods +"""Checks that class variables are seen as inherited ! +""" +__revision__ = '' + +class BaseClass(object): + """A simple base class + """ + + def __init__(self): + self.base_var = {} + + def met(self): + """yo""" + def meeting(self, with_): + """ye""" + return with_ +class MyClass(BaseClass): + """Inherits from BaseClass + """ + + def __init__(self): + BaseClass.__init__(self) + self.var = {} + + def met(self): + """Checks that base_var is not seen as defined outsite '__init__' + """ + self.var[1] = 'one' + self.base_var[1] = 'one' + return self.base_var, self.var + +if __name__ == '__main__': + OBJ = MyClass() + OBJ.met() diff --git a/pymode/libs/pylint/test/input/func_noerror_builtin_module_test.py b/pymode/libs/pylint/test/input/func_noerror_builtin_module_test.py new file mode 100644 index 00000000..9b1e7ce8 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_builtin_module_test.py @@ -0,0 +1,11 @@ +"""test import from a builtin module""" + +from __future__ import absolute_import +from math import log10 + +__revision__ = None + + +def log10_2(): + """bla bla bla""" + return log10(2) diff --git a/pymode/libs/pylint/test/input/func_noerror_class_attributes.py b/pymode/libs/pylint/test/input/func_noerror_class_attributes.py new file mode 100644 index 00000000..75fb435e --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_class_attributes.py @@ -0,0 +1,17 @@ +"""Test that valid class attribute doesn't trigger errors""" +__revision__ = 'sponge bob' + +class Clazz(object): + "dummy class" + + def __init__(self): + self.topic = 5 + self._data = 45 + + def change_type(self, new_class): + """Change type""" + self.__class__ = new_class + + def do_nothing(self): + "I do nothing useful" + return self.topic + 56 diff --git a/pymode/libs/pylint/test/input/func_noerror_classes_meth_could_be_a_function.py b/pymode/libs/pylint/test/input/func_noerror_classes_meth_could_be_a_function.py new file mode 100644 index 00000000..bdf0da43 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_classes_meth_could_be_a_function.py @@ -0,0 +1,34 @@ +# pylint: disable=C0111,R0903,W0232 +""" +#2479 + +R0201 (formely W0212), Method could be a function shouldn't be emitted in case +like factory method pattern +""" +__revision__ = 1 + +class XAsub(object): + pass +class XBsub(XAsub): + pass +class XCsub(XAsub): + pass + +class Aimpl(object): + # disable "method could be a function" on classes which are not overriding + # the factory method because in that case the usage of polymorphism is not + # detected + # pylint: disable=R0201 + def makex(self): + return XAsub() + +class Bimpl(Aimpl): + + def makex(self): + return XBsub() + +class Cimpl(Aimpl): + + def makex(self): + return XCsub() + diff --git a/pymode/libs/pylint/test/input/func_noerror_classes_protected_member_access.py b/pymode/libs/pylint/test/input/func_noerror_classes_protected_member_access.py new file mode 100644 index 00000000..2ffd9d11 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_classes_protected_member_access.py @@ -0,0 +1,26 @@ +""" +#3123: W0212 false positive on static method +""" +__revision__ = 1 + +# pylint: disable=no-classmethod-decorator, no-staticmethod-decorator +class A3123(object): + """oypuee""" + _protected = 1 + def __init__(self): + pass + + + def cmeth(cls, val): + """set protected member""" + cls._protected = +val + + cmeth = classmethod(cmeth) + + def smeth(val): + """set protected member""" + A3123._protected += val + + smeth = staticmethod(smeth) + + prop = property(lambda self: self._protected) diff --git a/pymode/libs/pylint/test/input/func_noerror_crash_127416.py b/pymode/libs/pylint/test/input/func_noerror_crash_127416.py new file mode 100644 index 00000000..6c30a792 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_crash_127416.py @@ -0,0 +1,20 @@ +# pylint: disable=C0111,R0201 +""" +FUNCTIONALITY +""" + +class Example(object): + """ + @summary: Demonstrates pylint error caused by method expecting tuple + but called method does not return tuple + """ + + def method_expects_tuple(self, obj): + meth, args = self.method_doesnot_return_tuple(obj) + result = meth(args) + return result + + def method_doesnot_return_tuple(self, obj): + # we want to lock what we have in the inventory, not what is to have + # in the future + return {'success': obj} diff --git a/pymode/libs/pylint/test/input/func_noerror_decorator_scope.py b/pymode/libs/pylint/test/input/func_noerror_decorator_scope.py new file mode 100644 index 00000000..81f929cd --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_decorator_scope.py @@ -0,0 +1,19 @@ +# -*- pylint: disable=W0232,R0903 +"""Test that decorators sees the class namespace - just like +function default values does but function body doesn't. + +https://www.logilab.net/elo/ticket/3711 - bug finding decorator arguments +https://www.logilab.net/elo/ticket/5626 - name resolution bug inside classes +""" + +from __future__ import print_function + +class Test(object): + """test class""" + ident = lambda x: x + + @ident(ident) + def method(self, val=ident(7), func=ident): + """hop""" + print(self) + return func(val) diff --git a/pymode/libs/pylint/test/input/func_noerror_e1101_9588_base_attr_aug_assign.py b/pymode/libs/pylint/test/input/func_noerror_e1101_9588_base_attr_aug_assign.py new file mode 100644 index 00000000..03106a57 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_e1101_9588_base_attr_aug_assign.py @@ -0,0 +1,39 @@ +# pylint: disable=R0903 +""" +False positive case of E1101: + +The error is triggered when the attribute set in the base class is +modified with augmented assignment in a derived class. + +http://www.logilab.org/ticket/9588 +""" +__revision__ = 0 + +class BaseClass(object): + "The base class" + def __init__(self): + "Set an attribute." + self.e1101 = 1 + +class FalsePositiveClass(BaseClass): + "The first derived class which triggers the false positive" + def __init__(self): + "Augmented assignment triggers E1101." + BaseClass.__init__(self) + self.e1101 += 1 + + def countup(self): + "Consequently this also triggers E1101." + self.e1101 += 1 + +class NegativeClass(BaseClass): + "The second derived class, which does not trigger the error E1101" + def __init__(self): + "Ordinary assignment is OK." + BaseClass.__init__(self) + self.e1101 = self.e1101 + 1 + + def countup(self): + "No problem." + self.e1101 += 1 + diff --git a/pymode/libs/pylint/test/input/func_noerror_encoding.py b/pymode/libs/pylint/test/input/func_noerror_encoding.py new file mode 100644 index 00000000..2e945a5e --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_encoding.py @@ -0,0 +1,6 @@ +# -*- coding: ISO-8859-1 -*- +""" check correct encoding declaration +""" + +__revision__ = 'éééé' + diff --git a/pymode/libs/pylint/test/input/func_noerror_except_pass.py b/pymode/libs/pylint/test/input/func_noerror_except_pass.py new file mode 100644 index 00000000..5bb8011b --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_except_pass.py @@ -0,0 +1,12 @@ +""" +#3205: W0704 (except doesn't do anything) false positive if some statements +follow a "pass" +""" +from __future__ import print_function +__revision__ = None + +try: + A = 2 +except ValueError: + pass # pylint: disable=W0107 + print(A) diff --git a/pymode/libs/pylint/test/input/func_noerror_exception.py b/pymode/libs/pylint/test/input/func_noerror_exception.py new file mode 100644 index 00000000..1c3d8b56 --- /dev/null +++ b/pymode/libs/pylint/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/pymode/libs/pylint/test/input/func_noerror_external_classmethod_crash.py b/pymode/libs/pylint/test/input/func_noerror_external_classmethod_crash.py new file mode 100644 index 00000000..318f01c3 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_external_classmethod_crash.py @@ -0,0 +1,21 @@ +# pylint: disable=W0232,R0903,W0613 +"""tagging a function as a class method cause a crash when checking for +signature overriding +""" + +def fetch_config(mainattr=None): + """return a class method""" + + def fetch_order(cls, attr, var): + """a class method""" + if attr == mainattr: + return var + return None + fetch_order = classmethod(fetch_order) + return fetch_order + +class Aaa(object): + """hop""" + fetch_order = fetch_config('A') + +__revision__ = None diff --git a/pymode/libs/pylint/test/input/func_noerror_function_as_method.py b/pymode/libs/pylint/test/input/func_noerror_function_as_method.py new file mode 100644 index 00000000..0fbb59cd --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_function_as_method.py @@ -0,0 +1,18 @@ +# pylint: disable=R0903 +'''Test that a function is considered a method when looked up through a class. +''' +from __future__ import print_function + +class Clazz(object): + 'test class' + + def __init__(self, value): + self.value = value + +def func(arg1, arg2): + 'function that will be used as a method' + return arg1.value + arg2 + +Clazz.method = func + +print(Clazz(1).method(2)) diff --git a/pymode/libs/pylint/test/input/func_noerror_genexp_in_class_scope.py b/pymode/libs/pylint/test/input/func_noerror_genexp_in_class_scope.py new file mode 100644 index 00000000..5631026a --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_genexp_in_class_scope.py @@ -0,0 +1,9 @@ +# pylint: disable=W0232,R0903 +"""class scope must be handled correctly in genexps""" + +__revision__ = '' + +class MyClass(object): + """ds""" + var1 = [] + var2 = list(value*2 for value in var1) diff --git a/pymode/libs/pylint/test/input/func_noerror_inner_classes.py b/pymode/libs/pylint/test/input/func_noerror_inner_classes.py new file mode 100644 index 00000000..84fb43d8 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_inner_classes.py @@ -0,0 +1,33 @@ +# pylint: disable=R0903 +"""Backend Base Classes for the schwelm user DB""" + +__revision__ = "alpha" + +class Aaa(object): + """docstring""" + def __init__(self): + self.__setattr__('a', 'b') + + + def one_public(self): + """docstring""" + pass + + def another_public(self): + """docstring""" + pass + +class Bbb(Aaa): + """docstring""" + pass + +class Ccc(Aaa): + """docstring""" + + class Ddd(Aaa): + """docstring""" + pass + + class Eee(Ddd): + """docstring""" + pass diff --git a/pymode/libs/pylint/test/input/func_noerror_lambda_use_before_assign.py b/pymode/libs/pylint/test/input/func_noerror_lambda_use_before_assign.py new file mode 100644 index 00000000..af2775ca --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_lambda_use_before_assign.py @@ -0,0 +1,8 @@ +"""https://www.logilab.net/elo/ticket/18862""" +from __future__ import print_function +__revision__ = 1 +def function(): + """hop""" + ggg = lambda: xxx + xxx = 1 + print(ggg()) diff --git a/pymode/libs/pylint/test/input/func_noerror_long_utf8_line.py b/pymode/libs/pylint/test/input/func_noerror_long_utf8_line.py new file mode 100644 index 00000000..6fba9491 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_long_utf8_line.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +"""this utf-8 doc string have some non ASCII caracters like 'é', or '¢»ß'""" +### check also comments with some more non ASCII caracters like 'é' or '¢»ß' + +__revision__ = 1100 + +ASCII = "----------------------------------------------------------------------" +UTF_8 = "--------------------------------------------------------------------éé" + diff --git a/pymode/libs/pylint/test/input/func_noerror_mcs_attr_access.py b/pymode/libs/pylint/test/input/func_noerror_mcs_attr_access.py new file mode 100644 index 00000000..7d9652ea --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_mcs_attr_access.py @@ -0,0 +1,20 @@ +# pylint: disable=R0903, metaclass-assignment +"""test attribute access on metaclass""" + + +from __future__ import print_function + +class Meta(type): + """the meta class""" + def __init__(cls, name, bases, dictionary): + super(Meta, cls).__init__(name, bases, dictionary) + print(cls, cls._meta_args) + delattr(cls, '_meta_args') + +class Test(object): + """metaclassed class""" + __metaclass__ = Meta + _meta_args = ('foo', 'bar') + + def __init__(self): + print('__init__', self) diff --git a/pymode/libs/pylint/test/input/func_noerror_new_style_class_py_30.py b/pymode/libs/pylint/test/input/func_noerror_new_style_class_py_30.py new file mode 100644 index 00000000..a33ae19f --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_new_style_class_py_30.py @@ -0,0 +1,45 @@ +"""check builtin data descriptors such as mode and name attributes +on a file are correctly handled + +bug notified by Pierre Rouleau on 2005-04-24 +""" +from __future__ import print_function +__revision__ = None + +class File(file): # pylint: disable=file-builtin + """ Testing new-style class inheritance from file""" + + # + def __init__(self, name, mode="r", buffering=-1, verbose=False): + """Constructor""" + + self.was_modified = False + self.verbose = verbose + super(File, self).__init__(name, mode, buffering) + if self.verbose: + print("File %s is opened. The mode is: %s" % (self.name, + self.mode)) + + # + def write(self, a_string): + """ Write a string to the file.""" + + super(File, self).write(a_string) + self.was_modified = True + + # + def writelines(self, sequence): + """ Write a sequence of strings to the file. """ + + super(File, self).writelines(sequence) + self.was_modified = True + + # + def close(self): + """Close the file.""" + + if self.verbose: + print("Closing file %s" % self.name) + + super(File, self).close() + self.was_modified = False diff --git a/pymode/libs/pylint/test/input/func_noerror_no_warning_docstring.py b/pymode/libs/pylint/test/input/func_noerror_no_warning_docstring.py new file mode 100644 index 00000000..bd30d7eb --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_no_warning_docstring.py @@ -0,0 +1,42 @@ +''' Test for inheritence ''' +from __future__ import print_function +__revision__ = 1 +# pylint: disable=too-few-public-methods, using-constant-test +class AAAA(object): + ''' class AAAA ''' + + def __init__(self): + pass + + def method1(self): + ''' method 1 ''' + print(self) + + def method2(self): + ''' method 2 ''' + print(self) + +class BBBB(AAAA): + ''' class BBBB ''' + + def __init__(self): + AAAA.__init__(self) + + # should ignore docstring calling from class AAAA + def method1(self): + AAAA.method1(self) + +class CCCC(BBBB): + ''' class CCCC ''' + + def __init__(self): + BBBB.__init__(self) + + # should ignore docstring since CCCC is inherited from BBBB which is + # inherited from AAAA containing method2 + if __revision__: + def method2(self): + AAAA.method2(self) + else: + def method2(self): + AAAA.method1(self) diff --git a/pymode/libs/pylint/test/input/func_noerror_nonregr.py b/pymode/libs/pylint/test/input/func_noerror_nonregr.py new file mode 100644 index 00000000..c4c8c382 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_nonregr.py @@ -0,0 +1,13 @@ +# pylint: disable=W0106 +"""snippets of codes which have at some point made pylint crash""" + +__revision__ = 1 + +def function1(cbarg=lambda: None): + """ + File "/usr/lib/python2.4/site-packages/logilab/astroid/scoped_nodes.py", line +391, in mularg_class # this method doesn't exist anymore + i = self.args.args.index(argname) +ValueError: list.index(x): x not in list + """ + cbarg().x diff --git a/pymode/libs/pylint/test/input/func_noerror_object_as_class_attribute.py b/pymode/libs/pylint/test/input/func_noerror_object_as_class_attribute.py new file mode 100644 index 00000000..c69b2b96 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_object_as_class_attribute.py @@ -0,0 +1,19 @@ +# pylint: disable=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/pymode/libs/pylint/test/input/func_noerror_overloaded_operator.py b/pymode/libs/pylint/test/input/func_noerror_overloaded_operator.py new file mode 100644 index 00000000..2e0e8a11 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_overloaded_operator.py @@ -0,0 +1,21 @@ +# pylint: disable=C0111,R0903 +"""#3291""" +from __future__ import print_function + +class Myarray(object): + def __init__(self, array): + self.array = array + + def __mul__(self, val): + return Myarray(val) + + def astype(self): + return "ASTYPE", self + +def randint(maximum): + if maximum is not None: + return Myarray([1, 2, 3]) * 2 + else: + return int(5) + +print(randint(1).astype()) # we don't wan't an error for astype access diff --git a/pymode/libs/pylint/test/input/func_noerror_overriden_method_varargs.py b/pymode/libs/pylint/test/input/func_noerror_overriden_method_varargs.py new file mode 100644 index 00000000..afe30863 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_overriden_method_varargs.py @@ -0,0 +1,19 @@ +# pylint: disable=R0201,R0903 +"""docstring""" + +__revision__ = 1 + +class SuperClass(object): + """docstring""" + def impl(self, arg1, arg2): + """docstring""" + return arg1 + arg2 + +class MyClass(SuperClass): + """docstring""" + def impl(self, *args, **kwargs): + """docstring""" + # ...do stuff here... + super(MyClass, self).impl(*args, **kwargs) + +# ...do stuff here... diff --git a/pymode/libs/pylint/test/input/func_noerror_property_affectation_py26.py b/pymode/libs/pylint/test/input/func_noerror_property_affectation_py26.py new file mode 100644 index 00000000..d91f455d --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_property_affectation_py26.py @@ -0,0 +1,24 @@ +# pylint: disable=R0903 +""" +Simple test case for an annoying behavior in pylint. +""" + +__revision__ = 'pouet' + +class Test(object): + """Smallest test case for reported issue.""" + + def __init__(self): + self._thing = None + + @property + def myattr(self): + """Getter for myattr""" + return self._thing + + @myattr.setter + def myattr(self, value): + """Setter for myattr.""" + self._thing = value + +Test().myattr = 'grou' diff --git a/pymode/libs/pylint/test/input/func_noerror_raise_return_self.py b/pymode/libs/pylint/test/input/func_noerror_raise_return_self.py new file mode 100644 index 00000000..36ce826d --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_raise_return_self.py @@ -0,0 +1,15 @@ +"""see ticket #5672""" +# pylint: disable=R0903,W0232,C0111,C0103,using-constant-test + +__revision__ = 0 + +class MultiException(Exception): + def __init__(self): + Exception.__init__(self) + def return_self(self): + return self + +# raise Exception +if 1: + raise MultiException().return_self() + diff --git a/pymode/libs/pylint/test/input/func_noerror_static_method.py b/pymode/libs/pylint/test/input/func_noerror_static_method.py new file mode 100644 index 00000000..7457f457 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_static_method.py @@ -0,0 +1,28 @@ +"""Checks if static / class methods works fine in Pylint +""" +from __future__ import print_function +__revision__ = '' + +#pylint: disable=no-classmethod-decorator, no-staticmethod-decorator +class MyClass(object): + """doc + """ + def __init__(self): + pass + + def static_met(var1, var2): + """This is a static method + """ + print(var1, var2) + + def class_met(cls, var1): + """This is a class method + """ + print(cls, var1) + + static_met = staticmethod(static_met) + class_met = classmethod(class_met) + +if __name__ == '__main__': + MyClass.static_met("var1", "var2") + MyClass.class_met("var1") diff --git a/pymode/libs/pylint/test/input/func_noerror_yield_assign_py25.py b/pymode/libs/pylint/test/input/func_noerror_yield_assign_py25.py new file mode 100644 index 00000000..f40d8d96 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_yield_assign_py25.py @@ -0,0 +1,21 @@ +"""http://www.logilab.org/ticket/8771""" + +from __future__ import print_function + +def generator(): + """yield as assignment""" + yield 45 + xxxx = yield 123 + print(xxxx) + +def generator_fp1(seq): + """W0631 false positive""" + for val in seq: + pass + for val in seq: + yield val + +def generator_fp2(): + """E0601 false positive""" + xxxx = 12 + yield xxxx diff --git a/pymode/libs/pylint/test/input/func_noerror_yield_return_mix.py b/pymode/libs/pylint/test/input/func_noerror_yield_return_mix.py new file mode 100644 index 00000000..bd786333 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_noerror_yield_return_mix.py @@ -0,0 +1,7 @@ +""" module doc """ +__revision__ = None + +def somegen(): + """this kind of mix is OK""" + yield 1 + return diff --git a/pymode/libs/pylint/test/input/func_nonregr___file___global.py b/pymode/libs/pylint/test/input/func_nonregr___file___global.py new file mode 100644 index 00000000..910033c4 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_nonregr___file___global.py @@ -0,0 +1,8 @@ +"""test no crash on __file__ global""" + +def func(): + """override __file__""" + global __file__ + __file__ = 'hop' + +__revision__ = 'pouet' diff --git a/pymode/libs/pylint/test/input/func_return_yield_mix_py_33.py b/pymode/libs/pylint/test/input/func_return_yield_mix_py_33.py new file mode 100644 index 00000000..4054ebb1 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_return_yield_mix_py_33.py @@ -0,0 +1,16 @@ +"""pylint should detect yield and return mix inside genrators""" +# pylint: disable=using-constant-test +def somegen(): + """this is a bad generator""" + if True: + return 1 + else: + yield 2 + +def moregen(): + """this is another bad generator""" + if True: + yield 1 + else: + return 2 + diff --git a/pymode/libs/pylint/test/input/func_too_many_returns_yields.py b/pymode/libs/pylint/test/input/func_too_many_returns_yields.py new file mode 100644 index 00000000..c61bd7d5 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_too_many_returns_yields.py @@ -0,0 +1,42 @@ +"""test""" + +__revision__ = None + +def too_many_returns(arg): + """is this real ?""" + 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 + +def many_yield(text): + """not a problem""" + if text: + yield " line 1: %s\n" % text + yield " line 2\n" + yield " line 3\n" + yield " line 4\n" + yield " line 5\n" + else: + yield " line 6\n" + yield " line 7\n" + yield " line 8\n" + yield " line 9\n" + yield " line 10\n" diff --git a/pymode/libs/pylint/test/input/func_typecheck_callfunc_assigment.py b/pymode/libs/pylint/test/input/func_typecheck_callfunc_assigment.py new file mode 100644 index 00000000..927dcb58 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_typecheck_callfunc_assigment.py @@ -0,0 +1,63 @@ + +"""check assignment to function call where the function doesn't return + + 'E1111': ('Assigning to function call which doesn\'t return', + 'Used when an assignment is done on a function call but the \ + infered function doesn\'t return anything.'), + 'W1111': ('Assigning to function call which only returns None', + 'Used when an assignment is done on a function call but the \ + infered function returns nothing but None.'), + +""" +from __future__ import generators, print_function + +#pylint: disable=redefined-variable-type + +def func_no_return(): + """function without return""" + print('dougloup') + +A = func_no_return() + + +def func_return_none(): + """function returning none""" + print('dougloup') + return None + +A = func_return_none() + + +def func_implicit_return_none(): + """Function returning None from bare return statement.""" + return + +A = func_implicit_return_none() + + +def func_return_none_and_smth(): + """function returning none and something else""" + print('dougloup') + if 2 or 3: + return None + return 3 + +A = func_return_none_and_smth() + +def generator(): + """no problemo""" + yield 2 + +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/pymode/libs/pylint/test/input/func_unused_import_py30.py b/pymode/libs/pylint/test/input/func_unused_import_py30.py new file mode 100644 index 00000000..0ea2c9b9 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_unused_import_py30.py @@ -0,0 +1,21 @@ +"""check unused import for metaclasses +""" +# pylint: disable=too-few-public-methods,wrong-import-position,ungrouped-imports +__revision__ = 1 +import abc +import sys +from abc import ABCMeta +from abc import ABCMeta as SomethingElse + +class Meta(metaclass=abc.ABCMeta): + """ Test """ + def __init__(self): + self.data = sys.executable + self.test = abc + +class Meta2(metaclass=ABCMeta): + """ Test """ + +class Meta3(metaclass=SomethingElse): + """ test """ + diff --git a/pymode/libs/pylint/test/input/func_unused_overridden_argument.py b/pymode/libs/pylint/test/input/func_unused_overridden_argument.py new file mode 100644 index 00000000..a0e61986 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_unused_overridden_argument.py @@ -0,0 +1,31 @@ +# pylint: disable=R0903, print-statement +"""for Sub.inherited, only the warning for "aay" is desired. +The warnings for "aab" and "aac" are most likely false positives though, +because there could be another subclass that overrides the same method and does +use the arguments (eg Sub2) +""" + +__revision__ = 'thx to Maarten ter Huurne' + +class Base(object): + "parent" + def inherited(self, aaa, aab, aac): + "abstract method" + raise NotImplementedError + +class Sub(Base): + "child 1" + def inherited(self, aaa, aab, aac): + "overridden method, though don't use every argument" + return aaa + + def newmethod(self, aax, aay): + "another method, warning for aay desired" + return self, aax + +class Sub2(Base): + "child 1" + + def inherited(self, aaa, aab, aac): + "overridden method, use every argument" + return aaa + aab + aac diff --git a/pymode/libs/pylint/test/input/func_use_for_or_listcomp_var.py b/pymode/libs/pylint/test/input/func_use_for_or_listcomp_var.py new file mode 100644 index 00000000..517d0a27 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_use_for_or_listcomp_var.py @@ -0,0 +1,27 @@ +"""test a warning is triggered when using for a lists comprehension variable""" +from __future__ import print_function +__revision__ = 'yo' + +TEST_LC = [C for C in __revision__ if C.isalpha()] +print(C) # WARN +C = 4 +print(C) # this one shouldn't trigger any warning + +B = [B for B in __revision__ if B.isalpha()] +print(B) # nor this one + +for var1, var2 in TEST_LC: + var1 = var2 + 4 +print(var1) # WARN + +for note in __revision__: + note.something() +for line in __revision__: + for note in line: + A = note.anotherthing() + + +for x in []: + pass +for x in range(3): + print((lambda: x)()) # OK diff --git a/pymode/libs/pylint/test/input/func_variables_unused_name_from_wilcard_import.py b/pymode/libs/pylint/test/input/func_variables_unused_name_from_wilcard_import.py new file mode 100644 index 00000000..bb92155c --- /dev/null +++ b/pymode/libs/pylint/test/input/func_variables_unused_name_from_wilcard_import.py @@ -0,0 +1,3 @@ +"""check unused import from a wildcard import""" +# pylint: disable=no-absolute-import +from input.func_w0611 import * diff --git a/pymode/libs/pylint/test/input/func_w0122_py_30.py b/pymode/libs/pylint/test/input/func_w0122_py_30.py new file mode 100644 index 00000000..178c2521 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0122_py_30.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/pymode/libs/pylint/test/input/func_w0205.py b/pymode/libs/pylint/test/input/func_w0205.py new file mode 100644 index 00000000..896be6c8 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0205.py @@ -0,0 +1,24 @@ +"""check different signatures""" +from __future__ import print_function +__revision__ = 0 +# pylint: disable=too-few-public-methods +class Abcd(object): + '''dummy''' + def __init__(self): + self.aarg = False + def abcd(self, aaa=1, bbbb=None): + """hehehe""" + print(self, aaa, bbbb) + def args(self): + """gaarrrggll""" + self.aarg = True + +class Cdef(Abcd): + """dummy""" + def __init__(self, aaa): + Abcd.__init__(self) + self.aaa = aaa + + def abcd(self, aaa, bbbb=None): + """hehehe""" + print(self, aaa, bbbb) diff --git a/pymode/libs/pylint/test/input/func_w0233.py b/pymode/libs/pylint/test/input/func_w0233.py new file mode 100644 index 00000000..f373d4fb --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0233.py @@ -0,0 +1,50 @@ +# pylint: disable=R0903,W0212,W0403,W0406,no-absolute-import +"""test for call to __init__ from a non ancestor class +""" +from __future__ import print_function +from . import func_w0233 +import nonexistant +__revision__ = '$Id: func_w0233.py,v 1.2 2004-09-29 08:35:13 syt Exp $' + +class AAAA(object): + """ancestor 1""" + + def __init__(self): + print('init', self) + BBBBMixin.__init__(self) + +class BBBBMixin(object): + """ancestor 2""" + + def __init__(self): + print('init', self) + +class CCC(BBBBMixin, func_w0233.AAAA, func_w0233.BBBB, nonexistant.AClass): + """mix different things, some inferable some not""" + def __init__(self): + BBBBMixin.__init__(self) + func_w0233.AAAA.__init__(self) + func_w0233.BBBB.__init__(self) + nonexistant.AClass.__init__(self) + +class DDDD(AAAA): + """call superclass constructor in disjunct branches""" + def __init__(self, value): + if value: + AAAA.__init__(self) + else: + AAAA.__init__(self) + +class Super(dict): + """ test late binding super() call """ + def __init__(self): + base = super() + base.__init__() + +class Super2(dict): + """ Using the same idiom as Super, but without calling + the __init__ method. + """ + def __init__(self): + base = super() + base.__woohoo__() diff --git a/pymode/libs/pylint/test/input/func_w0332_py_30.py b/pymode/libs/pylint/test/input/func_w0332_py_30.py new file mode 100644 index 00000000..6a38e8c0 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0332_py_30.py @@ -0,0 +1,4 @@ +"""check use of l as long int marker +""" +# pylint: disable=long-suffix +__revision__ = 1l diff --git a/pymode/libs/pylint/test/input/func_w0401.py b/pymode/libs/pylint/test/input/func_w0401.py new file mode 100644 index 00000000..12227bc9 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0401.py @@ -0,0 +1,9 @@ +"""test cyclic import +""" +# pylint: disable=no-absolute-import +from __future__ import print_function + +from . import w0401_cycle + +if w0401_cycle: + print(w0401_cycle) diff --git a/pymode/libs/pylint/test/input/func_w0401_package/__init__.py b/pymode/libs/pylint/test/input/func_w0401_package/__init__.py new file mode 100644 index 00000000..dedef660 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0401_package/__init__.py @@ -0,0 +1,2 @@ +"""Our big package.""" +__revision__ = None diff --git a/pymode/libs/pylint/test/input/func_w0401_package/all_the_things.py b/pymode/libs/pylint/test/input/func_w0401_package/all_the_things.py new file mode 100644 index 00000000..b8bd47b9 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0401_package/all_the_things.py @@ -0,0 +1,9 @@ +"""All the things!""" +# pylint: disable=no-absolute-import +from .thing1 import THING1 +from .thing2 import THING2 +from .thing2 import THING1_PLUS_THING2 +__revision__ = None + +_ = (THING1, THING2, THING1_PLUS_THING2) +del _ diff --git a/pymode/libs/pylint/test/input/func_w0401_package/thing1.py b/pymode/libs/pylint/test/input/func_w0401_package/thing1.py new file mode 100644 index 00000000..34972a7d --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0401_package/thing1.py @@ -0,0 +1,3 @@ +"""The first thing.""" +__revision__ = None +THING1 = "I am thing1" diff --git a/pymode/libs/pylint/test/input/func_w0401_package/thing2.py b/pymode/libs/pylint/test/input/func_w0401_package/thing2.py new file mode 100644 index 00000000..66d33167 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0401_package/thing2.py @@ -0,0 +1,7 @@ +"""The second thing.""" +# pylint: disable=no-absolute-import +from .all_the_things import THING1 +__revision__ = None + +THING2 = "I am thing2" +THING1_PLUS_THING2 = "%s, plus %s" % (THING1, THING2) diff --git a/pymode/libs/pylint/test/input/func_w0404.py b/pymode/libs/pylint/test/input/func_w0404.py new file mode 100644 index 00000000..5111c603 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0404.py @@ -0,0 +1,27 @@ +"""Unittests for W0404 (reimport)""" +from __future__ import absolute_import, print_function + +import sys + +import xml.etree.ElementTree +from xml.etree import ElementTree + +from email import encoders +import email.encoders + +import sys #pylint: disable=ungrouped-imports +__revision__ = 0 + +def no_reimport(): + """docstring""" + import os + print(os) + + +def reimport(): + """This function contains a reimport.""" + import sys + del sys + + +del sys, ElementTree, xml.etree.ElementTree, encoders, email.encoders diff --git a/pymode/libs/pylint/test/input/func_w0405.py b/pymode/libs/pylint/test/input/func_w0405.py new file mode 100644 index 00000000..50a069d7 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0405.py @@ -0,0 +1,31 @@ +"""check reimport +""" +from __future__ import absolute_import, print_function + +# pylint: disable=using-constant-test,ungrouped-imports,wrong-import-position +import os +from os.path import join, exists +import os +import re as _re + +__revision__ = 0 +_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: # pylint: disable=using-constant-test + import sys + print(sys.modules) +else: + print('bla') + import sys diff --git a/pymode/libs/pylint/test/input/func_w0406.py b/pymode/libs/pylint/test/input/func_w0406.py new file mode 100644 index 00000000..ceb12b5f --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0406.py @@ -0,0 +1,10 @@ +"""test module importing itself""" +# pylint: disable=no-absolute-import,using-constant-test +from __future__ import print_function +from . import func_w0406 + +__revision__ = 0 + + +if __revision__: + print(func_w0406) diff --git a/pymode/libs/pylint/test/input/func_w0611.py b/pymode/libs/pylint/test/input/func_w0611.py new file mode 100644 index 00000000..c129aba5 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0611.py @@ -0,0 +1,25 @@ +"""check unused import +""" +# pylint: disable=no-absolute-import + +from __future__ import print_function + +import os +import sys + +class NonRegr(object): + """???""" + def __init__(self): + print('initialized') + + def sys(self): + """should not get sys from there...""" + print(self, sys) + + def dummy(self, truc): + """yo""" + return self, truc + + def blop(self): + """yo""" + print(self, 'blip') diff --git a/pymode/libs/pylint/test/input/func_w0612.py b/pymode/libs/pylint/test/input/func_w0612.py new file mode 100644 index 00000000..3d873bbe --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0612.py @@ -0,0 +1,37 @@ +"""test unused variable +""" +# pylint: disable=invalid-name, redefined-outer-name, no-absolute-import +from __future__ import print_function +PATH = OS = collections = deque = None + +def function(matches): + """"yo""" + aaaa = 1 + index = -1 + for match in matches: + index += 1 + print(match) + +def visit_if(self, node): + """increments the branches counter""" + branches = 1 + # don't double count If nodes coming from some 'elif' + if node.orelse and len(node.orelse) > 1: + branches += 1 + self.inc_branch(branches) + self.stmts += branches + +def test_global(): + """ Test various assignments of global + variables through imports. + """ + global PATH, OS, collections, deque + from os import path as PATH + import os as OS + import collections + from collections import deque + # make sure that these triggers unused-variable + from sys import platform + from sys import version as VERSION + import this + import re as RE diff --git a/pymode/libs/pylint/test/input/func_w0613.py b/pymode/libs/pylint/test/input/func_w0613.py new file mode 100644 index 00000000..ea1ab29b --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0613.py @@ -0,0 +1,42 @@ +# pylint: disable=R0903, print-statement +"""test unused argument +""" +from __future__ import print_function + + +def function(arg=1): + """ignore arg""" + + +class AAAA(object): + """dummy class""" + + def method(self, arg): + """dummy method""" + print(self) + def __init__(self, *unused_args, **unused_kwargs): + pass + + @classmethod + def selected(cls, *args, **kwargs): + """called by the registry when the vobject has been selected. + """ + return cls + + def using_inner_function(self, etype, size=1): + """return a fake result set for a particular entity type""" + rset = AAAA([('A',)]*size, '%s X' % etype, + description=[(etype,)]*size) + def inner(row, col=0, etype=etype, req=self, rset=rset): + """inner using all its argument""" + # pylint: disable = E1103 + return req.vreg.etype_class(etype)(req, rset, row, col) + # pylint: disable = W0201 + rset.get_entity = inner + +class BBBB(object): + """dummy class""" + + def __init__(self, arg): + """Constructor with an extra parameter. Should raise a warning""" + self.spam = 1 diff --git a/pymode/libs/pylint/test/input/func_w0623_py30.py b/pymode/libs/pylint/test/input/func_w0623_py30.py new file mode 100644 index 00000000..163256b1 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0623_py30.py @@ -0,0 +1,16 @@ +"""Test for W0623, overwriting names in exception handlers.""" + +__revision__ = '' + +class MyError(Exception): + """Special exception class.""" + pass + + +def some_function(): + """A function.""" + + try: + {}["a"] + except KeyError as some_function: # W0623 + pass diff --git a/pymode/libs/pylint/test/input/func_w0623_py_30.py b/pymode/libs/pylint/test/input/func_w0623_py_30.py new file mode 100644 index 00000000..cc8d36cf --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0623_py_30.py @@ -0,0 +1,67 @@ +"""Test for W0623, overwriting names in exception handlers.""" +# pylint: disable=broad-except,bare-except,print-statement,no-absolute-import,duplicate-except +import exceptions + +__revision__ = '' + +class MyError(Exception): + """Special exception class.""" + pass + + +def some_function(): + """A function.""" + exc = None + + try: + {}["a"] + except KeyError, exceptions.RuntimeError: # W0623 + pass + except KeyError, OSError: # W0623 + pass + except KeyError, MyError: # W0623 + pass + except KeyError, exc: # this is fine + print exc + except KeyError, exc1: # this is fine + print exc1 + except KeyError, FOO: # C0103 + print FOO + + try: + pass + except KeyError, exc1: # this is fine + print exc1 + +class MyOtherError(Exception): + """Special exception class.""" + pass + + +exc3 = None + +try: + pass +except KeyError, exceptions.RuntimeError: # W0623 + pass +except KeyError, exceptions.RuntimeError.args: # W0623 + pass +except KeyError, OSError: # W0623 + pass +except KeyError, MyOtherError: # W0623 + pass +except KeyError, exc3: # this is fine + print exc3 +except KeyError, exc4: # this is fine + print exc4 +except KeyError, OOPS: # C0103 + print OOPS + +try: + pass +except KeyError, exc4: # this is fine + print exc4 +except IOError, exc5: # this is fine + print exc5 +except MyOtherError, exc5: # this is fine + print exc5 diff --git a/pymode/libs/pylint/test/input/func_w0631.py b/pymode/libs/pylint/test/input/func_w0631.py new file mode 100644 index 00000000..0390ac77 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0631.py @@ -0,0 +1,20 @@ +"""Check possible undefined loopvar +""" +from __future__ import print_function +__revision__ = 0 + +def do_stuff(some_random_list): + """This is not right.""" + for var in some_random_list: + pass + print(var) + + +def do_else(some_random_list): + """This is not right.""" + for var in some_random_list: + if var == 42: + break + else: + var = 84 + print(var) diff --git a/pymode/libs/pylint/test/input/func_w0703.py b/pymode/libs/pylint/test/input/func_w0703.py new file mode 100644 index 00000000..fb633fdf --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0703.py @@ -0,0 +1,9 @@ +"""check empty except statement +""" +from __future__ import print_function +__revision__ = 0 + +try: + __revision__ += 1 +except Exception: + print('error') diff --git a/pymode/libs/pylint/test/input/func_w0705.py b/pymode/libs/pylint/test/input/func_w0705.py new file mode 100644 index 00000000..ba2bab32 --- /dev/null +++ b/pymode/libs/pylint/test/input/func_w0705.py @@ -0,0 +1,46 @@ +"""test misordered except +""" + +__revision__ = 1 + +try: + __revision__ += 1 +except Exception: + __revision__ = 0 +except TypeError: + __revision__ = 0 + +try: + __revision__ += 1 +except LookupError: + __revision__ = 0 +except IndexError: + __revision__ = 0 + +try: + __revision__ += 1 +except (LookupError, NameError): + __revision__ = 0 +except (IndexError, UnboundLocalError): + __revision__ = 0 + +try: + __revision__ += 1 +except: + pass +except Exception: + pass + +try: + __revision__ += 1 +except TypeError: + __revision__ = 0 +except: + __revision__ = 0 + +try: + __revision__ += 1 +except Exception: + pass +except: + pass diff --git a/pymode/libs/pylint/test/input/func_w0801.py b/pymode/libs/pylint/test/input/func_w0801.py new file mode 100644 index 00000000..cd386ff5 --- /dev/null +++ b/pymode/libs/pylint/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/pymode/libs/pylint/test/input/func_with_without_as_py25.py b/pymode/libs/pylint/test/input/func_with_without_as_py25.py new file mode 100644 index 00000000..7ecf574a --- /dev/null +++ b/pymode/libs/pylint/test/input/func_with_without_as_py25.py @@ -0,0 +1,12 @@ +'''This is a little non regression test on a with statement +without 'as'. +''' +from __future__ import with_statement, print_function +__revision__ = 32321313 + +def do_nothing(arg): + 'ho foo' + print(arg) + with open('x'): + base.baz + base = 7 diff --git a/pymode/libs/pylint/test/input/ignore_except_pass_by_default.py b/pymode/libs/pylint/test/input/ignore_except_pass_by_default.py new file mode 100644 index 00000000..444e540d --- /dev/null +++ b/pymode/libs/pylint/test/input/ignore_except_pass_by_default.py @@ -0,0 +1,6 @@ +"""#5575: by default no W0704 warning on bare pass in except""" + +try: + __exception__ = 0 +except ValueError: + pass diff --git a/pymode/libs/pylint/test/input/noext b/pymode/libs/pylint/test/input/noext new file mode 100644 index 00000000..8aeda06d --- /dev/null +++ b/pymode/libs/pylint/test/input/noext @@ -0,0 +1,4 @@ +#!/usr/bin/env python +"""a python file without .py extension""" + +__revision__ = None diff --git a/pymode/libs/pylint/test/input/similar1 b/pymode/libs/pylint/test/input/similar1 new file mode 100644 index 00000000..2b04ee2f --- /dev/null +++ b/pymode/libs/pylint/test/input/similar1 @@ -0,0 +1,22 @@ +import one +from two import two +three +four +five +six # comments optionally ignored +seven +eight +nine +''' ten +eleven +twelve ''' +thirteen +fourteen +fifteen + + + + +sixteen +seventeen +eighteen diff --git a/pymode/libs/pylint/test/input/similar2 b/pymode/libs/pylint/test/input/similar2 new file mode 100644 index 00000000..77f5f1ed --- /dev/null +++ b/pymode/libs/pylint/test/input/similar2 @@ -0,0 +1,22 @@ +import one +from two import two +three +four +five +six +seven +eight +nine +''' ten +ELEVEN +twelve ''' +thirteen +fourteen +FIFTEEN + + + + +sixteen +seventeen +eighteen diff --git a/pymode/libs/pylint/test/input/syntax_error.py b/pymode/libs/pylint/test/input/syntax_error.py new file mode 100644 index 00000000..6c1e7a74 --- /dev/null +++ b/pymode/libs/pylint/test/input/syntax_error.py @@ -0,0 +1,2 @@ +if False: +print('hop') diff --git a/pymode/libs/pylint/test/input/w0401_cycle.py b/pymode/libs/pylint/test/input/w0401_cycle.py new file mode 100644 index 00000000..ddb42557 --- /dev/null +++ b/pymode/libs/pylint/test/input/w0401_cycle.py @@ -0,0 +1,9 @@ +"""w0401 dependency +""" +# pylint: disable=print-statement, no-absolute-import +from __future__ import print_function + +from . import func_w0401 + +if func_w0401: + print(input) diff --git a/pymode/libs/pylint/test/input/w0801_same.py b/pymode/libs/pylint/test/input/w0801_same.py new file mode 100644 index 00000000..cd386ff5 --- /dev/null +++ b/pymode/libs/pylint/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/pymode/libs/pylint/test/messages/builtin_module.txt b/pymode/libs/pylint/test/messages/builtin_module.txt new file mode 100644 index 00000000..2616c0e2 --- /dev/null +++ b/pymode/libs/pylint/test/messages/builtin_module.txt @@ -0,0 +1 @@ +F: 1: ignored builtin module sys diff --git a/pymode/libs/pylint/test/messages/func_3k_removed_stuff_py_30.txt b/pymode/libs/pylint/test/messages/func_3k_removed_stuff_py_30.txt new file mode 100644 index 00000000..8e3b2038 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_3k_removed_stuff_py_30.txt @@ -0,0 +1,4 @@ +E: 12:function: Instance of 'unicode' has no 'looower' member +W: 4: Relative import 'func_w0401', should be 'input.func_w0401' +W: 5: Used builtin function 'filter'. Using a list comprehension can be clearer. +W: 5: Used builtin function 'map'. Using a list comprehension can be clearer. diff --git a/pymode/libs/pylint/test/messages/func_bad_assigment_to_exception_var.txt b/pymode/libs/pylint/test/messages/func_bad_assigment_to_exception_var.txt new file mode 100644 index 00000000..119fbe2d --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_bad_assigment_to_exception_var.txt @@ -0,0 +1,5 @@ +E: 11: Raising int while only classes or instances are allowed +E: 20:func: Raising NoneType while only classes or instances are allowed +E: 30: Raising NoneType while only classes or instances are allowed +W: 12: Catching too general exception Exception + diff --git a/pymode/libs/pylint/test/messages/func_bad_cont_dictcomp_py27.txt b/pymode/libs/pylint/test/messages/func_bad_cont_dictcomp_py27.txt new file mode 100644 index 00000000..51bde771 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_bad_cont_dictcomp_py27.txt @@ -0,0 +1,6 @@ +C: 35: Wrong continued indentation (add 2 spaces). + for x in range(3)} # [bad-continuation] + ^ | +C: 38: Wrong continued indentation (remove 4 spaces). + for x in range(3)} # [bad-continuation] + | ^ diff --git a/pymode/libs/pylint/test/messages/func_block_disable_msg.txt b/pymode/libs/pylint/test/messages/func_block_disable_msg.txt new file mode 100644 index 00000000..1a16fbed --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_block_disable_msg.txt @@ -0,0 +1,13 @@ +E: 26:Foo.meth3: Instance of 'Foo' has no 'blop' member +E: 36:Foo.meth4: Instance of 'Foo' has no 'blip' member +E: 46:Foo.meth5: Instance of 'Foo' has no 'blip' member +E: 61:Foo.meth6: Instance of 'Foo' has no 'blip' member +E: 72:Foo.meth7: Instance of 'Foo' has no 'blip' member +E: 75:Foo.meth7: Instance of 'Foo' has no 'blip' member +E: 77:Foo.meth7: Instance of 'Foo' has no 'blip' member +E: 83:Foo.meth8: Instance of 'Foo' has no 'blip' member +E:102:Foo.meth9: Instance of 'Foo' has no 'blip' member +W: 11:Foo.meth1: Unused argument 'arg' +W:144:ClassLevelMessage.too_complex_but_thats_ok: Else clause on loop without a break statement +W:150:ClassLevelMessage.too_complex_but_thats_ok: Else clause on loop without a break statement +W:156:ClassLevelMessage.too_complex_but_thats_ok: Else clause on loop without a break statement diff --git a/pymode/libs/pylint/test/messages/func_break_or_return_in_try_finally.txt b/pymode/libs/pylint/test/messages/func_break_or_return_in_try_finally.txt new file mode 100644 index 00000000..04f27fe7 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_break_or_return_in_try_finally.txt @@ -0,0 +1,3 @@ +W: 18:insidious_break_and_return: break statement in finally block may swallow exception +W: 20:insidious_break_and_return: return statement in finally block may swallow exception +W: 39:break_and_return.strange: Cell variable my_var defined in loop diff --git a/pymode/libs/pylint/test/messages/func_bug113231.txt b/pymode/libs/pylint/test/messages/func_bug113231.txt new file mode 100644 index 00000000..a9d3f7ef --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_bug113231.txt @@ -0,0 +1,2 @@ +W: 20: Specify string format arguments as logging function parameters +W: 21: Specify string format arguments as logging function parameters diff --git a/pymode/libs/pylint/test/messages/func_disable_linebased.txt b/pymode/libs/pylint/test/messages/func_disable_linebased.txt new file mode 100644 index 00000000..06e391d4 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_disable_linebased.txt @@ -0,0 +1,2 @@ +C: 1: Line too long (127/100) +C: 14: Line too long (114/100) diff --git a/pymode/libs/pylint/test/messages/func_disable_linebased_py30.txt b/pymode/libs/pylint/test/messages/func_disable_linebased_py30.txt new file mode 100644 index 00000000..06e391d4 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_disable_linebased_py30.txt @@ -0,0 +1,2 @@ +C: 1: Line too long (127/100) +C: 14: Line too long (114/100) diff --git a/pymode/libs/pylint/test/messages/func_dotted_ancestor.txt b/pymode/libs/pylint/test/messages/func_dotted_ancestor.txt new file mode 100644 index 00000000..6e2c6fa5 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_dotted_ancestor.txt @@ -0,0 +1 @@ +R: 8:Aaaa: Too few public methods (0/2) diff --git a/pymode/libs/pylint/test/messages/func_e0012.txt b/pymode/libs/pylint/test/messages/func_e0012.txt new file mode 100644 index 00000000..a6d1b698 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_e0012.txt @@ -0,0 +1 @@ +E: 1: Bad option value 'W04044' diff --git a/pymode/libs/pylint/test/messages/func_e0203.txt b/pymode/libs/pylint/test/messages/func_e0203.txt new file mode 100644 index 00000000..96769def --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_e0203.txt @@ -0,0 +1,2 @@ +C: 12:Abcd.abcd: Class method abcd should have 'cls' as first argument +R: 15:Abcd: Consider using a decorator instead of calling classmethod diff --git a/pymode/libs/pylint/test/messages/func_e0204.txt b/pymode/libs/pylint/test/messages/func_e0204.txt new file mode 100644 index 00000000..8e05efea --- /dev/null +++ b/pymode/libs/pylint/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/pymode/libs/pylint/test/messages/func_e0601.txt b/pymode/libs/pylint/test/messages/func_e0601.txt new file mode 100644 index 00000000..321c7316 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_e0601.txt @@ -0,0 +1 @@ +E: 8:function: Using variable 'aaaa' before assignment diff --git a/pymode/libs/pylint/test/messages/func_e0604.txt b/pymode/libs/pylint/test/messages/func_e0604.txt new file mode 100644 index 00000000..e2326331 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_e0604.txt @@ -0,0 +1 @@ +E: 13: Invalid object 'some_function' in __all__, must contain only strings diff --git a/pymode/libs/pylint/test/messages/func_e12xx.txt b/pymode/libs/pylint/test/messages/func_e12xx.txt new file mode 100644 index 00000000..92640d25 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_e12xx.txt @@ -0,0 +1,6 @@ +E: 14:pprint: Too many arguments for logging format string +E: 15:pprint: Too many arguments for logging format string +E: 16:pprint: Logging format string ends in middle of conversion specifier +E: 17:pprint: Not enough arguments for logging format string +E: 18:pprint: Unsupported logging format character 'y' (0x79) at index 3 +E: 19:pprint: Too many arguments for logging format string diff --git a/pymode/libs/pylint/test/messages/func_e13xx.txt b/pymode/libs/pylint/test/messages/func_e13xx.txt new file mode 100644 index 00000000..f2d0d364 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_e13xx.txt @@ -0,0 +1,13 @@ +E: 11:pprint: Not enough arguments for format string +E: 12:pprint: Too many arguments for format string +E: 13:pprint: Mixing named and unnamed conversion specifiers in format string +E: 14:pprint: Missing key 'PARG_2' in format string dictionary +E: 16:pprint: Missing key 'PARG_2' in format string dictionary +E: 17:pprint: Expected mapping for format string, not Tuple +E: 18:pprint: Expected mapping for format string, not List +E: 19:pprint: Unsupported format character 'z' (0x7a) at index 2 +E: 20:pprint: Format string ends in middle of conversion specifier +E: 21:pprint: Unsupported format character 'a' (0x61) at index 12 +W: 15:pprint: Unused key 'PARG_3' in format string dictionary +W: 16:pprint: Format string dictionary key should be a string, not 2 + diff --git a/pymode/libs/pylint/test/messages/func_e13xx_py30.txt b/pymode/libs/pylint/test/messages/func_e13xx_py30.txt new file mode 100644 index 00000000..7ac9fb18 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_e13xx_py30.txt @@ -0,0 +1,11 @@ +E: 11:pprint: Not enough arguments for format string +E: 12:pprint: Too many arguments for format string +E: 13:pprint: Mixing named and unnamed conversion specifiers in format string +E: 14:pprint: Missing key 'PARG_2' in format string dictionary +E: 16:pprint: Missing key 'PARG_2' in format string dictionary +E: 17:pprint: Expected mapping for format string, not Tuple +E: 18:pprint: Expected mapping for format string, not List +E: 19:pprint: Unsupported format character 'z' (0x7a) at index 2 +E: 20:pprint: Format string ends in middle of conversion specifier +W: 15:pprint: Unused key 'PARG_3' in format string dictionary +W: 16:pprint: Format string dictionary key should be a string, not 2 \ No newline at end of file diff --git a/pymode/libs/pylint/test/messages/func_excess_escapes.txt b/pymode/libs/pylint/test/messages/func_excess_escapes.txt new file mode 100644 index 00000000..2f07722c --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_excess_escapes.txt @@ -0,0 +1,9 @@ +W: 7: Anomalous backslash in string: '\['. String constant might be missing an r prefix. +W: 7: Anomalous backslash in string: '\]'. String constant might be missing an r prefix. +W: 8: Anomalous backslash in string: '\/'. String constant might be missing an r prefix. +W: 9: Anomalous backslash in string: '\`'. String constant might be missing an r prefix. +W: 15: Anomalous backslash in string: '\o'. String constant might be missing an r prefix. +W: 15: Anomalous backslash in string: '\o'. String constant might be missing an r prefix. +W: 17: Anomalous backslash in string: '\8'. String constant might be missing an r prefix. +W: 17: Anomalous backslash in string: '\9'. String constant might be missing an r prefix. +W: 27: Anomalous backslash in string: '\P'. String constant might be missing an r prefix. diff --git a/pymode/libs/pylint/test/messages/func_first_arg.txt b/pymode/libs/pylint/test/messages/func_first_arg.txt new file mode 100644 index 00000000..ba4efb8a --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_first_arg.txt @@ -0,0 +1,9 @@ +C: 10:Obj.__new__: Class method __new__ should have 'cls' as first argument +C: 18:Obj.class2: Class method class2 should have 'cls' as first argument +C: 25:Meta.__new__: Metaclass class method __new__ should have 'mcs' as first argument +C: 32:Meta.method2: Metaclass method method2 should have 'cls' as first argument +C: 40:Meta.class2: Metaclass class method class2 should have 'mcs' as first argument +R: 16:Obj: Consider using a decorator instead of calling classmethod +R: 20:Obj: Consider using a decorator instead of calling classmethod +R: 38:Meta: Consider using a decorator instead of calling classmethod +R: 42:Meta: Consider using a decorator instead of calling classmethod diff --git a/pymode/libs/pylint/test/messages/func_i0011.txt b/pymode/libs/pylint/test/messages/func_i0011.txt new file mode 100644 index 00000000..6c1071d3 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_i0011.txt @@ -0,0 +1,2 @@ +I: 1: Locally disabling reimported (W0404) +I: 1: Useless suppression of 'reimported' diff --git a/pymode/libs/pylint/test/messages/func_i0012.txt b/pymode/libs/pylint/test/messages/func_i0012.txt new file mode 100644 index 00000000..76b0c77e --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_i0012.txt @@ -0,0 +1 @@ +I: 1: Locally enabling reimported (W0404) diff --git a/pymode/libs/pylint/test/messages/func_i0013.txt b/pymode/libs/pylint/test/messages/func_i0013.txt new file mode 100644 index 00000000..75d7afd6 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_i0013.txt @@ -0,0 +1 @@ +I: 1: Ignoring entire file diff --git a/pymode/libs/pylint/test/messages/func_i0014.txt b/pymode/libs/pylint/test/messages/func_i0014.txt new file mode 100644 index 00000000..c3b521d5 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_i0014.txt @@ -0,0 +1,2 @@ +I: 1: Ignoring entire file +I: 1: Pragma "disable-all" is deprecated, use "skip-file" instead diff --git a/pymode/libs/pylint/test/messages/func_i0020.txt b/pymode/libs/pylint/test/messages/func_i0020.txt new file mode 100644 index 00000000..73f83a42 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_i0020.txt @@ -0,0 +1,2 @@ +I: 7: Locally disabling unused-variable (W0612) +I: 8: Suppressed 'unused-variable' (from line 7) diff --git a/pymode/libs/pylint/test/messages/func_i0022.txt b/pymode/libs/pylint/test/messages/func_i0022.txt new file mode 100644 index 00000000..fb527b1d --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_i0022.txt @@ -0,0 +1,21 @@ +I: 5: Locally disabling invalid-name (C0103) +I: 5: Suppressed 'invalid-name' (from line 5) +I: 6: Locally disabling invalid-name (C0103) +I: 6: Pragma "disable-msg" is deprecated, use "disable" instead +I: 6: Suppressed 'invalid-name' (from line 6) +I: 8: Locally disabling invalid-name (C0103) +I: 9: Suppressed 'invalid-name' (from line 8) +I: 10: Locally enabling invalid-name (C0103) +I: 12: Locally disabling invalid-name (C0103) +I: 12: Pragma "disable-msg" is deprecated, use "disable" instead +I: 13: Suppressed 'invalid-name' (from line 12) +I: 14: Locally enabling invalid-name (C0103) +I: 14: Pragma "enable-msg" is deprecated, use "enable" instead +I: 16: Locally disabling invalid-name (C0103) +I: 16: Pragma "disable-msg" is deprecated, use "disable" instead +I: 17: Suppressed 'invalid-name' (from line 16) +I: 18: Locally enabling invalid-name (C0103) +I: 18: Pragma "enable-msg" is deprecated, use "enable" instead +I: 20: Locally disabling invalid-name (C0103) +I: 21: Suppressed 'invalid-name' (from line 20) +I: 22: Locally enabling invalid-name (C0103) diff --git a/pymode/libs/pylint/test/messages/func_init_vars.txt b/pymode/libs/pylint/test/messages/func_init_vars.txt new file mode 100644 index 00000000..44ef6f31 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_init_vars.txt @@ -0,0 +1 @@ +W: 18:MyClass.met: Attribute 'base_var' defined outside __init__ diff --git a/pymode/libs/pylint/test/messages/func_kwoa_py30.txt b/pymode/libs/pylint/test/messages/func_kwoa_py30.txt new file mode 100644 index 00000000..08dd8c57 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_kwoa_py30.txt @@ -0,0 +1,5 @@ +E: 10: Missing mandatory keyword argument 'foo' in function call +E: 10: Too many positional arguments for function call +E: 12: Missing mandatory keyword argument 'foo' in function call +E: 12: Too many positional arguments for function call +W: 3:function: Redefining name 'foo' from outer scope (line 9) diff --git a/pymode/libs/pylint/test/messages/func_logging_not_lazy_with_logger.txt b/pymode/libs/pylint/test/messages/func_logging_not_lazy_with_logger.txt new file mode 100644 index 00000000..04823cf3 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_logging_not_lazy_with_logger.txt @@ -0,0 +1,4 @@ +W: 8: Specify string format arguments as logging function parameters +W: 9: Specify string format arguments as logging function parameters +W: 11: Specify string format arguments as logging function parameters +W: 13: Specify string format arguments as logging function parameters diff --git a/pymode/libs/pylint/test/messages/func_loopvar_in_dict_comp_py27.txt b/pymode/libs/pylint/test/messages/func_loopvar_in_dict_comp_py27.txt new file mode 100644 index 00000000..bc11121c --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_loopvar_in_dict_comp_py27.txt @@ -0,0 +1 @@ +W: 8:bad_case.: Cell variable x defined in loop diff --git a/pymode/libs/pylint/test/messages/func_module___dict__.txt b/pymode/libs/pylint/test/messages/func_module___dict__.txt new file mode 100644 index 00000000..41698243 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_module___dict__.txt @@ -0,0 +1 @@ +E: 5: Using variable '__dict__' before assignment diff --git a/pymode/libs/pylint/test/messages/func_nameerror_on_string_substitution.txt b/pymode/libs/pylint/test/messages/func_nameerror_on_string_substitution.txt new file mode 100644 index 00000000..aab21024 --- /dev/null +++ b/pymode/libs/pylint/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/pymode/libs/pylint/test/messages/func_no_dummy_redefined.txt b/pymode/libs/pylint/test/messages/func_no_dummy_redefined.txt new file mode 100644 index 00000000..9ef7891e --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_no_dummy_redefined.txt @@ -0,0 +1,2 @@ +C: 7: Invalid constant name "value" +W: 12:clobbering: Redefining name 'value' from outer scope (line 7) diff --git a/pymode/libs/pylint/test/messages/func_nonregr___file___global.txt b/pymode/libs/pylint/test/messages/func_nonregr___file___global.txt new file mode 100644 index 00000000..c0d7340f --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_nonregr___file___global.txt @@ -0,0 +1,2 @@ +W: 5:func: Redefining built-in '__file__' +W: 5:func: Using the global statement diff --git a/pymode/libs/pylint/test/messages/func_raw_escapes.txt b/pymode/libs/pylint/test/messages/func_raw_escapes.txt new file mode 100644 index 00000000..991fba7d --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_raw_escapes.txt @@ -0,0 +1,3 @@ +W: 5: Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix. +W: 6: Anomalous Unicode escape in byte string: '\U'. String constant might be missing an r or u prefix. +W: 7: Anomalous Unicode escape in byte string: '\N'. String constant might be missing an r or u prefix. diff --git a/pymode/libs/pylint/test/messages/func_return_yield_mix_py_33.txt b/pymode/libs/pylint/test/messages/func_return_yield_mix_py_33.txt new file mode 100644 index 00000000..d81ce5cf --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_return_yield_mix_py_33.txt @@ -0,0 +1,2 @@ +E: 6:somegen: Return with argument inside generator +E: 15:moregen: Return with argument inside generator diff --git a/pymode/libs/pylint/test/messages/func_too_many_returns_yields.txt b/pymode/libs/pylint/test/messages/func_too_many_returns_yields.txt new file mode 100644 index 00000000..f8a0f0d2 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_too_many_returns_yields.txt @@ -0,0 +1 @@ +R: 5:too_many_returns: Too many return statements (11/6) diff --git a/pymode/libs/pylint/test/messages/func_toolonglines_py30.txt b/pymode/libs/pylint/test/messages/func_toolonglines_py30.txt new file mode 100644 index 00000000..cd594f5f --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_toolonglines_py30.txt @@ -0,0 +1,4 @@ +C: 1: Line too long (101/100) +C: 2: Line too long (104/100) +C: 17: Line too long (102/100) +C: 25: Line too long (105/100) diff --git a/pymode/libs/pylint/test/messages/func_typecheck_callfunc_assigment.txt b/pymode/libs/pylint/test/messages/func_typecheck_callfunc_assigment.txt new file mode 100644 index 00000000..e1d05642 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_typecheck_callfunc_assigment.txt @@ -0,0 +1,3 @@ +E: 20: Assigning to function call which doesn't return +E: 28: Assigning to function call which only returns None +E: 35: Assigning to function call which only returns None diff --git a/pymode/libs/pylint/test/messages/func_typecheck_getattr_py30.txt b/pymode/libs/pylint/test/messages/func_typecheck_getattr_py30.txt new file mode 100644 index 00000000..b6bf150f --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_typecheck_getattr_py30.txt @@ -0,0 +1,9 @@ +E: 25:Client.__init__: Class 'Provider' has no 'cattribute' member +E: 35:Client.use_method: Instance of 'Provider' has no 'hophophop' member +E: 40:Client.use_attr: Instance of 'Provider' has no 'attribute' member +E: 52:Client.test_bt_types: Instance of 'list' has no 'apppend' member +E: 54:Client.test_bt_types: Instance of 'dict' has no 'set' member +E: 56:Client.test_bt_types: Instance of 'tuple' has no 'append' member +E: 58:Client.test_bt_types: Instance of 'str' has no 'loower' member +E: 62:Client.test_bt_types: Instance of 'int' has no 'whatever' member +E: 66: Instance of 'int' has no 'lower' member (but some types could not be inferred) diff --git a/pymode/libs/pylint/test/messages/func_typecheck_non_callable_call.txt b/pymode/libs/pylint/test/messages/func_typecheck_non_callable_call.txt new file mode 100644 index 00000000..8baa237a --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_typecheck_non_callable_call.txt @@ -0,0 +1,8 @@ +E: 10: __revision__ is not callable +E: 29: INSTANCE is not callable +E: 31: LIST is not callable +E: 33: DICT is not callable +E: 35: TUPLE is not callable +E: 37: INT is not callable +E: 72: PROP.test is not callable +E: 73: PROP.custom is not callable \ No newline at end of file diff --git a/pymode/libs/pylint/test/messages/func_unicode_literal_py26.txt b/pymode/libs/pylint/test/messages/func_unicode_literal_py26.txt new file mode 100644 index 00000000..e69de29b diff --git a/pymode/libs/pylint/test/messages/func_unicode_literal_py274.txt b/pymode/libs/pylint/test/messages/func_unicode_literal_py274.txt new file mode 100644 index 00000000..25efa999 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_unicode_literal_py274.txt @@ -0,0 +1 @@ +W: 6: Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix. diff --git a/pymode/libs/pylint/test/messages/func_unused_import_py30.txt b/pymode/libs/pylint/test/messages/func_unused_import_py30.txt new file mode 100644 index 00000000..1868abc2 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_unused_import_py30.txt @@ -0,0 +1 @@ +W: 8: Reimport 'ABCMeta' (imported line 7) diff --git a/pymode/libs/pylint/test/messages/func_unused_overridden_argument.txt b/pymode/libs/pylint/test/messages/func_unused_overridden_argument.txt new file mode 100644 index 00000000..d7da45b1 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_unused_overridden_argument.txt @@ -0,0 +1 @@ +W: 22:Sub.newmethod: Unused argument 'aay' diff --git a/pymode/libs/pylint/test/messages/func_use_for_or_listcomp_var_py29.txt b/pymode/libs/pylint/test/messages/func_use_for_or_listcomp_var_py29.txt new file mode 100644 index 00000000..14d61ad6 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_use_for_or_listcomp_var_py29.txt @@ -0,0 +1,3 @@ +W: 6: Using possibly undefined loop variable 'C' +W: 15: Using possibly undefined loop variable 'var1' + diff --git a/pymode/libs/pylint/test/messages/func_use_for_or_listcomp_var_py30.txt b/pymode/libs/pylint/test/messages/func_use_for_or_listcomp_var_py30.txt new file mode 100644 index 00000000..46d3430a --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_use_for_or_listcomp_var_py30.txt @@ -0,0 +1,3 @@ +E: 6: Using variable 'C' before assignment +W: 15: Using possibly undefined loop variable 'var1' + diff --git a/pymode/libs/pylint/test/messages/func_variables_unused_name_from_wilcard_import.txt b/pymode/libs/pylint/test/messages/func_variables_unused_name_from_wilcard_import.txt new file mode 100644 index 00000000..01c57af0 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_variables_unused_name_from_wilcard_import.txt @@ -0,0 +1,4 @@ +W: 3: Unused import NonRegr from wildcard import +W: 3: Unused import os from wildcard import +W: 3: Unused import sys from wildcard import +W: 3: Wildcard import input.func_w0611 diff --git a/pymode/libs/pylint/test/messages/func_w0122_py_30.txt b/pymode/libs/pylint/test/messages/func_w0122_py_30.txt new file mode 100644 index 00000000..d833076d --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0122_py_30.txt @@ -0,0 +1,5 @@ +W: 5: Use of exec +W: 6: Use of exec +W: 8: Use of exec +W: 12:func: Use of exec + diff --git a/pymode/libs/pylint/test/messages/func_w0205.txt b/pymode/libs/pylint/test/messages/func_w0205.txt new file mode 100644 index 00000000..83ac5de2 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0205.txt @@ -0,0 +1,2 @@ +W: 22:Cdef.abcd: Signature differs from overridden 'abcd' method + diff --git a/pymode/libs/pylint/test/messages/func_w0233.txt b/pymode/libs/pylint/test/messages/func_w0233.txt new file mode 100644 index 00000000..7c7780e7 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0233.txt @@ -0,0 +1,6 @@ +E: 6: Unable to import 'nonexistant' +E: 22:CCC: Module 'input.func_w0233' has no 'BBBB' member +E: 27:CCC.__init__: Module 'input.func_w0233' has no 'BBBB' member +E: 50:Super2.__init__: Super of 'Super2' has no '__woohoo__' member +W: 14:AAAA.__init__: __init__ method from a non direct base class 'BBBBMixin' is called +W: 48:Super2.__init__: __init__ method from base class 'dict' is not called diff --git a/pymode/libs/pylint/test/messages/func_w0312.txt b/pymode/libs/pylint/test/messages/func_w0312.txt new file mode 100644 index 00000000..917e8d0e --- /dev/null +++ b/pymode/libs/pylint/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/pymode/libs/pylint/test/messages/func_w0332_py_30.txt b/pymode/libs/pylint/test/messages/func_w0332_py_30.txt new file mode 100644 index 00000000..16f1d802 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0332_py_30.txt @@ -0,0 +1 @@ +W: 4: Use of "l" as long integer identifier diff --git a/pymode/libs/pylint/test/messages/func_w0401.txt b/pymode/libs/pylint/test/messages/func_w0401.txt new file mode 100644 index 00000000..f6648f47 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0401.txt @@ -0,0 +1,3 @@ +R: 1: Cyclic import (input.func_w0401 -> input.w0401_cycle) +W: 8: Using a conditional statement with a constant value +W: 8: Using a conditional statement with a constant value diff --git a/pymode/libs/pylint/test/messages/func_w0401_package.txt b/pymode/libs/pylint/test/messages/func_w0401_package.txt new file mode 100644 index 00000000..4b1145bb --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0401_package.txt @@ -0,0 +1 @@ +R: 1: Cyclic import (input.func_w0401_package.all_the_things -> input.func_w0401_package.thing2) diff --git a/pymode/libs/pylint/test/messages/func_w0404.txt b/pymode/libs/pylint/test/messages/func_w0404.txt new file mode 100644 index 00000000..2cfc1373 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0404.txt @@ -0,0 +1,5 @@ +W: 7: Reimport 'ElementTree' (imported line 6) +W: 10: Reimport 'email.encoders' (imported line 9) +W: 12: Reimport 'sys' (imported line 4) +W: 23:reimport: Redefining name 'sys' from outer scope (line 4) +W: 23:reimport: Reimport 'sys' (imported line 4) diff --git a/pymode/libs/pylint/test/messages/func_w0405.txt b/pymode/libs/pylint/test/messages/func_w0405.txt new file mode 100644 index 00000000..072b1554 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0405.txt @@ -0,0 +1,4 @@ +W: 8: 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 9) diff --git a/pymode/libs/pylint/test/messages/func_w0406.txt b/pymode/libs/pylint/test/messages/func_w0406.txt new file mode 100644 index 00000000..f6bc14db --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0406.txt @@ -0,0 +1 @@ +W: 4: Module import itself diff --git a/pymode/libs/pylint/test/messages/func_w0611.txt b/pymode/libs/pylint/test/messages/func_w0611.txt new file mode 100644 index 00000000..2be0d0a8 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0611.txt @@ -0,0 +1 @@ +W: 7: Unused import os diff --git a/pymode/libs/pylint/test/messages/func_w0612.txt b/pymode/libs/pylint/test/messages/func_w0612.txt new file mode 100644 index 00000000..752aa700 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0612.txt @@ -0,0 +1,5 @@ +W: 9:function: Unused variable 'aaaa' +W: 34:test_global: Unused variable 'platform' +W: 35:test_global: Unused variable 'VERSION' +W: 36:test_global: Unused variable 'this' +W: 37:test_global: Unused variable 'RE' diff --git a/pymode/libs/pylint/test/messages/func_w0613.txt b/pymode/libs/pylint/test/messages/func_w0613.txt new file mode 100644 index 00000000..36cb3801 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0613.txt @@ -0,0 +1,5 @@ +W: 7:function: Unused argument 'arg' +W: 14:AAAA.method: Unused argument 'arg' +W: 21:AAAA.selected: Unused argument 'args' +W: 21:AAAA.selected: Unused argument 'kwargs' +W: 40:BBBB.__init__: Unused argument 'arg' diff --git a/pymode/libs/pylint/test/messages/func_w0622.txt b/pymode/libs/pylint/test/messages/func_w0622.txt new file mode 100644 index 00000000..71913472 --- /dev/null +++ b/pymode/libs/pylint/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/pymode/libs/pylint/test/messages/func_w0623.txt b/pymode/libs/pylint/test/messages/func_w0623.txt new file mode 100644 index 00000000..b764def8 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0623.txt @@ -0,0 +1,11 @@ +C: 28:some_function: Invalid variable name "FOO" +C: 41: Invalid constant name "exc3" +C: 55: Invalid variable name "OOPS" +W: 18:some_function: Redefining name 'RuntimeError' from object 'exceptions' in exception handler +W: 20:some_function: Redefining name 'OSError' from builtins in exception handler +W: 20:some_function: Unused variable 'OSError' +W: 22:some_function: Redefining name 'MyError' from outer scope (line 7) in exception handler +W: 22:some_function: Unused variable 'MyError' +W: 45: Redefining name 'RuntimeError' from object 'exceptions' in exception handler +W: 47: Redefining name 'OSError' from builtins in exception handler +W: 49: Redefining name 'MyOtherError' from outer scope (line 36) in exception handler diff --git a/pymode/libs/pylint/test/messages/func_w0623_py30.txt b/pymode/libs/pylint/test/messages/func_w0623_py30.txt new file mode 100644 index 00000000..5d78dbfd --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0623_py30.txt @@ -0,0 +1,2 @@ +W: 15:some_function: Redefining name 'some_function' from outer scope (line 10) in exception handler +W: 15:some_function: Unused variable 'some_function' diff --git a/pymode/libs/pylint/test/messages/func_w0623_py_30.txt b/pymode/libs/pylint/test/messages/func_w0623_py_30.txt new file mode 100644 index 00000000..a2923f1f --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0623_py_30.txt @@ -0,0 +1,12 @@ +C: 28:some_function: Invalid variable name "FOO" +C: 41: Invalid constant name "exc3" +C: 57: Invalid variable name "OOPS" +W: 18:some_function: Redefining name 'RuntimeError' from object 'exceptions' in exception handler +W: 20:some_function: Redefining name 'OSError' from builtins in exception handler +W: 20:some_function: Unused variable 'OSError' +W: 22:some_function: Redefining name 'MyError' from outer scope (line 7) in exception handler +W: 22:some_function: Unused variable 'MyError' +W: 45: Redefining name 'RuntimeError' from object 'exceptions' in exception handler +W: 47: Redefining name 'args' from object 'exceptions.RuntimeError' in exception handler +W: 49: Redefining name 'OSError' from builtins in exception handler +W: 51: Redefining name 'MyOtherError' from outer scope (line 36) in exception handler diff --git a/pymode/libs/pylint/test/messages/func_w0631.txt b/pymode/libs/pylint/test/messages/func_w0631.txt new file mode 100644 index 00000000..af335a17 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0631.txt @@ -0,0 +1 @@ +W: 10:do_stuff: Using possibly undefined loop variable 'var' diff --git a/pymode/libs/pylint/test/messages/func_w0703.txt b/pymode/libs/pylint/test/messages/func_w0703.txt new file mode 100644 index 00000000..6135664e --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0703.txt @@ -0,0 +1 @@ +W: 8: Catching too general exception Exception diff --git a/pymode/libs/pylint/test/messages/func_w0705.txt b/pymode/libs/pylint/test/messages/func_w0705.txt new file mode 100644 index 00000000..1cc32a7a --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0705.txt @@ -0,0 +1,11 @@ +E: 10: Bad except clauses order (Exception is an ancestor class of TypeError) +E: 17: Bad except clauses order (LookupError is an ancestor class of IndexError) +E: 24: Bad except clauses order (LookupError is an ancestor class of IndexError) +E: 24: Bad except clauses order (NameError is an ancestor class of UnboundLocalError) +E: 27: Bad except clauses order (empty except clause should always appear last) +W: 8: Catching too general exception Exception +W: 29: No exception type(s) specified +W: 31: Catching too general exception Exception +W: 38: No exception type(s) specified +W: 43: Catching too general exception Exception +W: 45: No exception type(s) specified diff --git a/pymode/libs/pylint/test/messages/func_w0801.txt b/pymode/libs/pylint/test/messages/func_w0801.txt new file mode 100644 index 00000000..203ce929 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_w0801.txt @@ -0,0 +1,11 @@ +R: 1: Similar lines in 2 files +==input.func_w0801:3 +==input.w0801_same:3 +__revision__ = 'id' +A = 2 +B = 3 +C = A + B +# need more than X lines to trigger the message +C *= 2 +A -= B +# all this should be detected diff --git a/pymode/libs/pylint/test/messages/func_with_without_as_py25.txt b/pymode/libs/pylint/test/messages/func_with_without_as_py25.txt new file mode 100644 index 00000000..18ca3711 --- /dev/null +++ b/pymode/libs/pylint/test/messages/func_with_without_as_py25.txt @@ -0,0 +1,3 @@ +E: 11:do_nothing: Using variable 'base' before assignment +W: 11:do_nothing: Statement seems to have no effect + diff --git a/pymode/libs/pylint/test/regrtest_data/absimp/__init__.py b/pymode/libs/pylint/test/regrtest_data/absimp/__init__.py new file mode 100644 index 00000000..b98444df --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/absimp/__init__.py @@ -0,0 +1,5 @@ +"""a package with absolute import activated +""" + +from __future__ import absolute_import + diff --git a/pymode/libs/pylint/test/regrtest_data/absimp/string.py b/pymode/libs/pylint/test/regrtest_data/absimp/string.py new file mode 100644 index 00000000..f47e9a5e --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/absimp/string.py @@ -0,0 +1,7 @@ +""" +http://www.logilab.org/ticket/70495 +http://www.logilab.org/ticket/70565 +""" +from __future__ import absolute_import, print_function +import string +print(string) diff --git a/pymode/libs/pylint/test/regrtest_data/application_crash.py b/pymode/libs/pylint/test/regrtest_data/application_crash.py new file mode 100644 index 00000000..6e6044aa --- /dev/null +++ b/pymode/libs/pylint/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/pymode/libs/pylint/test/regrtest_data/bad_package/__init__.py b/pymode/libs/pylint/test/regrtest_data/bad_package/__init__.py new file mode 100644 index 00000000..60585fdb --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/bad_package/__init__.py @@ -0,0 +1,2 @@ +import missing +raise missing.Missing.. \ No newline at end of file diff --git a/pymode/libs/pylint/test/regrtest_data/bad_package/wrong.py b/pymode/libs/pylint/test/regrtest_data/bad_package/wrong.py new file mode 100644 index 00000000..2d2d78f9 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/bad_package/wrong.py @@ -0,0 +1,5 @@ +""" +Test that pylint doesn't crash when a relative import +depends on the local __init__, which contains an expected syntax error. +""" +from . import missing diff --git a/pymode/libs/pylint/test/regrtest_data/classdoc_usage.py b/pymode/libs/pylint/test/regrtest_data/classdoc_usage.py new file mode 100644 index 00000000..7c30f7e9 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/classdoc_usage.py @@ -0,0 +1,17 @@ +"""ds""" + +__revision__ = None + +class SomeClass(object): + """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/pymode/libs/pylint/test/regrtest_data/decimal_inference.py b/pymode/libs/pylint/test/regrtest_data/decimal_inference.py new file mode 100644 index 00000000..00c91304 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/decimal_inference.py @@ -0,0 +1,10 @@ +"""hum E1011 on .prec member is justifiable since Context instance are built +using setattr/locals :( + +2007/02/17 update: .prec attribute is now detected by astroid :o) +""" +from __future__ import print_function +import decimal + +decimal.getcontext().prec = 200 +print(decimal.getcontext().prec) diff --git a/pymode/libs/pylint/test/regrtest_data/descriptor_crash.py b/pymode/libs/pylint/test/regrtest_data/descriptor_crash.py new file mode 100644 index 00000000..4b3adccd --- /dev/null +++ b/pymode/libs/pylint/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/pymode/libs/pylint/test/regrtest_data/dummy/__init__.py b/pymode/libs/pylint/test/regrtest_data/dummy/__init__.py new file mode 100644 index 00000000..0b43a709 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/dummy/__init__.py @@ -0,0 +1,3 @@ +# pylint: disable=missing-docstring +from .dummy import DUMMY +from .another import ANOTHER diff --git a/pymode/libs/pylint/test/regrtest_data/dummy/another.py b/pymode/libs/pylint/test/regrtest_data/dummy/another.py new file mode 100644 index 00000000..046b4476 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/dummy/another.py @@ -0,0 +1,4 @@ +# pylint: disable=missing-docstring + +ANOTHER = 42 + diff --git a/pymode/libs/pylint/test/regrtest_data/dummy/dummy.py b/pymode/libs/pylint/test/regrtest_data/dummy/dummy.py new file mode 100644 index 00000000..60345dc1 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/dummy/dummy.py @@ -0,0 +1,2 @@ +# pylint: disable=missing-docstring +DUMMY = 42 diff --git a/pymode/libs/pylint/test/regrtest_data/html_crash_420.py b/pymode/libs/pylint/test/regrtest_data/html_crash_420.py new file mode 100644 index 00000000..a0edbb5f --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/html_crash_420.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +tag2struct = {u"#": "R_HEADER" + ,u"£": "RDR_HEADER" + ,u"µ": "RDR_DRAFT" + } \ No newline at end of file diff --git a/pymode/libs/pylint/test/regrtest_data/import_assign.py b/pymode/libs/pylint/test/regrtest_data/import_assign.py new file mode 100644 index 00000000..c01cd52f --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/import_assign.py @@ -0,0 +1,5 @@ +import shmixml.dom.minidom +import xml.dom.minidom + +if 'dom' not in xml.__dict__: + xml.dom = shmixml.dom diff --git a/pymode/libs/pylint/test/regrtest_data/import_package_subpackage_module.py b/pymode/libs/pylint/test/regrtest_data/import_package_subpackage_module.py new file mode 100644 index 00000000..2864e3c9 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/import_package_subpackage_module.py @@ -0,0 +1,49 @@ +# pylint: disable=I0011,C0301,W0611 +"""I found some of my scripts trigger off an AttributeError in pylint +0.8.1 (with common 0.12.0 and astroid 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 + astroid = self._check_file(filepath, modname, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 450, in _check_file + self.check_astroid_module(astroid, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 494, in check_astroid_module + self.astroid_events(astroid, [checker for checker in checkers + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events + self.astroid_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astroid_events + self.astroid_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 508, in astroid_events + checker.visit(astroid) + File "/usr/lib/python2.4/site-packages/logilab/astroid/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. +""" +import package.subpackage.module +__revision__ = '$Id: import_package_subpackage_module.py,v 1.1 2005-11-10 16:08:54 syt Exp $' diff --git a/pymode/libs/pylint/test/regrtest_data/import_something.py b/pymode/libs/pylint/test/regrtest_data/import_something.py new file mode 100644 index 00000000..3a74a717 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/import_something.py @@ -0,0 +1,4 @@ +# pylint: disable=missing-docstring,unused-import + +import os +import sys diff --git a/pymode/libs/pylint/test/regrtest_data/module_global.py b/pymode/libs/pylint/test/regrtest_data/module_global.py new file mode 100644 index 00000000..107d6528 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/module_global.py @@ -0,0 +1,7 @@ +# pylint: disable=W0603,W0601,W0604,E0602,W0104 +"""was causing infinite recursion +""" +__revision__ = 1 + +global GLOBAL_VAR +GLOBAL_VAR.foo diff --git a/pymode/libs/pylint/test/regrtest_data/no_stdout_encoding.py b/pymode/libs/pylint/test/regrtest_data/no_stdout_encoding.py new file mode 100644 index 00000000..d5c175b3 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/no_stdout_encoding.py @@ -0,0 +1,5 @@ +#-*- coding:iso-8859-1 -*- +class test: + def __init__ (self, dir): + testString = u"répertoire :\n%s !"%dir + diff --git a/pymode/libs/pylint/test/regrtest_data/numarray_import.py b/pymode/libs/pylint/test/regrtest_data/numarray_import.py new file mode 100644 index 00000000..e89757f1 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/numarray_import.py @@ -0,0 +1,7 @@ +"""#10077""" + +__revision__ = 1 + +from numarray import zeros + +zeros(shape=(4, 5)) diff --git a/pymode/libs/pylint/test/regrtest_data/numarray_inf.py b/pymode/libs/pylint/test/regrtest_data/numarray_inf.py new file mode 100644 index 00000000..4ea22a9c --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/numarray_inf.py @@ -0,0 +1,5 @@ +"""#3216""" + +import numarray as na +import numarray.random_array as nar +IM16 = nar.randint(0, 256, 300).astype(na.UInt8) diff --git a/pymode/libs/pylint/test/regrtest_data/package/AudioTime.py b/pymode/libs/pylint/test/regrtest_data/package/AudioTime.py new file mode 100644 index 00000000..a1fde965 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/package/AudioTime.py @@ -0,0 +1,3 @@ +"""test preceeded by the AudioTime class in __init__.py""" + +__revision__ = 0 diff --git a/pymode/libs/pylint/test/regrtest_data/package/__init__.py b/pymode/libs/pylint/test/regrtest_data/package/__init__.py new file mode 100644 index 00000000..d94c9fcc --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/package/__init__.py @@ -0,0 +1,15 @@ +# pylint: disable=R0903,W0403 +"""package's __init__ file""" + +from . import subpackage + +__revision__ = 0 + +# E0602 - Undefined variable '__path__' +__path__ += "folder" + +class AudioTime(object): + """test precedence over the AudioTime submodule""" + + DECIMAL = 3 + diff --git a/pymode/libs/pylint/test/regrtest_data/package/subpackage/__init__.py b/pymode/libs/pylint/test/regrtest_data/package/subpackage/__init__.py new file mode 100644 index 00000000..dc4782e6 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/package/subpackage/__init__.py @@ -0,0 +1 @@ +"""package.subpackage""" diff --git a/pymode/libs/pylint/test/regrtest_data/package/subpackage/module.py b/pymode/libs/pylint/test/regrtest_data/package/subpackage/module.py new file mode 100644 index 00000000..4b7244ba --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/package/subpackage/module.py @@ -0,0 +1 @@ +"""package.subpackage.module""" diff --git a/pymode/libs/pylint/test/regrtest_data/package_all/__init__.py b/pymode/libs/pylint/test/regrtest_data/package_all/__init__.py new file mode 100644 index 00000000..4e3696be --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/package_all/__init__.py @@ -0,0 +1,3 @@ +""" Check for E0603 for missing submodule found in __all__ """ +__revision__ = 1 +__all__ = ['notmissing', 'missing'] diff --git a/pymode/libs/pylint/test/regrtest_data/package_all/notmissing.py b/pymode/libs/pylint/test/regrtest_data/package_all/notmissing.py new file mode 100644 index 00000000..7cf85430 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/package_all/notmissing.py @@ -0,0 +1,2 @@ +""" empty """ +__revision__ = 1 diff --git a/pymode/libs/pylint/test/regrtest_data/precedence_test.py b/pymode/libs/pylint/test/regrtest_data/precedence_test.py new file mode 100644 index 00000000..ff5871d4 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/precedence_test.py @@ -0,0 +1,21 @@ +""" + # package/__init__.py + class AudioTime(object): + DECIMAL = 3 + + # package/AudioTime.py + class AudioTime(object): + pass + + # test.py + from package import AudioTime + # E0611 - No name 'DECIMAL' in module 'AudioTime.AudioTime' + print AudioTime.DECIMAL + +""" +from __future__ import print_function +from package import AudioTime +__revision__ = 0 + + +print(AudioTime.DECIMAL) diff --git a/pymode/libs/pylint/test/regrtest_data/py3k_error_flag.py b/pymode/libs/pylint/test/regrtest_data/py3k_error_flag.py new file mode 100644 index 00000000..c12d2502 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/py3k_error_flag.py @@ -0,0 +1,11 @@ +"""Contains both normal error messages and Python3 porting error messages.""" +# pylint: disable=bad-builtin, too-few-public-methods + +raise Exception, 1 # Error emitted here with the Python 3 checker. + + +class Test(object): + """dummy""" + + def __init__(self): + return 42 diff --git a/pymode/libs/pylint/test/regrtest_data/special_attr_scope_lookup_crash.py b/pymode/libs/pylint/test/regrtest_data/special_attr_scope_lookup_crash.py new file mode 100644 index 00000000..b693a9fc --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/special_attr_scope_lookup_crash.py @@ -0,0 +1,3 @@ +class Klass(object): + """A""" + __doc__ += "B" diff --git a/pymode/libs/pylint/test/regrtest_data/syntax_error.py b/pymode/libs/pylint/test/regrtest_data/syntax_error.py new file mode 100644 index 00000000..bf86aff0 --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/syntax_error.py @@ -0,0 +1 @@ +class A extends B {} \ No newline at end of file diff --git a/pymode/libs/pylint/test/regrtest_data/try_finally_disable_msg_crash.py b/pymode/libs/pylint/test/regrtest_data/try_finally_disable_msg_crash.py new file mode 100644 index 00000000..1719308f --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/try_finally_disable_msg_crash.py @@ -0,0 +1,5 @@ +try: + pass +finally: + # pylint: disable=W0201 + pass diff --git a/pymode/libs/pylint/test/regrtest_data/wrong_import_position.py b/pymode/libs/pylint/test/regrtest_data/wrong_import_position.py new file mode 100644 index 00000000..9e2d099e --- /dev/null +++ b/pymode/libs/pylint/test/regrtest_data/wrong_import_position.py @@ -0,0 +1,11 @@ +"""Test that wrong-import-position is properly reset when +other errors are disabled. +""" +# pylint: disable=unused-import, too-few-public-methods + + +class Something(object): + """A class before an import.""" + + +import os diff --git a/pymode/libs/pylint/test/test_func.py b/pymode/libs/pylint/test/test_func.py new file mode 100644 index 00000000..60330e4e --- /dev/null +++ b/pymode/libs/pylint/test/test_func.py @@ -0,0 +1,85 @@ +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""functional/non regression tests for pylint""" + +import unittest +import sys +import re + +from os import getcwd +from os.path import abspath, dirname, join + +from pylint.testutils import (make_tests, LintTestUsingModule, LintTestUsingFile, + LintTestUpdate, cb_test_gen, linter, test_reporter) + +PY3K = sys.version_info >= (3, 0) + +# Configure paths +INPUT_DIR = join(dirname(abspath(__file__)), 'input') +MSG_DIR = join(dirname(abspath(__file__)), 'messages') + +# Classes + +quote = "'" if sys.version_info >= (3, 3) else '' + +class LintTestNonExistentModuleTC(LintTestUsingModule): + module = 'nonexistent' + _get_expected = lambda self: 'F: 1: No module named %snonexistent%s\n' % (quote, quote) + + +def gen_tests(filter_rgx): + if UPDATE: + callbacks = [cb_test_gen(LintTestUpdate)] + else: + callbacks = [cb_test_gen(LintTestUsingModule)] + tests = make_tests(INPUT_DIR, MSG_DIR, filter_rgx, callbacks) + if UPDATE: + return tests + + if filter_rgx: + is_to_run = re.compile(filter_rgx).search + else: + is_to_run = lambda x: 1 + + if is_to_run('nonexistent'): + tests.append(LintTestNonExistentModuleTC) + + assert len(tests) < 196, "Please do not add new test cases here." + return tests + +# Create suite + +FILTER_RGX = None +UPDATE = False + +def suite(): + return unittest.TestSuite([unittest.makeSuite(test, suiteClass=unittest.TestSuite) + for test in gen_tests(FILTER_RGX)]) + + +def load_tests(loader, tests, pattern): + return suite() + + +if __name__=='__main__': + if '-u' in sys.argv: + UPDATE = True + sys.argv.remove('-u') + + if len(sys.argv) > 1: + FILTER_RGX = sys.argv[1] + del sys.argv[1] + unittest.main(defaultTest='suite') diff --git a/pymode/libs/pylint/test/test_functional.py b/pymode/libs/pylint/test/test_functional.py new file mode 100644 index 00000000..7da9e55a --- /dev/null +++ b/pymode/libs/pylint/test/test_functional.py @@ -0,0 +1,369 @@ +"""Functional full-module tests for PyLint.""" +import csv +import collections +import io +import operator +import os +import re +import sys +import platform +import unittest + +import six +from six.moves import configparser + +from pylint import checkers +from pylint import interfaces +from pylint import lint +from pylint import reporters +from pylint import utils + +class test_dialect(csv.excel): + if sys.version_info[0] < 3: + delimiter = b':' + lineterminator = b'\n' + else: + delimiter = ':' + lineterminator = '\n' + + +csv.register_dialect('test', test_dialect) + + +class NoFileError(Exception): + pass + +# Notes: +# - for the purpose of this test, the confidence levels HIGH and UNDEFINED +# are treated as the same. + +# TODOs +# - implement exhaustivity tests + +# If message files should be updated instead of checked. +UPDATE = False + +class OutputLine(collections.namedtuple('OutputLine', + ['symbol', 'lineno', 'object', 'msg', 'confidence'])): + @classmethod + def from_msg(cls, msg): + return cls( + msg.symbol, msg.line, msg.obj or '', msg.msg.replace("\r\n", "\n"), + msg.confidence.name + if msg.confidence != interfaces.UNDEFINED else interfaces.HIGH.name) + + @classmethod + def from_csv(cls, row): + confidence = row[4] if len(row) == 5 else interfaces.HIGH.name + return cls(row[0], int(row[1]), row[2], row[3], confidence) + + def to_csv(self): + if self.confidence == interfaces.HIGH.name: + return self[:-1] + else: + return self + + +# Common sub-expressions. +_MESSAGE = {'msg': r'[a-z][a-z\-]+'} +# Matches a #, +# - followed by a comparison operator and a Python version (optional), +# - followed by an line number with a +/- (optional), +# - followed by a list of bracketed message symbols. +# Used to extract expected messages from testdata files. +_EXPECTED_RE = re.compile( + r'\s*#\s*(?:(?P[+-]?[0-9]+):)?' + r'(?:(?P[><=]+) *(?P[0-9.]+):)?' + r'\s*\[(?P%(msg)s(?:,\s*%(msg)s)*)\]' % _MESSAGE) + + +def parse_python_version(str): + return tuple(int(digit) for digit in str.split('.')) + + +class TestReporter(reporters.BaseReporter): + def handle_message(self, msg): + self.messages.append(msg) + + def on_set_current_module(self, module, filepath): + self.messages = [] + + def display_reports(self, layout): + """Ignore layouts.""" + + +class TestFile(object): + """A single functional test case file with options.""" + + _CONVERTERS = { + 'min_pyver': parse_python_version, + 'max_pyver': parse_python_version, + 'requires': lambda s: s.split(',') + } + + + def __init__(self, directory, filename): + self._directory = directory + self.base = filename.replace('.py', '') + self.options = { + 'min_pyver': (2, 5), + 'max_pyver': (4, 0), + 'requires': [], + 'except_implementations': [], + } + self._parse_options() + + def _parse_options(self): + cp = configparser.ConfigParser() + cp.add_section('testoptions') + try: + cp.read(self.option_file) + except NoFileError: + pass + + for name, value in cp.items('testoptions'): + conv = self._CONVERTERS.get(name, lambda v: v) + self.options[name] = conv(value) + + @property + def option_file(self): + return self._file_type('.rc') + + @property + def module(self): + package = os.path.basename(self._directory) + return '.'.join([package, self.base]) + + @property + def expected_output(self): + return self._file_type('.txt', check_exists=False) + + @property + def source(self): + return self._file_type('.py') + + def _file_type(self, ext, check_exists=True): + name = os.path.join(self._directory, self.base + ext) + if not check_exists or os.path.exists(name): + return name + else: + raise NoFileError + + +_OPERATORS = { + '>': operator.gt, + '<': operator.lt, + '>=': operator.ge, + '<=': operator.le, +} + +def parse_expected_output(stream): + return [OutputLine.from_csv(row) for row in csv.reader(stream, 'test')] + + +def get_expected_messages(stream): + """Parses a file and get expected messages. + + :param stream: File-like input stream. + :returns: A dict mapping line,msg-symbol tuples to the count on this line. + """ + messages = collections.Counter() + for i, line in enumerate(stream): + match = _EXPECTED_RE.search(line) + if match is None: + continue + line = match.group('line') + if line is None: + line = i + 1 + elif line.startswith('+') or line.startswith('-'): + line = i + 1 + int(line) + else: + line = int(line) + + version = match.group('version') + op = match.group('op') + if version: + required = parse_python_version(version) + if not _OPERATORS[op](sys.version_info, required): + continue + + for msg_id in match.group('msgs').split(','): + messages[line, msg_id.strip()] += 1 + return messages + + +def multiset_difference(left_op, right_op): + """Takes two multisets and compares them. + + A multiset is a dict with the cardinality of the key as the value. + + :param left_op: The expected entries. + :param right_op: Actual entries. + + :returns: The two multisets of missing and unexpected messages. + """ + missing = left_op.copy() + missing.subtract(right_op) + unexpected = {} + for key, value in list(six.iteritems(missing)): + if value <= 0: + missing.pop(key) + if value < 0: + unexpected[key] = -value + return missing, unexpected + + +class LintModuleTest(unittest.TestCase): + maxDiff = None + + def __init__(self, test_file): + super(LintModuleTest, self).__init__('_runTest') + test_reporter = TestReporter() + self._linter = lint.PyLinter() + self._linter.set_reporter(test_reporter) + self._linter.config.persistent = 0 + checkers.initialize(self._linter) + self._linter.disable('I') + try: + self._linter.read_config_file(test_file.option_file) + self._linter.load_config_file() + except NoFileError: + pass + self._test_file = test_file + + def setUp(self): + if (sys.version_info < self._test_file.options['min_pyver'] + or sys.version_info >= self._test_file.options['max_pyver']): + self.skipTest( + 'Test cannot run with Python %s.' % (sys.version.split(' ')[0],)) + missing = [] + for req in self._test_file.options['requires']: + try: + __import__(req) + except ImportError: + missing.append(req) + if missing: + self.skipTest('Requires %s to be present.' % (','.join(missing),)) + if self._test_file.options['except_implementations']: + implementations = [ + item.strip() for item in + self._test_file.options['except_implementations'].split(",") + ] + implementation = platform.python_implementation() + if implementation in implementations: + self.skipTest( + 'Test cannot run with Python implementation %r' + % (implementation, )) + + def __str__(self): + return "%s (%s.%s)" % (self._test_file.base, self.__class__.__module__, + self.__class__.__name__) + + def _open_expected_file(self): + return open(self._test_file.expected_output) + + def _open_source_file(self): + if self._test_file.base == "invalid_encoded_data": + return open(self._test_file.source) + else: + return io.open(self._test_file.source, encoding="utf8") + + def _get_expected(self): + with self._open_source_file() as fobj: + expected_msgs = get_expected_messages(fobj) + + if expected_msgs: + with self._open_expected_file() as fobj: + expected_output_lines = parse_expected_output(fobj) + else: + expected_output_lines = [] + return expected_msgs, expected_output_lines + + def _get_received(self): + messages = self._linter.reporter.messages + messages.sort(key=lambda m: (m.line, m.symbol, m.msg)) + received_msgs = collections.Counter() + received_output_lines = [] + for msg in messages: + received_msgs[msg.line, msg.symbol] += 1 + received_output_lines.append(OutputLine.from_msg(msg)) + return received_msgs, received_output_lines + + def _runTest(self): + self._linter.check([self._test_file.module]) + + expected_messages, expected_text = self._get_expected() + received_messages, received_text = self._get_received() + + if expected_messages != received_messages: + msg = ['Wrong results for file "%s":' % (self._test_file.base)] + missing, unexpected = multiset_difference(expected_messages, + received_messages) + if missing: + msg.append('\nExpected in testdata:') + msg.extend(' %3d: %s' % msg for msg in sorted(missing)) + if unexpected: + msg.append('\nUnexpected in testdata:') + msg.extend(' %3d: %s' % msg for msg in sorted(unexpected)) + self.fail('\n'.join(msg)) + self._check_output_text(expected_messages, expected_text, received_text) + + def _split_lines(self, expected_messages, lines): + emitted, omitted = [], [] + for msg in lines: + if (msg[1], msg[0]) in expected_messages: + emitted.append(msg) + else: + omitted.append(msg) + return emitted, omitted + + def _check_output_text(self, expected_messages, expected_lines, + received_lines): + self.assertSequenceEqual( + self._split_lines(expected_messages, expected_lines)[0], + received_lines) + + +class LintModuleOutputUpdate(LintModuleTest): + def _open_expected_file(self): + try: + return super(LintModuleOutputUpdate, self)._open_expected_file() + except IOError: + return io.StringIO() + + def _check_output_text(self, expected_messages, expected_lines, + received_lines): + if not expected_messages: + return + emitted, remaining = self._split_lines(expected_messages, expected_lines) + if emitted != received_lines: + remaining.extend(received_lines) + remaining.sort(key=lambda m: (m[1], m[0], m[3])) + with open(self._test_file.expected_output, 'w') as fobj: + writer = csv.writer(fobj, dialect='test') + for line in remaining: + writer.writerow(line.to_csv()) + +def suite(): + input_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'functional') + suite = unittest.TestSuite() + for fname in os.listdir(input_dir): + if fname != '__init__.py' and fname.endswith('.py'): + test_file = TestFile(input_dir, fname) + if UPDATE: + suite.addTest(LintModuleOutputUpdate(test_file)) + else: + suite.addTest(LintModuleTest(test_file)) + return suite + + +def load_tests(loader, tests, pattern): + return suite() + + +if __name__=='__main__': + if '-u' in sys.argv: + UPDATE = True + sys.argv.remove('-u') + unittest.main(defaultTest='suite') diff --git a/pymode/libs/pylint/test/test_import_graph.py b/pymode/libs/pylint/test/test_import_graph.py new file mode 100644 index 00000000..edef0db1 --- /dev/null +++ b/pymode/libs/pylint/test/test_import_graph.py @@ -0,0 +1,68 @@ +import sys +import os +from os.path import exists +import unittest + +import six + +from pylint.checkers import initialize, imports +from pylint.lint import PyLinter + +from pylint.testutils import TestReporter + +class DependenciesGraphTC(unittest.TestCase): + """test the imports graph function""" + + dest = 'dependencies_graph.dot' + def tearDown(self): + os.remove(self.dest) + + def test_dependencies_graph(self): + imports._dependencies_graph(self.dest, {'labas': ['hoho', 'yep'], + 'hoho': ['yep']}) + with open(self.dest) as stream: + self.assertEqual(stream.read().strip(), + ''' +digraph "dependencies_graph" { +rankdir=LR +charset="utf-8" +URL="." node[shape="box"] +"hoho" []; +"yep" []; +"labas" []; +"yep" -> "hoho" []; +"hoho" -> "labas" []; +"yep" -> "labas" []; +} +'''.strip()) + +class ImportCheckerTC(unittest.TestCase): + def setUp(self): + self.linter = l = PyLinter(reporter=TestReporter()) + initialize(l) + + def test_checker_dep_graphs(self): + l = self.linter + l.global_set_option('persistent', False) + l.global_set_option('enable', 'imports') + l.global_set_option('import-graph', 'import.dot') + l.global_set_option('ext-import-graph', 'ext_import.dot') + l.global_set_option('int-import-graph', 'int_import.dot') + l.global_set_option('int-import-graph', 'int_import.dot') + # ignore this file causing spurious MemoryError w/ some python version (>=2.3?) + l.global_set_option('ignore', ('func_unknown_encoding.py',)) + try: + l.check('input') + l.generate_reports() + self.assertTrue(exists('import.dot')) + self.assertTrue(exists('ext_import.dot')) + self.assertTrue(exists('int_import.dot')) + finally: + for fname in ('import.dot', 'ext_import.dot', 'int_import.dot'): + try: + os.remove(fname) + except: + pass + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/test_regr.py b/pymode/libs/pylint/test/test_regr.py new file mode 100644 index 00000000..94085c34 --- /dev/null +++ b/pymode/libs/pylint/test/test_regr.py @@ -0,0 +1,149 @@ +# Copyright (c) 2005-2014 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""non regression tests for pylint, which requires a too specific configuration +to be incorporated in the automatic functional test framework +""" + +import sys +import platform +import os +from os.path import abspath, dirname, join +import unittest + +from pylint.testutils import TestReporter +from pylint.lint import PyLinter +from pylint import checkers + +test_reporter = TestReporter() +linter = PyLinter() +linter.set_reporter(test_reporter) +linter.disable('I') +linter.config.persistent = 0 +checkers.initialize(linter) + +REGR_DATA = join(dirname(abspath(__file__)), 'regrtest_data') +sys.path.insert(1, REGR_DATA) + +class NonRegrTC(unittest.TestCase): + def setUp(self): + """call reporter.finalize() to cleanup + pending messages if a test finished badly + """ + linter.reporter.finalize() + + def test_package___path___manipulation(self): + linter.check('package.__init__') + got = linter.reporter.finalize().strip() + self.assertEqual(got, '') + + def test_package___init___precedence(self): + linter.check('precedence_test') + got = linter.reporter.finalize().strip() + self.assertEqual(got, '') + + def test_check_package___init__(self): + for variation in ('package.__init__', join(REGR_DATA, 'package', '__init__.py')): + linter.check(variation) + got = linter.reporter.finalize().strip() + checked = list(linter.stats['by_module'].keys()) + self.assertEqual(checked, ['package.__init__'], + '%s: %s' % (variation, checked)) + cwd = os.getcwd() + os.chdir(join(REGR_DATA, 'package')) + sys.path.insert(0, '') + try: + for variation in ('__init__', '__init__.py'): + linter.check(variation) + got = linter.reporter.finalize().strip() + checked = list(linter.stats['by_module'].keys()) + self.assertEqual(checked, ['__init__'], + '%s: %s' % (variation, checked)) + finally: + sys.path.pop(0) + os.chdir(cwd) + + def test_numarray_inference(self): + try: + from numarray import random_array + except ImportError: + self.skipTest('test skipped: numarray.random_array is not available') + linter.check(join(REGR_DATA, 'numarray_inf.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, "E: 5: Instance of 'int' has no 'astype' member (but some types could not be inferred)") + + def test_numarray_import(self): + try: + import numarray + except ImportError: + self.skipTest('test skipped: numarray is not available') + linter.check(join(REGR_DATA, 'numarray_import.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, '') + + def test_class__doc__usage(self): + linter.check(join(REGR_DATA, 'classdoc_usage.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, '') + + def test_package_import_relative_subpackage_no_attribute_error(self): + linter.check('import_package_subpackage_module') + got = linter.reporter.finalize().strip() + self.assertEqual(got, '') + + def test_import_assign_crash(self): + linter.check(join(REGR_DATA, 'import_assign.py')) + + def test_special_attr_scope_lookup_crash(self): + linter.check(join(REGR_DATA, 'special_attr_scope_lookup_crash.py')) + + def test_module_global_crash(self): + linter.check(join(REGR_DATA, 'module_global.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, '') + + def test_decimal_inference(self): + linter.check(join(REGR_DATA, 'decimal_inference.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, "") + + def test_descriptor_crash(self): + for fname in os.listdir(REGR_DATA): + if fname.endswith('_crash.py'): + linter.check(join(REGR_DATA, fname)) + linter.reporter.finalize().strip() + + def test_try_finally_disable_msg_crash(self): + linter.check(join(REGR_DATA, 'try_finally_disable_msg_crash')) + + def test___path__(self): + linter.check('pylint.checkers.__init__') + messages = linter.reporter.finalize().strip() + self.assertFalse('__path__' in messages, messages) + + def test_absolute_import(self): + linter.check(join(REGR_DATA, 'absimp', 'string.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, "") + + def test_no_context_file(self): + expected = "Unused import missing" + linter.check(join(REGR_DATA, 'bad_package')) + got = linter.reporter.finalize().strip() + self.assertIn(expected, got) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/test_self.py b/pymode/libs/pylint/test/test_self.py new file mode 100644 index 00000000..9a23cc03 --- /dev/null +++ b/pymode/libs/pylint/test/test_self.py @@ -0,0 +1,352 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. + +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import contextlib +import json +import re +import sys +import os +from os.path import join, dirname, abspath +import tempfile +import textwrap +import unittest + +import six + +from pylint.lint import Run +from pylint import __pkginfo__ +from pylint.reporters import BaseReporter +from pylint.reporters.text import * +from pylint.reporters.html import HTMLReporter +from pylint.reporters.json import JSONReporter + +HERE = abspath(dirname(__file__)) + + + +@contextlib.contextmanager +def _patch_streams(out): + sys.stderr = sys.stdout = out + try: + yield + finally: + sys.stderr = sys.__stderr__ + sys.stdout = sys.__stdout__ + + +class MultiReporter(BaseReporter): + def __init__(self, reporters): + self._reporters = reporters + self.path_strip_prefix = os.getcwd() + os.sep + + def on_set_current_module(self, *args, **kwargs): + for rep in self._reporters: + rep.on_set_current_module(*args, **kwargs) + + def handle_message(self, msg): + for rep in self._reporters: + rep.handle_message(msg) + + def display_reports(self, layout): + pass + + @property + def out(self): + return self._reporters[0].out + + @property + def linter(self): + return self._linter + + @linter.setter + def linter(self, value): + self._linter = value + for rep in self._reporters: + rep.linter = value + + +class RunTC(unittest.TestCase): + + def _runtest(self, args, reporter=None, out=None, code=28): + if out is None: + out = six.StringIO() + pylint_code = self._run_pylint(args, reporter=reporter, out=out) + if reporter: + output = reporter.out.getvalue() + elif hasattr(out, 'getvalue'): + output = out.getvalue() + else: + output = None + msg = 'expected output status %s, got %s' % (code, pylint_code) + if output is not None: + msg = '%s. Below pylint output: \n%s' % (msg, output) + self.assertEqual(pylint_code, code, msg) + + def _run_pylint(self, args, out, reporter=None): + args = args + ['--persistent=no'] + with _patch_streams(out): + with self.assertRaises(SystemExit) as cm: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + Run(args, reporter=reporter) + return cm.exception.code + + def _test_output(self, args, expected_output): + out = six.StringIO() + self._run_pylint(args, out=out) + actual_output = out.getvalue() + self.assertIn(expected_output.strip(), actual_output.strip()) + + def test_pkginfo(self): + """Make pylint check itself.""" + self._runtest(['pylint.__pkginfo__'], reporter=TextReporter(six.StringIO()), + code=0) + + def test_all(self): + """Make pylint check itself.""" + reporters = [ + TextReporter(six.StringIO()), + HTMLReporter(six.StringIO()), + ColorizedTextReporter(six.StringIO()), + JSONReporter(six.StringIO()) + ] + self._runtest(['pylint/test/functional/arguments.py'], + reporter=MultiReporter(reporters), code=1) + + def test_no_ext_file(self): + self._runtest([join(HERE, 'input', 'noext')], code=0) + + def test_w0704_ignored(self): + self._runtest([join(HERE, 'input', 'ignore_except_pass_by_default.py')], code=0) + + def test_generate_config_option(self): + self._runtest(['--generate-rcfile'], code=0) + + def test_generate_config_disable_symbolic_names(self): + # Test that --generate-rcfile puts symbolic names in the --disable + # option. + + out = six.StringIO() + self._run_pylint(["--generate-rcfile", "--rcfile="], out=out) + + output = out.getvalue() + # Get rid of the pesky messages that pylint emits if the + # configuration file is not found. + master = re.search("\[MASTER", output) + out = six.StringIO(output[master.start():]) + parser = six.moves.configparser.RawConfigParser() + parser.readfp(out) + messages = parser.get('MESSAGES CONTROL', 'disable').split(",") + self.assertIn('suppressed-message', messages) + + def test_generate_rcfile_no_obsolete_methods(self): + out = six.StringIO() + self._run_pylint(["--generate-rcfile"], out=out) + output = out.getvalue() + self.assertNotIn("profile", output) + + def _test_deprecated_options(self, option, expected): + out = six.StringIO() + self._run_pylint([option, "--rcfile=", "pylint.config"], out=out) + output = out.getvalue() + if __pkginfo__.numversion >= (1, 6, 0): + self.assertIn("no such option", output) + else: + self.assertIn(expected, output) + + def test_deprecated_options_zope(self): + expected = ("option --zope is obsolete and it is " + "slated for removal in Pylint 1.6") + self._test_deprecated_options("--zope=y", expected) + + def test_deprecated_options_symbols(self): + expected = ("option --symbols is obsolete and it is " + "slated for removal in Pylint 1.6") + self._test_deprecated_options("--symbols=y", expected) + + def test_deprecated_options_include_ids(self): + expected = ("option --include-ids is obsolete and it is " + "slated for removal in Pylint 1.6") + self._test_deprecated_options("--include-ids=y", expected) + + def test_deprecated_options_profile(self): + expected = ("option --profile is obsolete and it is " + "slated for removal in Pylint 1.6") + self._test_deprecated_options("--profile=y", expected) + + def test_help_message_option(self): + self._runtest(['--help-msg', 'W0101'], code=0) + + def test_error_help_message_option(self): + self._runtest(['--help-msg', 'WX101'], code=0) + + def test_error_missing_arguments(self): + self._runtest([], code=32) + + def test_no_out_encoding(self): + """test redirection of stdout with non ascii caracters + """ + #This test reproduces bug #48066 ; it happens when stdout is redirected + # through '>' : the sys.stdout.encoding becomes then None, and if the + # output contains non ascii, pylint will crash + if sys.version_info < (3, 0): + strio = tempfile.TemporaryFile() + else: + strio = six.StringIO() + assert strio.encoding is None + self._runtest([join(HERE, 'regrtest_data/no_stdout_encoding.py')], + out=strio) + + def test_parallel_execution(self): + self._runtest(['-j 2', 'pylint/test/functional/arguments.py', + 'pylint/test/functional/bad_continuation.py'], code=1) + + def test_parallel_execution_missing_arguments(self): + self._runtest(['-j 2', 'not_here', 'not_here_too'], code=1) + + def test_py3k_option(self): + # Test that --py3k flag works. + rc_code = 2 if six.PY2 else 0 + self._runtest([join(HERE, 'functional', 'unpacked_exceptions.py'), + '--py3k'], + code=rc_code) + + def test_py3k_jobs_option(self): + rc_code = 2 if six.PY2 else 0 + self._runtest([join(HERE, 'functional', 'unpacked_exceptions.py'), + '--py3k', '-j 2'], + code=rc_code) + + @unittest.skipIf(sys.version_info[0] > 2, "Requires the --py3k flag.") + def test_py3k_commutative_with_errors_only(self): + + # Test what gets emitted with -E only + module = join(HERE, 'regrtest_data', 'py3k_error_flag.py') + expected = textwrap.dedent(""" + No config file found, using default configuration + ************* Module py3k_error_flag + Explicit return in __init__ + """) + self._test_output([module, "-E", "--msg-template='{msg}'"], + expected_output=expected) + + # Test what gets emitted with -E --py3k + expected = textwrap.dedent(""" + No config file found, using default configuration + ************* Module py3k_error_flag + Use raise ErrorClass(args) instead of raise ErrorClass, args. + """) + self._test_output([module, "-E", "--py3k", "--msg-template='{msg}'"], + expected_output=expected) + + # Test what gets emitted with --py3k -E + self._test_output([module, "--py3k", "-E", "--msg-template='{msg}'"], + expected_output=expected) + + def test_abbreviations_are_not_supported(self): + expected = "no such option: --load-plugin" + self._test_output([".", "--load-plugin"], expected_output=expected) + + def test_enable_all_works(self): + module = join(HERE, 'data', 'clientmodule_test.py') + expected = textwrap.dedent(""" + No config file found, using default configuration + ************* Module data.clientmodule_test + W: 10, 8: Unused variable 'local_variable' (unused-variable) + C: 18, 4: Missing method docstring (missing-docstring) + C: 22, 0: Missing class docstring (missing-docstring) + """) + self._test_output([module, "--disable=all", "--enable=all", "-rn"], + expected_output=expected) + + def test_html_crash_report(self): + out = six.StringIO() + module = join(HERE, 'regrtest_data', 'html_crash_420.py') + self._runtest([module], code=16, reporter=HTMLReporter(out)) + + def test_wrong_import_position_when_others_disabled(self): + expected_output = textwrap.dedent(''' + No config file found, using default configuration + ************* Module wrong_import_position + C: 11, 0: Import "import os" should be placed at the top of the module (wrong-import-position) + ''') + module1 = join(HERE, 'regrtest_data', 'import_something.py') + module2 = join(HERE, 'regrtest_data', 'wrong_import_position.py') + args = [module2, module1, + "--disable=all", "--enable=wrong-import-position", + "-rn"] + out = six.StringIO() + self._run_pylint(args, out=out) + actual_output = out.getvalue() + self.assertEqual(expected_output.strip(), actual_output.strip()) + + def test_import_itself_not_accounted_for_relative_imports(self): + expected = 'No config file found, using default configuration' + package = join(HERE, 'regrtest_data', 'dummy') + self._test_output([package, '--disable=locally-disabled', '-rn'], + expected_output=expected) + + + def test_json_report_when_file_has_syntax_error(self): + out = six.StringIO() + module = join(HERE, 'regrtest_data', 'syntax_error.py') + self._runtest([module], code=2, reporter=JSONReporter(out)) + output = json.loads(out.getvalue()) + self.assertIsInstance(output, list) + self.assertEqual(len(output), 1) + self.assertIsInstance(output[0], dict) + expected = { + "obj": "", + "column": 0, + "line": 1, + "type": "error", + "symbol": "syntax-error", + "module": "syntax_error" + } + message = output[0] + for key, value in expected.items(): + self.assertIn(key, message) + self.assertEqual(message[key], value) + self.assertIn("invalid syntax", message["message"].lower()) + + def test_json_report_when_file_is_missing(self): + out = six.StringIO() + module = join(HERE, 'regrtest_data', 'totally_missing.py') + self._runtest([module], code=1, reporter=JSONReporter(out)) + output = json.loads(out.getvalue()) + self.assertIsInstance(output, list) + self.assertEqual(len(output), 1) + self.assertIsInstance(output[0], dict) + expected = { + "obj": "", + "column": 0, + "line": 1, + "type": "fatal", + "symbol": "fatal", + "module": module + } + message = output[0] + for key, value in expected.items(): + self.assertIn(key, message) + self.assertEqual(message[key], value) + self.assertTrue(message['message'].startswith("No module named")) + + def test_confidence_levels(self): + expected = 'No config file found, using default configuration' + path = join(HERE, 'regrtest_data', 'meta.py') + self._test_output([path, "--confidence=HIGH,INFERENCE"], + expected_output=expected) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_checker_base.py b/pymode/libs/pylint/test/unittest_checker_base.py new file mode 100644 index 00000000..9901765c --- /dev/null +++ b/pymode/libs/pylint/test/unittest_checker_base.py @@ -0,0 +1,305 @@ +"""Unittest for the base checker.""" + +import re +import unittest + +import astroid +from astroid import test_utils +from pylint.checkers import base +from pylint.testutils import CheckerTestCase, Message, set_config + + +class DocstringTest(CheckerTestCase): + CHECKER_CLASS = base.DocStringChecker + + def test_missing_docstring_module(self): + module = astroid.parse("something") + message = Message('missing-docstring', node=module, args=('module',)) + with self.assertAddsMessages(message): + self.checker.visit_module(module) + + def test_missing_docstring_emtpy_module(self): + module = astroid.parse("") + with self.assertNoMessages(): + self.checker.visit_module(module) + + def test_empty_docstring_module(self): + module = astroid.parse("''''''") + message = Message('empty-docstring', node=module, args=('module',)) + with self.assertAddsMessages(message): + self.checker.visit_module(module) + + def test_empty_docstring_function(self): + func = test_utils.extract_node(""" + def func(tion): + pass""") + message = Message('missing-docstring', node=func, args=('function',)) + with self.assertAddsMessages(message): + self.checker.visit_functiondef(func) + + @set_config(docstring_min_length=2) + def test_short_function_no_docstring(self): + func = test_utils.extract_node(""" + def func(tion): + pass""") + with self.assertNoMessages(): + self.checker.visit_functiondef(func) + + @set_config(docstring_min_length=2) + def test_function_no_docstring_by_name(self): + func = test_utils.extract_node(""" + def __fun__(tion): + pass""") + with self.assertNoMessages(): + self.checker.visit_functiondef(func) + + def test_class_no_docstring(self): + klass = test_utils.extract_node(""" + class Klass(object): + pass""") + message = Message('missing-docstring', node=klass, args=('class',)) + with self.assertAddsMessages(message): + self.checker.visit_classdef(klass) + + +class NameCheckerTest(CheckerTestCase): + CHECKER_CLASS = base.NameChecker + CONFIG = { + 'bad_names': set(), + } + + @set_config(include_naming_hint=True) + def test_naming_hint(self): + const = test_utils.extract_node(""" + const = "CONSTANT" #@ + """) + message = Message( + 'invalid-name', node=const.targets[0], + args=('constant', 'const', + ' (hint: (([A-Z_][A-Z0-9_]*)|(__.*__))$)')) + with self.assertAddsMessages(message): + self.checker.visit_assignname(const.targets[0]) + + @set_config(include_naming_hint=True, const_name_hint='CONSTANT') + def test_naming_hint_configured_hint(self): + const = test_utils.extract_node(""" + const = "CONSTANT" #@ + """) + with self.assertAddsMessages( + Message('invalid-name', node=const.targets[0], + args=('constant', 'const', ' (hint: CONSTANT)'))): + self.checker.visit_assignname(const.targets[0]) + + @set_config(attr_rgx=re.compile('[A-Z]+')) + def test_property_names(self): + # If a method is annotated with @property, it's name should + # match the attr regex. Since by default the attribute regex is the same + # as the method regex, we override it here. + methods = test_utils.extract_node(""" + import abc + + class FooClass(object): + @property + def FOO(self): #@ + pass + + @property + def bar(self): #@ + pass + + @abc.abstractproperty + def BAZ(self): #@ + pass + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(methods[0]) + self.checker.visit_functiondef(methods[2]) + with self.assertAddsMessages(Message('invalid-name', node=methods[1], + args=('attribute', 'bar', ''))): + self.checker.visit_functiondef(methods[1]) + + @set_config(attr_rgx=re.compile('[A-Z]+')) + def test_property_setters(self): + method = test_utils.extract_node(""" + class FooClass(object): + @property + def foo(self): pass + + @foo.setter + def FOOSETTER(self): #@ + pass + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(method) + + def test_module_level_names(self): + assign = test_utils.extract_node(""" + import collections + Class = collections.namedtuple("a", ("b", "c")) #@ + """) + with self.assertNoMessages(): + self.checker.visit_assignname(assign.targets[0]) + + assign = test_utils.extract_node(""" + class ClassA(object): + pass + ClassB = ClassA + """) + with self.assertNoMessages(): + self.checker.visit_assignname(assign.targets[0]) + + module = astroid.parse(""" + def A(): + return 1, 2, 3 + CONSTA, CONSTB, CONSTC = A() + CONSTD = A()""") + with self.assertNoMessages(): + self.checker.visit_assignname(module.body[1].targets[0].elts[0]) + self.checker.visit_assignname(module.body[2].targets[0]) + + assign = test_utils.extract_node(""" + CONST = "12 34 ".rstrip().split()""") + with self.assertNoMessages(): + self.checker.visit_assignname(assign.targets[0]) + + +class MultiNamingStyleTest(CheckerTestCase): + CHECKER_CLASS = base.NameChecker + + MULTI_STYLE_RE = re.compile('(?:(?P[A-Z]+)|(?P[a-z]+))$') + + @set_config(class_rgx=MULTI_STYLE_RE) + def test_multi_name_detection_majority(self): + classes = test_utils.extract_node(""" + class classb(object): #@ + pass + class CLASSA(object): #@ + pass + class CLASSC(object): #@ + pass + """) + message = Message('invalid-name', + node=classes[0], + args=('class', 'classb', '')) + with self.assertAddsMessages(message): + for cls in classes: + self.checker.visit_classdef(cls) + self.checker.leave_module(cls.root) + + @set_config(class_rgx=MULTI_STYLE_RE) + def test_multi_name_detection_first_invalid(self): + classes = test_utils.extract_node(""" + class class_a(object): #@ + pass + class classb(object): #@ + pass + class CLASSC(object): #@ + pass + """) + messages = [ + Message('invalid-name', node=classes[0], + args=('class', 'class_a', '')), + Message('invalid-name', node=classes[2], + args=('class', 'CLASSC', '')) + ] + with self.assertAddsMessages(*messages): + for cls in classes: + self.checker.visit_classdef(cls) + self.checker.leave_module(cls.root) + + @set_config(method_rgx=MULTI_STYLE_RE, + function_rgx=MULTI_STYLE_RE, + name_group=('function:method',)) + def test_multi_name_detection_group(self): + function_defs = test_utils.extract_node(""" + class First(object): + def func(self): #@ + pass + + def FUNC(): #@ + pass + """, module_name='test') + message = Message('invalid-name', node=function_defs[1], + args=('function', 'FUNC', '')) + with self.assertAddsMessages(message): + for func in function_defs: + self.checker.visit_functiondef(func) + self.checker.leave_module(func.root) + + @set_config(function_rgx=re.compile('(?:(?PFOO)|(?P[A-Z]+)|(?P[a-z]+))$')) + def test_multi_name_detection_exempt(self): + function_defs = test_utils.extract_node(""" + def FOO(): #@ + pass + def lower(): #@ + pass + def FOO(): #@ + pass + def UPPER(): #@ + pass + """) + message = Message('invalid-name', node=function_defs[3], + args=('function', 'UPPER', '')) + with self.assertAddsMessages(message): + for func in function_defs: + self.checker.visit_functiondef(func) + self.checker.leave_module(func.root) + +class ComparisonTest(CheckerTestCase): + CHECKER_CLASS = base.ComparisonChecker + + def test_comparison(self): + node = test_utils.extract_node("foo == True") + message = Message('singleton-comparison', + node=node, + args=(True, "just 'expr' or 'expr is True'")) + with self.assertAddsMessages(message): + self.checker.visit_compare(node) + + node = test_utils.extract_node("foo == False") + message = Message('singleton-comparison', + node=node, + args=(False, "'not expr' or 'expr is False'")) + with self.assertAddsMessages(message): + self.checker.visit_compare(node) + + node = test_utils.extract_node("foo == None") + message = Message('singleton-comparison', + node=node, + args=(None, "'expr is None'")) + with self.assertAddsMessages(message): + self.checker.visit_compare(node) + + node = test_utils.extract_node("True == foo") + messages = (Message('misplaced-comparison-constant', + node=node, + args=('foo == True',)), + Message('singleton-comparison', + node=node, + args=(True, "just 'expr' or 'expr is True'"))) + with self.assertAddsMessages(*messages): + self.checker.visit_compare(node) + + node = test_utils.extract_node("False == foo") + messages = (Message('misplaced-comparison-constant', + node=node, + args=('foo == False',)), + Message('singleton-comparison', + node=node, + args=(False, "'not expr' or 'expr is False'"))) + with self.assertAddsMessages(*messages): + self.checker.visit_compare(node) + + node = test_utils.extract_node("None == foo") + messages = (Message('misplaced-comparison-constant', + node=node, + args=('foo == None',)), + Message('singleton-comparison', + node=node, + args=(None, "'expr is None'"))) + with self.assertAddsMessages(*messages): + self.checker.visit_compare(node) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_checker_classes.py b/pymode/libs/pylint/test/unittest_checker_classes.py new file mode 100644 index 00000000..ae1b919f --- /dev/null +++ b/pymode/libs/pylint/test/unittest_checker_classes.py @@ -0,0 +1,84 @@ +"""Unit tests for the variables checker.""" +import unittest +import sys + +import astroid +from astroid import test_utils +from pylint.checkers import classes +from pylint.testutils import CheckerTestCase, Message, set_config + +class VariablesCheckerTC(CheckerTestCase): + + CHECKER_CLASS = classes.ClassChecker + + def test_bitbucket_issue_164(self): + """Issue 164 report a false negative for access-member-before-definition""" + n1, n2 = test_utils.extract_node(""" + class MyClass1(object): + def __init__(self): + self.first += 5 #@ + self.first = 0 #@ + """) + message = Message('access-member-before-definition', + node=n1.target, args=('first', n2.lineno)) + with self.assertAddsMessages(message): + self.walk(n1.root()) + + @set_config(exclude_protected=('_meta', '_manager')) + def test_exclude_protected(self): + """Test that exclude-protected can be used to + exclude names from protected-access warning. + """ + + node = astroid.parse(""" + class Protected(object): + '''empty''' + def __init__(self): + self._meta = 42 + self._manager = 24 + self._teta = 29 + OBJ = Protected() + OBJ._meta + OBJ._manager + OBJ._teta + """) + with self.assertAddsMessages( + Message('protected-access', + node=node.body[-1].value, + args='_teta')): + self.walk(node.root()) + + @unittest.skipUnless(sys.version_info[0] == 3, + "The test works on Python 3.") + def test_regression_non_parent_init_called_tracemalloc(self): + # This used to raise a non-parent-init-called on Pylint 1.3 + # See issue https://bitbucket.org/logilab/pylint/issue/308/ + # for reference. + node = test_utils.extract_node(""" + from tracemalloc import Sequence + class _Traces(Sequence): + def __init__(self, traces): #@ + Sequence.__init__(self) + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + + def test_super_init_not_called_regression(self): + # This should not emit a super-init-not-called + # warning. It previously did this, because + # ``next(node.infer())`` was used in that checker's + # logic and the first inferred node was an YES object, + # leading to this false positive. + node = test_utils.extract_node(""" + import ctypes + + class Foo(ctypes.BigEndianStructure): + def __init__(self): #@ + ctypes.BigEndianStructure.__init__(self) + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_checker_exceptions.py b/pymode/libs/pylint/test/unittest_checker_exceptions.py new file mode 100644 index 00000000..29036914 --- /dev/null +++ b/pymode/libs/pylint/test/unittest_checker_exceptions.py @@ -0,0 +1,69 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Tests for pylint.checkers.exceptions.""" + +import sys +import unittest + +from astroid import test_utils +from pylint.checkers import exceptions +from pylint.testutils import CheckerTestCase, Message + + +class ExceptionsCheckerTest(CheckerTestCase): + """Tests for pylint.checkers.exceptions.""" + + CHECKER_CLASS = exceptions.ExceptionsChecker + + # These tests aren't in the functional test suite, + # since they will be converted with 2to3 for Python 3 + # and `raise (Error, ...)` will be converted to + # `raise Error(...)`, so it beats the purpose of the test. + + @unittest.skipUnless(sys.version_info[0] == 3, + "The test should emit an error on Python 3.") + def test_raising_bad_type_python3(self): + node = test_utils.extract_node('raise (ZeroDivisionError, None) #@') + message = Message('raising-bad-type', node=node, args='tuple') + with self.assertAddsMessages(message): + self.checker.visit_raise(node) + + @unittest.skipUnless(sys.version_info[0] == 2, + "The test is valid only on Python 2.") + def test_raising_bad_type_python2(self): + nodes = test_utils.extract_node(''' + raise (ZeroDivisionError, None) #@ + from something import something + raise (something, None) #@ + + raise (4, None) #@ + raise () #@ + ''') + with self.assertNoMessages(): + self.checker.visit_raise(nodes[0]) + with self.assertNoMessages(): + self.checker.visit_raise(nodes[1]) + + message = Message('raising-bad-type', node=nodes[2], args='tuple') + with self.assertAddsMessages(message): + self.checker.visit_raise(nodes[2]) + message = Message('raising-bad-type', node=nodes[3], args='tuple') + with self.assertAddsMessages(message): + self.checker.visit_raise(nodes[3]) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_checker_format.py b/pymode/libs/pylint/test/unittest_checker_format.py new file mode 100644 index 00000000..0d2fbe2a --- /dev/null +++ b/pymode/libs/pylint/test/unittest_checker_format.py @@ -0,0 +1,262 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" Copyright (c) 2000-2011 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Check format checker helper functions +""" + +from __future__ import unicode_literals + +import astroid +from astroid import test_utils + +from pylint.checkers.format import * + +from pylint.testutils import ( + CheckerTestCase, Message, set_config, tokenize_str, +) + + +class MultiStatementLineTest(CheckerTestCase): + CHECKER_CLASS = FormatChecker + + def testSingleLineIfStmts(self): + stmt = test_utils.extract_node(""" + if True: pass #@ + """) + with self.assertAddsMessages(Message('multiple-statements', node=stmt.body[0])): + self.checker.process_tokens([]) + self.checker.visit_default(stmt.body[0]) + self.checker.config.single_line_if_stmt = True + with self.assertNoMessages(): + self.checker.process_tokens([]) + self.checker.visit_default(stmt.body[0]) + stmt = test_utils.extract_node(""" + if True: pass #@ + else: + pass + """) + with self.assertAddsMessages(Message('multiple-statements', node=stmt.body[0])): + self.checker.process_tokens([]) + self.checker.visit_default(stmt.body[0]) + + def testTryExceptFinallyNoMultipleStatement(self): + tree = test_utils.extract_node(""" + try: #@ + pass + except: + pass + finally: + pass""") + with self.assertNoMessages(): + self.checker.process_tokens([]) + self.checker.visit_default(tree.body[0]) + + + +class SuperfluousParenthesesTest(CheckerTestCase): + CHECKER_CLASS = FormatChecker + + def testCheckKeywordParensHandlesValidCases(self): + self.checker._keywords_with_parens = set() + cases = [ + 'if foo:', + 'if foo():', + 'if (x and y) or z:', + 'assert foo()', + 'assert ()', + 'if (1, 2) in (3, 4):', + 'if (a or b) in c:', + 'return (x for x in x)', + 'if (x for x in x):', + 'for x in (x for x in x):', + 'not (foo or bar)', + 'not (foo or bar) and baz', + ] + with self.assertNoMessages(): + for code in cases: + self.checker._check_keyword_parentheses(tokenize_str(code), 0) + + def testCheckKeywordParensHandlesUnnecessaryParens(self): + self.checker._keywords_with_parens = set() + cases = [ + (Message('superfluous-parens', line=1, args='if'), + 'if (foo):', 0), + (Message('superfluous-parens', line=1, args='if'), + 'if ((foo, bar)):', 0), + (Message('superfluous-parens', line=1, args='if'), + 'if (foo(bar)):', 0), + (Message('superfluous-parens', line=1, args='return'), + 'return ((x for x in x))', 0), + (Message('superfluous-parens', line=1, args='not'), + 'not (foo)', 0), + (Message('superfluous-parens', line=1, args='not'), + 'if not (foo):', 1), + (Message('superfluous-parens', line=1, args='if'), + 'if (not (foo)):', 0), + (Message('superfluous-parens', line=1, args='not'), + 'if (not (foo)):', 2), + ] + for msg, code, offset in cases: + with self.assertAddsMessages(msg): + self.checker._check_keyword_parentheses(tokenize_str(code), offset) + + def testFuturePrintStatementWithoutParensWarning(self): + code = """from __future__ import print_function +print('Hello world!') +""" + tree = astroid.parse(code) + with self.assertNoMessages(): + self.checker.process_module(tree) + self.checker.process_tokens(tokenize_str(code)) + + +class CheckSpaceTest(CheckerTestCase): + CHECKER_CLASS = FormatChecker + + def testParenthesesGood(self): + good_cases = [ + '(a)\n', + '(a * (b + c))\n', + '(#\n a)\n', + ] + with self.assertNoMessages(): + for code in good_cases: + self.checker.process_tokens(tokenize_str(code)) + + def testParenthesesBad(self): + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'after', 'bracket', '( a)\n^'))): + self.checker.process_tokens(tokenize_str('( a)\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'bracket', '(a )\n ^'))): + self.checker.process_tokens(tokenize_str('(a )\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'bracket', 'foo (a)\n ^'))): + self.checker.process_tokens(tokenize_str('foo (a)\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'bracket', '{1: 2} [1]\n ^'))): + self.checker.process_tokens(tokenize_str('{1: 2} [1]\n')) + + def testTrailingCommaGood(self): + with self.assertNoMessages(): + self.checker.process_tokens(tokenize_str('(a, )\n')) + self.checker.process_tokens(tokenize_str('(a,)\n')) + + self.checker.config.no_space_check = [] + with self.assertNoMessages(): + self.checker.process_tokens(tokenize_str('(a,)\n')) + + @set_config(no_space_check=[]) + def testTrailingCommaBad(self): + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'bracket', '(a, )\n ^'))): + self.checker.process_tokens(tokenize_str('(a, )\n')) + + def testComma(self): + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'comma', '(a , b)\n ^'))): + self.checker.process_tokens(tokenize_str('(a , b)\n')) + + def testSpacesAllowedInsideSlices(self): + good_cases = [ + '[a:b]\n', + '[a : b]\n', + '[a : ]\n', + '[:a]\n', + '[:]\n', + '[::]\n', + ] + with self.assertNoMessages(): + for code in good_cases: + self.checker.process_tokens(tokenize_str(code)) + + def testKeywordSpacingGood(self): + with self.assertNoMessages(): + self.checker.process_tokens(tokenize_str('foo(foo=bar)\n')) + self.checker.process_tokens(tokenize_str('lambda x=1: x\n')) + + def testKeywordSpacingBad(self): + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'keyword argument assignment', + '(foo =bar)\n ^'))): + self.checker.process_tokens(tokenize_str('(foo =bar)\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'after', 'keyword argument assignment', + '(foo= bar)\n ^'))): + self.checker.process_tokens(tokenize_str('(foo= bar)\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'around', 'keyword argument assignment', + '(foo = bar)\n ^'))): + self.checker.process_tokens(tokenize_str('(foo = bar)\n')) + + def testOperatorSpacingGood(self): + good_cases = [ + 'a = b\n' + 'a < b\n' + 'a\n< b\n', + ] + with self.assertNoMessages(): + for code in good_cases: + self.checker.process_tokens(tokenize_str(code)) + + def testOperatorSpacingBad(self): + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('Exactly one', 'required', 'before', 'comparison', 'a< b\n ^'))): + self.checker.process_tokens(tokenize_str('a< b\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('Exactly one', 'required', 'after', 'comparison', 'a 2, 'Python 2 only')(test) + +# TODO(cpopa): Port these to the functional test framework instead. + +class Python3CheckerTest(testutils.CheckerTestCase): + CHECKER_CLASS = checker.Python3Checker + + def check_bad_builtin(self, builtin_name): + node = test_utils.extract_node(builtin_name + ' #@') + message = builtin_name.lower() + '-builtin' + with self.assertAddsMessages(testutils.Message(message, node=node)): + self.checker.visit_name(node) + + @python2_only + def test_bad_builtins(self): + builtins = [ + 'apply', + 'buffer', + 'cmp', + 'coerce', + 'execfile', + 'file', + 'input', + 'intern', + 'long', + 'raw_input', + 'round', + 'reduce', + 'StandardError', + 'unichr', + 'unicode', + 'xrange', + 'reload', + ] + for builtin in builtins: + self.check_bad_builtin(builtin) + + def as_iterable_in_for_loop_test(self, fxn): + code = "for x in {}(): pass".format(fxn) + module = astroid.parse(code) + with self.assertNoMessages(): + self.walk(module) + + def as_used_by_iterable_in_for_loop_test(self, fxn): + checker = '{}-builtin-not-iterating'.format(fxn) + node = test_utils.extract_node(""" + for x in (whatever( + {}() #@ + )): + pass + """.format(fxn)) + message = testutils.Message(checker, node=node) + with self.assertAddsMessages(message): + self.checker.visit_call(node) + + def as_iterable_in_genexp_test(self, fxn): + code = "x = (x for x in {}())".format(fxn) + module = astroid.parse(code) + with self.assertNoMessages(): + self.walk(module) + + def as_iterable_in_listcomp_test(self, fxn): + code = "x = [x for x in {}(None, [1])]".format(fxn) + module = astroid.parse(code) + with self.assertNoMessages(): + self.walk(module) + + def as_used_in_variant_in_genexp_test(self, fxn): + checker = '{}-builtin-not-iterating'.format(fxn) + node = test_utils.extract_node(""" + list( + __({}(x)) + for x in [1] + ) + """.format(fxn)) + message = testutils.Message(checker, node=node) + with self.assertAddsMessages(message): + self.checker.visit_call(node) + + def as_used_in_variant_in_listcomp_test(self, fxn): + checker = '{}-builtin-not-iterating'.format(fxn) + node = test_utils.extract_node(""" + [ + __({}(None, x)) + for x in [[1]]] + """.format(fxn)) + message = testutils.Message(checker, node=node) + with self.assertAddsMessages(message): + self.checker.visit_call(node) + + def as_argument_to_callable_constructor_test(self, fxn, callable_fn): + module = astroid.parse("x = {}({}())".format(callable_fn, fxn)) + with self.assertNoMessages(): + self.walk(module) + + def as_argument_to_random_fxn_test(self, fxn): + checker = '{}-builtin-not-iterating'.format(fxn) + node = test_utils.extract_node(""" + y( + {}() #@ + ) + """.format(fxn)) + message = testutils.Message(checker, node=node) + with self.assertAddsMessages(message): + self.checker.visit_call(node) + + def as_argument_to_str_join_test(self, fxn): + code = "x = ''.join({}())".format(fxn) + module = astroid.parse(code) + with self.assertNoMessages(): + self.walk(module) + + def as_iterable_in_unpacking(self, fxn): + node = test_utils.extract_node(""" + a, b = __({}()) + """.format(fxn)) + with self.assertNoMessages(): + self.checker.visit_call(node) + + def as_assignment(self, fxn): + checker = '{}-builtin-not-iterating'.format(fxn) + node = test_utils.extract_node(""" + a = __({}()) + """.format(fxn)) + message = testutils.Message(checker, node=node) + with self.assertAddsMessages(message): + self.checker.visit_call(node) + + def iterating_context_tests(self, fxn): + """Helper for verifying a function isn't used as an iterator.""" + self.as_iterable_in_for_loop_test(fxn) + self.as_used_by_iterable_in_for_loop_test(fxn) + self.as_iterable_in_genexp_test(fxn) + self.as_iterable_in_listcomp_test(fxn) + self.as_used_in_variant_in_genexp_test(fxn) + self.as_used_in_variant_in_listcomp_test(fxn) + self.as_argument_to_random_fxn_test(fxn) + self.as_argument_to_str_join_test(fxn) + self.as_iterable_in_unpacking(fxn) + self.as_assignment(fxn) + + for func in ('iter', 'list', 'tuple', 'sorted', + 'set', 'sum', 'any', 'all', + 'enumerate', 'dict'): + self.as_argument_to_callable_constructor_test(fxn, func) + + @python2_only + def test_map_in_iterating_context(self): + self.iterating_context_tests('map') + + @python2_only + def test_zip_in_iterating_context(self): + self.iterating_context_tests('zip') + + @python2_only + def test_range_in_iterating_context(self): + self.iterating_context_tests('range') + + @python2_only + def test_filter_in_iterating_context(self): + self.iterating_context_tests('filter') + + def defined_method_test(self, method, warning): + """Helper for verifying that a certain method is not defined.""" + node = test_utils.extract_node(""" + class Foo(object): + def __{0}__(self, other): #@ + pass""".format(method)) + message = testutils.Message(warning, node=node) + with self.assertAddsMessages(message): + self.checker.visit_functiondef(node) + + def test_delslice_method(self): + self.defined_method_test('delslice', 'delslice-method') + + def test_getslice_method(self): + self.defined_method_test('getslice', 'getslice-method') + + def test_setslice_method(self): + self.defined_method_test('setslice', 'setslice-method') + + def test_coerce_method(self): + self.defined_method_test('coerce', 'coerce-method') + + def test_oct_method(self): + self.defined_method_test('oct', 'oct-method') + + def test_hex_method(self): + self.defined_method_test('hex', 'hex-method') + + def test_nonzero_method(self): + self.defined_method_test('nonzero', 'nonzero-method') + + def test_cmp_method(self): + self.defined_method_test('cmp', 'cmp-method') + + @python2_only + def test_print_statement(self): + node = test_utils.extract_node('print "Hello, World!" #@') + message = testutils.Message('print-statement', node=node) + with self.assertAddsMessages(message): + self.checker.visit_print(node) + + @python2_only + def test_backtick(self): + node = test_utils.extract_node('`test`') + message = testutils.Message('backtick', node=node) + with self.assertAddsMessages(message): + self.checker.visit_repr(node) + + def test_relative_import(self): + node = test_utils.extract_node('import string #@') + message = testutils.Message('no-absolute-import', node=node) + with self.assertAddsMessages(message): + self.checker.visit_import(node) + + def test_relative_from_import(self): + node = test_utils.extract_node('from os import path #@') + message = testutils.Message('no-absolute-import', node=node) + with self.assertAddsMessages(message): + self.checker.visit_import(node) + + def test_absolute_import(self): + module_import = astroid.parse( + 'from __future__ import absolute_import; import os') + module_from = astroid.parse( + 'from __future__ import absolute_import; from os import path') + with self.assertNoMessages(): + for module in (module_import, module_from): + self.walk(module) + + def test_import_star_module_level(self): + node = test_utils.extract_node(''' + def test(): + from lala import * #@ + ''') + absolute = testutils.Message('no-absolute-import', node=node) + star = testutils.Message('import-star-module-level', node=node) + with self.assertAddsMessages(absolute, star): + self.checker.visit_importfrom(node) + + def test_division(self): + node = test_utils.extract_node('3 / 2 #@') + message = testutils.Message('old-division', node=node) + with self.assertAddsMessages(message): + self.checker.visit_binop(node) + + def test_division_with_future_statement(self): + module = astroid.parse('from __future__ import division; 3 / 2') + with self.assertNoMessages(): + self.walk(module) + + def test_floor_division(self): + node = test_utils.extract_node(' 3 // 2 #@') + with self.assertNoMessages(): + self.checker.visit_binop(node) + + def test_division_by_float(self): + left_node = test_utils.extract_node('3.0 / 2 #@') + right_node = test_utils.extract_node(' 3 / 2.0 #@') + with self.assertNoMessages(): + for node in (left_node, right_node): + self.checker.visit_binop(node) + + def test_dict_iter_method(self): + for meth in ('keys', 'values', 'items'): + node = test_utils.extract_node('x.iter%s() #@' % meth) + message = testutils.Message('dict-iter-method', node=node) + with self.assertAddsMessages(message): + self.checker.visit_call(node) + + def test_dict_iter_method_on_dict(self): + node = test_utils.extract_node('{}.iterkeys()') + message = testutils.Message('dict-iter-method', node=node) + with self.assertAddsMessages(message): + self.checker.visit_call(node) + + def test_dict_not_iter_method(self): + arg_node = test_utils.extract_node('x.iterkeys(x) #@') + stararg_node = test_utils.extract_node('x.iterkeys(*x) #@') + kwarg_node = test_utils.extract_node('x.iterkeys(y=x) #@') + non_dict_node = test_utils.extract_node('x=[]\nx.iterkeys() #@') + with self.assertNoMessages(): + for node in (arg_node, stararg_node, kwarg_node, non_dict_node): + self.checker.visit_call(node) + + def test_dict_view_method(self): + for meth in ('keys', 'values', 'items'): + node = test_utils.extract_node('x.view%s() #@' % meth) + message = testutils.Message('dict-view-method', node=node) + with self.assertAddsMessages(message): + self.checker.visit_call(node) + + def test_dict_view_method_on_dict(self): + node = test_utils.extract_node('{}.viewkeys()') + message = testutils.Message('dict-view-method', node=node) + with self.assertAddsMessages(message): + self.checker.visit_call(node) + + def test_dict_not_view_method(self): + arg_node = test_utils.extract_node('x.viewkeys(x) #@') + stararg_node = test_utils.extract_node('x.viewkeys(*x) #@') + kwarg_node = test_utils.extract_node('x.viewkeys(y=x) #@') + non_dict_node = test_utils.extract_node('x=[]\nx.viewkeys() #@') + with self.assertNoMessages(): + for node in (arg_node, stararg_node, kwarg_node, non_dict_node): + self.checker.visit_call(node) + + def test_next_method(self): + node = test_utils.extract_node('x.next() #@') + message = testutils.Message('next-method-called', node=node) + with self.assertAddsMessages(message): + self.checker.visit_call(node) + + def test_not_next_method(self): + arg_node = test_utils.extract_node('x.next(x) #@') + stararg_node = test_utils.extract_node('x.next(*x) #@') + kwarg_node = test_utils.extract_node('x.next(y=x) #@') + with self.assertNoMessages(): + for node in (arg_node, stararg_node, kwarg_node): + self.checker.visit_call(node) + + def test_metaclass_assignment(self): + node = test_utils.extract_node(""" + class Foo(object): #@ + __metaclass__ = type""") + message = testutils.Message('metaclass-assignment', node=node) + with self.assertAddsMessages(message): + self.checker.visit_classdef(node) + + def test_metaclass_global_assignment(self): + module = astroid.parse('__metaclass__ = type') + with self.assertNoMessages(): + self.walk(module) + + @python2_only + def test_parameter_unpacking(self): + node = test_utils.extract_node('def func((a, b)):#@\n pass') + arg = node.args.args[0] + with self.assertAddsMessages(testutils.Message('parameter-unpacking', node=arg)): + self.checker.visit_arguments(node.args) + + @python2_only + def test_old_raise_syntax(self): + node = test_utils.extract_node('raise Exception, "test"') + message = testutils.Message('old-raise-syntax', node=node) + with self.assertAddsMessages(message): + self.checker.visit_raise(node) + + @python2_only + def test_raising_string(self): + node = test_utils.extract_node('raise "Test"') + message = testutils.Message('raising-string', node=node) + with self.assertAddsMessages(message): + self.checker.visit_raise(node) + + @python2_only + def test_checker_disabled_by_default(self): + node = astroid.parse(textwrap.dedent(""" + abc = 1l + raise Exception, "test" + raise "test" + `abc` + """)) + with self.assertNoMessages(): + self.walk(node) + + def test_using_cmp_argument(self): + nodes = test_utils.extract_node(""" + [].sort(cmp=lambda x: x) #@ + a = list(range(x)) + a.sort(cmp=lambda x: x) #@ + + sorted([], cmp=lambda x: x) #@ + """) + for node in nodes: + message = testutils.Message('using-cmp-argument', node=node) + with self.assertAddsMessages(message): + self.checker.visit_call(node) + + +@python2_only +class Python3TokenCheckerTest(testutils.CheckerTestCase): + + CHECKER_CLASS = checker.Python3TokenChecker + + def _test_token_message(self, code, symbolic_message): + tokens = testutils.tokenize_str(code) + message = testutils.Message(symbolic_message, line=1) + with self.assertAddsMessages(message): + self.checker.process_tokens(tokens) + + def test_long_suffix(self): + for code in ("1l", "1L"): + self._test_token_message(code, 'long-suffix') + + def test_old_ne_operator(self): + self._test_token_message("1 <> 2", "old-ne-operator") + + def test_old_octal_literal(self): + for octal in ("045", "055", "075", "077", "076543"): + self._test_token_message(octal, "old-octal-literal") + + # Make sure we are catching only octals. + for non_octal in ("45", "00", "085", "08", "1"): + tokens = testutils.tokenize_str(non_octal) + with self.assertNoMessages(): + self.checker.process_tokens(tokens) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_checker_similar.py b/pymode/libs/pylint/test/unittest_checker_similar.py new file mode 100644 index 00000000..4cd48cc4 --- /dev/null +++ b/pymode/libs/pylint/test/unittest_checker_similar.py @@ -0,0 +1,142 @@ +import sys +from os.path import join, dirname, abspath +import unittest + +import six + +from pylint.checkers import similar + +SIMILAR1 = join(dirname(abspath(__file__)), 'input', 'similar1') +SIMILAR2 = join(dirname(abspath(__file__)), 'input', 'similar2') + +class SimilarTC(unittest.TestCase): + """test the similar command line utility""" + + def test_ignore_comments(self): + sys.stdout = six.StringIO() + try: + similar.Run(['--ignore-comments', SIMILAR1, SIMILAR2]) + except SystemExit as ex: + self.assertEqual(ex.code, 0) + output = sys.stdout.getvalue() + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + self.assertMultiLineEqual(output.strip(), (""" +10 similar lines in 2 files +==%s:0 +==%s:0 + import one + from two import two + three + four + five + six + seven + eight + nine + ''' ten +TOTAL lines=44 duplicates=10 percent=22.73 +""" % (SIMILAR1, SIMILAR2)).strip()) + + + def test_ignore_docsrings(self): + sys.stdout = six.StringIO() + try: + similar.Run(['--ignore-docstrings', SIMILAR1, SIMILAR2]) + except SystemExit as ex: + self.assertEqual(ex.code, 0) + output = sys.stdout.getvalue() + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + self.assertMultiLineEqual(output.strip(), (""" +8 similar lines in 2 files +==%s:6 +==%s:6 + seven + eight + nine + ''' ten + ELEVEN + twelve ''' + thirteen + fourteen + +5 similar lines in 2 files +==%s:0 +==%s:0 + import one + from two import two + three + four + five +TOTAL lines=44 duplicates=13 percent=29.55 +""" % ((SIMILAR1, SIMILAR2) * 2)).strip()) + + + def test_ignore_imports(self): + sys.stdout = six.StringIO() + try: + similar.Run(['--ignore-imports', SIMILAR1, SIMILAR2]) + except SystemExit as ex: + self.assertEqual(ex.code, 0) + output = sys.stdout.getvalue() + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + self.assertMultiLineEqual(output.strip(), """ +TOTAL lines=44 duplicates=0 percent=0.00 +""".strip()) + + + def test_ignore_nothing(self): + sys.stdout = six.StringIO() + try: + similar.Run([SIMILAR1, SIMILAR2]) + except SystemExit as ex: + self.assertEqual(ex.code, 0) + output = sys.stdout.getvalue() + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + self.assertMultiLineEqual(output.strip(), (""" +5 similar lines in 2 files +==%s:0 +==%s:0 + import one + from two import two + three + four + five +TOTAL lines=44 duplicates=5 percent=11.36 +""" % (SIMILAR1, SIMILAR2)).strip()) + + def test_help(self): + sys.stdout = six.StringIO() + try: + similar.Run(['--help']) + except SystemExit as ex: + self.assertEqual(ex.code, 0) + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + + def test_no_args(self): + sys.stdout = six.StringIO() + try: + similar.Run([]) + except SystemExit as ex: + self.assertEqual(ex.code, 1) + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_checker_spelling.py b/pymode/libs/pylint/test/unittest_checker_spelling.py new file mode 100644 index 00000000..3c43474a --- /dev/null +++ b/pymode/libs/pylint/test/unittest_checker_spelling.py @@ -0,0 +1,93 @@ +# Copyright 2014 Michal Nowikowski. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Unittest for the spelling checker.""" + +import unittest + +from astroid import test_utils + +from pylint.checkers import spelling +from pylint.testutils import ( + CheckerTestCase, Message, set_config, tokenize_str, +) + +# try to create enchant dictionary +try: + import enchant +except ImportError: + enchant = None + +spell_dict = None +if enchant is not None: + try: + enchant.Dict("en_US") + spell_dict = "en_US" + except enchant.DictNotFoundError: + pass + + +class SpellingCheckerTest(CheckerTestCase): + CHECKER_CLASS = spelling.SpellingChecker + + @unittest.skipIf(spell_dict is None, + "missing python-enchant package or missing " + "spelling dictionaries") + @set_config(spelling_dict=spell_dict) + def test_check_bad_coment(self): + with self.assertAddsMessages( + Message('wrong-spelling-in-comment', line=1, + args=('coment', '# bad coment', + ' ^^^^^^', + "comet' or 'comment' or 'moment' or 'foment"))): + self.checker.process_tokens(tokenize_str("# bad coment")) + + @unittest.skipIf(spell_dict is None, + "missing python-enchant package or missing " + "spelling dictionaries") + @set_config(spelling_dict=spell_dict) + def test_check_bad_docstring(self): + stmt = test_utils.extract_node( + 'def fff():\n """bad coment"""\n pass') + with self.assertAddsMessages( + Message('wrong-spelling-in-docstring', line=2, + args=('coment', 'bad coment', + ' ^^^^^^', + "comet' or 'comment' or 'moment' or 'foment"))): + self.checker.visit_functiondef(stmt) + + stmt = test_utils.extract_node( + 'class Abc(object):\n """bad coment"""\n pass') + with self.assertAddsMessages( + Message('wrong-spelling-in-docstring', line=2, + args=('coment', 'bad coment', + ' ^^^^^^', + "comet' or 'comment' or 'moment' or 'foment"))): + self.checker.visit_classdef(stmt) + + @unittest.skipIf(spell_dict is None, + "missing python-enchant package or missing " + "spelling dictionaries") + @set_config(spelling_dict=spell_dict) + def test_invalid_docstring_characters(self): + stmt = test_utils.extract_node( + 'def fff():\n """test\\x00"""\n pass') + with self.assertAddsMessages( + Message('invalid-characters-in-docstring', line=2, + args=('test\x00',))): + self.checker.visit_functiondef(stmt) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_checker_stdlib.py b/pymode/libs/pylint/test/unittest_checker_stdlib.py new file mode 100644 index 00000000..82c1f17d --- /dev/null +++ b/pymode/libs/pylint/test/unittest_checker_stdlib.py @@ -0,0 +1,65 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import contextlib +import unittest + +import astroid +from astroid import test_utils + +from pylint.checkers import stdlib +from pylint.testutils import CheckerTestCase + + +@contextlib.contextmanager +def _add_transform(manager, node, transform, predicate=None): + manager.register_transform(node, transform, predicate) + try: + yield + finally: + manager.unregister_transform(node, transform, predicate) + + +class StdlibCheckerTest(CheckerTestCase): + CHECKER_CLASS = stdlib.StdlibChecker + + def test_deprecated_no_qname_on_unexpected_nodes(self): + # Test that we don't crash on nodes which don't have + # a qname method. While this test might seem weird since + # it uses a transform, it's actually testing a crash that + # happened in production, but there was no way to retrieve + # the code for which this occurred (how an AssignAttr + # got to be the result of a function inference + # beats me..) + + def infer_func(node, context=None): + new_node = astroid.AssignAttr() + new_node.parent = node + yield new_node + + manager = astroid.MANAGER + transform = astroid.inference_tip(infer_func) + with _add_transform(manager, astroid.Name, transform): + node = test_utils.extract_node(''' + call_something() + ''') + with self.assertNoMessages(): + self.checker.visit_call(node) + + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_checker_strings.py b/pymode/libs/pylint/test/unittest_checker_strings.py new file mode 100644 index 00000000..bc90be73 --- /dev/null +++ b/pymode/libs/pylint/test/unittest_checker_strings.py @@ -0,0 +1,41 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import sys +import unittest + +from astroid import test_utils + +from pylint.checkers import strings +from pylint.testutils import CheckerTestCase + + +class StringCheckerTest(CheckerTestCase): + CHECKER_CLASS = strings.StringFormatChecker + + @unittest.skipUnless(sys.version_info > (3, 0), + "Tests that the string formatting checker " + "doesn't fail when encountering a bytes " + "string with a .format call") + def test_format_bytes(self): + code = "b'test'.format(1, 2)" + node = test_utils.extract_node(code) + with self.assertNoMessages(): + self.checker.visit_call(node) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_checker_typecheck.py b/pymode/libs/pylint/test/unittest_checker_typecheck.py new file mode 100644 index 00000000..0aaa8a57 --- /dev/null +++ b/pymode/libs/pylint/test/unittest_checker_typecheck.py @@ -0,0 +1,94 @@ +"""Unittest for the type checker.""" +import unittest + +from astroid import test_utils +from pylint.checkers import typecheck +from pylint.testutils import CheckerTestCase, Message, set_config + + +class TypeCheckerTest(CheckerTestCase): + "Tests for pylint.checkers.typecheck" + CHECKER_CLASS = typecheck.TypeChecker + + def test_no_member_in_getattr(self): + """Make sure that a module attribute access is checked by pylint. + """ + + node = test_utils.extract_node(""" + import optparse + optparse.THIS_does_not_EXIST + """) + with self.assertAddsMessages( + Message( + 'no-member', + node=node, + args=('Module', 'optparse', 'THIS_does_not_EXIST'))): + self.checker.visit_attribute(node) + + @set_config(ignored_modules=('argparse',)) + def test_no_member_in_getattr_ignored(self): + """Make sure that a module attribute access check is omitted with a + module that is configured to be ignored. + """ + + node = test_utils.extract_node(""" + import argparse + argparse.THIS_does_not_EXIST + """) + with self.assertNoMessages(): + self.checker.visit_attribute(node) + + @set_config(ignored_classes=('xml.etree.', )) + def test_ignored_modules_invalid_pattern(self): + node = test_utils.extract_node(''' + import xml + xml.etree.Lala + ''') + message = Message('no-member', node=node, + args=('Module', 'xml.etree', 'Lala')) + with self.assertAddsMessages(message): + self.checker.visit_attribute(node) + + @set_config(ignored_modules=('xml.etree*', )) + def test_ignored_modules_patterns(self): + node = test_utils.extract_node(''' + import xml + xml.etree.portocola #@ + ''') + with self.assertNoMessages(): + self.checker.visit_attribute(node) + + @set_config(ignored_classes=('xml.*', )) + def test_ignored_classes_no_recursive_pattern(self): + node = test_utils.extract_node(''' + import xml + xml.etree.ElementTree.Test + ''') + message = Message('no-member', node=node, + args=('Module', 'xml.etree.ElementTree', 'Test')) + with self.assertAddsMessages(message): + self.checker.visit_attribute(node) + + @set_config(ignored_classes=('optparse.Values', )) + def test_ignored_classes_qualified_name(self): + """Test that ignored-classes supports qualified name for ignoring.""" + node = test_utils.extract_node(''' + import optparse + optparse.Values.lala + ''') + with self.assertNoMessages(): + self.checker.visit_attribute(node) + + @set_config(ignored_classes=('Values', )) + def test_ignored_classes_only_name(self): + """Test that ignored_classes works with the name only.""" + node = test_utils.extract_node(''' + import optparse + optparse.Values.lala + ''') + with self.assertNoMessages(): + self.checker.visit_attribute(node) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_checker_variables.py b/pymode/libs/pylint/test/unittest_checker_variables.py new file mode 100644 index 00000000..e42dffcf --- /dev/null +++ b/pymode/libs/pylint/test/unittest_checker_variables.py @@ -0,0 +1,109 @@ +"""Unit tests for the variables checker.""" +import sys +import os +import unittest + +import astroid +from astroid import test_utils + +from pylint.checkers import variables +from pylint.testutils import CheckerTestCase, linter, set_config, Message + +class VariablesCheckerTC(CheckerTestCase): + + CHECKER_CLASS = variables.VariablesChecker + + def test_bitbucket_issue_78(self): + """ Issue 78 report a false positive for unused-module """ + module = astroid.parse(""" + from sys import path + path += ['stuff'] + def func(): + other = 1 + return len(other) + """) + with self.assertNoMessages(): + self.walk(module) + + @set_config(ignored_modules=('argparse',)) + def test_no_name_in_module_skipped(self): + """Make sure that 'from ... import ...' does not emit a + 'no-name-in-module' with a module that is configured + to be ignored. + """ + + node = test_utils.extract_node(""" + from argparse import THIS_does_not_EXIST + """) + with self.assertNoMessages(): + self.checker.visit_importfrom(node) + + def test_all_elements_without_parent(self): + node = test_utils.extract_node('__all__ = []') + node.value.elts.append(astroid.Const('test')) + root = node.root() + with self.assertNoMessages(): + self.checker.visit_module(root) + self.checker.leave_module(root) + + @set_config(callbacks=('callback_', '_callback')) + def test_custom_callback_string(self): + """ Test the --calbacks option works. """ + def cleanup(): + self.checker._to_consume = _to_consume + _to_consume = self.checker._to_consume + self.checker._to_consume = [] + self.addCleanup(cleanup) + + node = test_utils.extract_node(""" + def callback_one(abc): + ''' should not emit unused-argument. ''' + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + self.checker.leave_functiondef(node) + + node = test_utils.extract_node(""" + def two_callback(abc, defg): + ''' should not emit unused-argument. ''' + """) + with self.assertNoMessages(): + self.checker.visit_functiondef(node) + self.checker.leave_functiondef(node) + + node = test_utils.extract_node(""" + def normal_func(abc): + ''' should emit unused-argument. ''' + """) + with self.assertAddsMessages( + Message('unused-argument', node=node['abc'], args='abc')): + self.checker.visit_functiondef(node) + self.checker.leave_functiondef(node) + + node = test_utils.extract_node(""" + def cb_func(abc): + ''' Previous callbacks are overriden. ''' + """) + with self.assertAddsMessages( + Message('unused-argument', node=node['abc'], args='abc')): + self.checker.visit_functiondef(node) + self.checker.leave_functiondef(node) + + +class MissingSubmoduleTest(CheckerTestCase): + CHECKER_CLASS = variables.VariablesChecker + + def test_package_all(self): + regr_data = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'regrtest_data') + sys.path.insert(0, regr_data) + try: + linter.check(os.path.join(regr_data, 'package_all')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, "E: 3: Undefined variable name " + "'missing' in __all__") + finally: + sys.path.pop(0) + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_checkers_utils.py b/pymode/libs/pylint/test/unittest_checkers_utils.py new file mode 100644 index 00000000..07cec60d --- /dev/null +++ b/pymode/libs/pylint/test/unittest_checkers_utils.py @@ -0,0 +1,128 @@ +# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Tests for the pylint.checkers.utils module.""" + +import unittest +import warnings + +from astroid import test_utils + +from pylint.checkers import utils +from pylint import __pkginfo__ + +class UtilsTC(unittest.TestCase): + + def test_is_builtin(self): + self.assertEqual(utils.is_builtin('min'), True) + self.assertEqual(utils.is_builtin('__builtins__'), True) + self.assertEqual(utils.is_builtin('__path__'), False) + self.assertEqual(utils.is_builtin('__file__'), False) + self.assertEqual(utils.is_builtin('whatever'), False) + self.assertEqual(utils.is_builtin('mybuiltin'), False) + + def testGetArgumentFromCall(self): + node = test_utils.extract_node('foo(bar=3)') + self.assertIsNotNone(utils.get_argument_from_call(node, keyword='bar')) + with self.assertRaises(utils.NoSuchArgumentError): + node = test_utils.extract_node('foo(3)') + utils.get_argument_from_call(node, keyword='bar') + with self.assertRaises(utils.NoSuchArgumentError): + node = test_utils.extract_node('foo(one=a, two=b, three=c)') + utils.get_argument_from_call(node, position=1) + node = test_utils.extract_node('foo(a, b, c)') + self.assertIsNotNone(utils.get_argument_from_call(node, position=1)) + node = test_utils.extract_node('foo(a, not_this_one=1, this_one=2)') + arg = utils.get_argument_from_call(node, position=2, keyword='this_one') + self.assertEqual(2, arg.value) + node = test_utils.extract_node('foo(a)') + with self.assertRaises(utils.NoSuchArgumentError): + utils.get_argument_from_call(node, position=1) + with self.assertRaises(ValueError): + utils.get_argument_from_call(node, None, None) + + name = utils.get_argument_from_call(node, position=0) + self.assertEqual(name.name, 'a') + + def test_is_import_error(self): + import_error, not_import_error = test_utils.extract_node(""" + try: + pass + except ImportError: #@ + pass + + try: + pass + except AttributeError: #@ + pass + """) + + if __pkginfo__.numversion >= (1, 6, 0): + with self.assertRaises(AttributeError): + utils.is_import_error + + with warnings.catch_warnings(record=True) as cm: + warnings.simplefilter("always") + + self.assertTrue(utils.is_import_error(import_error)) + self.assertFalse(utils.is_import_error(not_import_error)) + + self.assertEqual(len(cm), 2) + self.assertIsInstance(cm[0].message, DeprecationWarning) + + def test_error_of_type(self): + nodes = test_utils.extract_node(""" + try: pass + except AttributeError: #@ + pass + try: pass + except Exception: #@ + pass + except: #@ + pass + """) + self.assertTrue(utils.error_of_type(nodes[0], AttributeError)) + self.assertTrue(utils.error_of_type(nodes[0], (AttributeError, ))) + self.assertFalse(utils.error_of_type(nodes[0], Exception)) + self.assertTrue(utils.error_of_type(nodes[1], Exception)) + self.assertFalse(utils.error_of_type(nodes[2], ImportError)) + + def test_node_ignores_exception(self): + nodes = test_utils.extract_node(""" + try: + 1/0 #@ + except ZeroDivisionError: + pass + try: + 1/0 #@ + except Exception: + pass + try: + 2/0 #@ + except: + pass + try: + 1/0 #@ + except ValueError: + pass + """) + self.assertTrue(utils.node_ignores_exception(nodes[0], ZeroDivisionError)) + self.assertFalse(utils.node_ignores_exception(nodes[1], ZeroDivisionError)) + self.assertFalse(utils.node_ignores_exception(nodes[2], ZeroDivisionError)) + self.assertFalse(utils.node_ignores_exception(nodes[3], ZeroDivisionError)) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_lint.py b/pymode/libs/pylint/test/unittest_lint.py new file mode 100644 index 00000000..5877d4c2 --- /dev/null +++ b/pymode/libs/pylint/test/unittest_lint.py @@ -0,0 +1,691 @@ +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from contextlib import contextmanager +import sys +import os +import tempfile +from shutil import rmtree +from os import getcwd, chdir +from os.path import join, basename, dirname, isdir, abspath, sep +import unittest + +import six +from six.moves import reload_module + +from pylint import config, lint +from pylint.lint import PyLinter, Run, preprocess_options, \ + ArgumentPreprocessingError +from pylint.utils import MSG_STATE_SCOPE_CONFIG, MSG_STATE_SCOPE_MODULE, MSG_STATE_CONFIDENCE, \ + MessagesStore, PyLintASTWalker, MessageDefinition, FileState, \ + build_message_def, tokenize_module, UnknownMessage +from pylint.testutils import TestReporter +from pylint.reporters import text, html +from pylint import checkers +from pylint.checkers.utils import check_messages +from pylint import interfaces + +if os.name == 'java': + if os._name == 'nt': + HOME = 'USERPROFILE' + else: + HOME = 'HOME' +else: + if sys.platform == 'win32': + HOME = 'USERPROFILE' + else: + HOME = 'HOME' + +@contextmanager +def fake_home(): + folder = tempfile.mkdtemp('fake-home') + old_home = os.environ.get(HOME) + try: + os.environ[HOME] = folder + yield + finally: + os.environ.pop('PYLINTRC', '') + if old_home is None: + del os.environ[HOME] + else: + os.environ[HOME] = old_home + rmtree(folder, ignore_errors=True) + +def remove(file): + try: + os.remove(file) + except OSError: + pass + +HERE = abspath(dirname(__file__)) +INPUTDIR = join(HERE, 'input') + + +@contextmanager +def tempdir(): + """Create a temp directory and change the current location to it. + + This is supposed to be used with a *with* statement. + """ + tmp = tempfile.mkdtemp() + + # Get real path of tempfile, otherwise test fail on mac os x + current_dir = getcwd() + chdir(tmp) + abs_tmp = abspath('.') + + try: + yield abs_tmp + finally: + chdir(current_dir) + rmtree(abs_tmp) + + +def create_files(paths, chroot='.'): + """Creates directories and files found in . + + :param paths: list of relative paths to files or directories + :param chroot: the root directory in which paths will be created + + >>> from os.path import isdir, isfile + >>> isdir('/tmp/a') + False + >>> create_files(['a/b/foo.py', 'a/b/c/', 'a/b/c/d/e.py'], '/tmp') + >>> isdir('/tmp/a') + True + >>> isdir('/tmp/a/b/c') + True + >>> isfile('/tmp/a/b/c/d/e.py') + True + >>> isfile('/tmp/a/b/foo.py') + True + """ + dirs, files = set(), set() + for path in paths: + path = join(chroot, path) + filename = basename(path) + # path is a directory path + if filename == '': + dirs.add(path) + # path is a filename path + else: + dirs.add(dirname(path)) + files.add(path) + for dirpath in dirs: + if not isdir(dirpath): + os.makedirs(dirpath) + for filepath in files: + open(filepath, 'w').close() + + +class SysPathFixupTC(unittest.TestCase): + def setUp(self): + self.orig = list(sys.path) + self.fake = [1, 2, 3] + sys.path[:] = self.fake + + def tearDown(self): + sys.path[:] = self.orig + + def test_no_args(self): + with lint.fix_import_path([]): + self.assertEqual(sys.path, self.fake) + self.assertEqual(sys.path, self.fake) + + def test_one_arg(self): + with tempdir() as chroot: + create_files(['a/b/__init__.py']) + expected = [join(chroot, 'a')] + self.fake + + cases = ( + ['a/b/'], + ['a/b'], + ['a/b/__init__.py'], + ['a/'], + ['a'], + ) + + self.assertEqual(sys.path, self.fake) + for case in cases: + with lint.fix_import_path(case): + self.assertEqual(sys.path, expected) + self.assertEqual(sys.path, self.fake) + + def test_two_similar_args(self): + with tempdir() as chroot: + create_files(['a/b/__init__.py', 'a/c/__init__.py']) + expected = [join(chroot, 'a')] + self.fake + + cases = ( + ['a/b', 'a/c'], + ['a/c/', 'a/b/'], + ['a/b/__init__.py', 'a/c/__init__.py'], + ['a', 'a/c/__init__.py'], + ) + + self.assertEqual(sys.path, self.fake) + for case in cases: + with lint.fix_import_path(case): + self.assertEqual(sys.path, expected) + self.assertEqual(sys.path, self.fake) + + def test_more_args(self): + with tempdir() as chroot: + create_files(['a/b/c/__init__.py', 'a/d/__init__.py', 'a/e/f.py']) + expected = [ + join(chroot, suffix) + for suffix in [sep.join(('a', 'b')), 'a', sep.join(('a', 'e'))] + ] + self.fake + + cases = ( + ['a/b/c/__init__.py', 'a/d/__init__.py', 'a/e/f.py'], + ['a/b/c', 'a', 'a/e'], + ['a/b/c', 'a', 'a/b/c', 'a/e', 'a'], + ) + + self.assertEqual(sys.path, self.fake) + for case in cases: + with lint.fix_import_path(case): + self.assertEqual(sys.path, expected) + self.assertEqual(sys.path, self.fake) + + +class PyLinterTC(unittest.TestCase): + + def setUp(self): + self.linter = PyLinter() + self.linter.disable('I') + self.linter.config.persistent = 0 + # register checkers + checkers.initialize(self.linter) + self.linter.set_reporter(TestReporter()) + + def init_linter(self): + linter = self.linter + linter.open() + linter.set_current_module('toto') + linter.file_state = FileState('toto') + return linter + + def test_pylint_visit_method_taken_in_account(self): + class CustomChecker(checkers.BaseChecker): + __implements__ = interfaces.IAstroidChecker + name = 'custom' + msgs = {'W9999': ('', 'custom', '')} + + @check_messages('custom') + def visit_class(self, _): + pass + + self.linter.register_checker(CustomChecker(self.linter)) + self.linter.open() + out = six.moves.StringIO() + self.linter.set_reporter(text.TextReporter(out)) + self.linter.check('abc') + + def test_enable_message(self): + linter = self.init_linter() + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('W0102')) + linter.disable('W0101', scope='package') + linter.disable('W0102', scope='module', line=1) + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertFalse(linter.is_message_enabled('W0102', 1)) + linter.set_current_module('tutu') + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('W0102')) + linter.enable('W0101', scope='package') + linter.enable('W0102', scope='module', line=1) + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('W0102', 1)) + + def test_enable_message_category(self): + linter = self.init_linter() + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('C0202')) + linter.disable('W', scope='package') + linter.disable('C', scope='module', line=1) + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('C0202')) + self.assertFalse(linter.is_message_enabled('C0202', line=1)) + linter.set_current_module('tutu') + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('C0202')) + linter.enable('W', scope='package') + linter.enable('C', scope='module', line=1) + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('C0202')) + self.assertTrue(linter.is_message_enabled('C0202', line=1)) + + def test_message_state_scope(self): + class FakeConfig(object): + confidence = ['HIGH'] + + linter = self.init_linter() + linter.disable('C0202') + self.assertEqual(MSG_STATE_SCOPE_CONFIG, + linter.get_message_state_scope('C0202')) + linter.disable('W0101', scope='module', line=3) + self.assertEqual(MSG_STATE_SCOPE_CONFIG, + linter.get_message_state_scope('C0202')) + self.assertEqual(MSG_STATE_SCOPE_MODULE, + linter.get_message_state_scope('W0101', 3)) + linter.enable('W0102', scope='module', line=3) + self.assertEqual(MSG_STATE_SCOPE_MODULE, + linter.get_message_state_scope('W0102', 3)) + linter.config = FakeConfig() + self.assertEqual( + MSG_STATE_CONFIDENCE, + linter.get_message_state_scope('this-is-bad', + confidence=interfaces.INFERENCE)) + + def test_enable_message_block(self): + linter = self.init_linter() + linter.open() + filepath = join(INPUTDIR, 'func_block_disable_msg.py') + linter.set_current_module('func_block_disable_msg') + astroid = linter.get_ast(filepath, 'func_block_disable_msg') + linter.process_tokens(tokenize_module(astroid)) + fs = linter.file_state + fs.collect_block_lines(linter.msgs_store, astroid) + # global (module level) + self.assertTrue(linter.is_message_enabled('W0613')) + self.assertTrue(linter.is_message_enabled('E1101')) + # meth1 + self.assertTrue(linter.is_message_enabled('W0613', 13)) + # meth2 + self.assertFalse(linter.is_message_enabled('W0613', 18)) + # meth3 + self.assertFalse(linter.is_message_enabled('E1101', 24)) + self.assertTrue(linter.is_message_enabled('E1101', 26)) + # meth4 + self.assertFalse(linter.is_message_enabled('E1101', 32)) + self.assertTrue(linter.is_message_enabled('E1101', 36)) + # meth5 + self.assertFalse(linter.is_message_enabled('E1101', 42)) + self.assertFalse(linter.is_message_enabled('E1101', 43)) + self.assertTrue(linter.is_message_enabled('E1101', 46)) + self.assertFalse(linter.is_message_enabled('E1101', 49)) + self.assertFalse(linter.is_message_enabled('E1101', 51)) + # meth6 + self.assertFalse(linter.is_message_enabled('E1101', 57)) + self.assertTrue(linter.is_message_enabled('E1101', 61)) + self.assertFalse(linter.is_message_enabled('E1101', 64)) + self.assertFalse(linter.is_message_enabled('E1101', 66)) + + self.assertTrue(linter.is_message_enabled('E0602', 57)) + self.assertTrue(linter.is_message_enabled('E0602', 61)) + self.assertFalse(linter.is_message_enabled('E0602', 62)) + self.assertTrue(linter.is_message_enabled('E0602', 64)) + self.assertTrue(linter.is_message_enabled('E0602', 66)) + # meth7 + self.assertFalse(linter.is_message_enabled('E1101', 70)) + self.assertTrue(linter.is_message_enabled('E1101', 72)) + self.assertTrue(linter.is_message_enabled('E1101', 75)) + self.assertTrue(linter.is_message_enabled('E1101', 77)) + + fs = linter.file_state + self.assertEqual(17, fs._suppression_mapping['W0613', 18]) + self.assertEqual(30, fs._suppression_mapping['E1101', 33]) + self.assertTrue(('E1101', 46) not in fs._suppression_mapping) + self.assertEqual(1, fs._suppression_mapping['C0302', 18]) + self.assertEqual(1, fs._suppression_mapping['C0302', 50]) + # This is tricky. While the disable in line 106 is disabling + # both 108 and 110, this is usually not what the user wanted. + # Therefore, we report the closest previous disable comment. + self.assertEqual(106, fs._suppression_mapping['E1101', 108]) + self.assertEqual(109, fs._suppression_mapping['E1101', 110]) + + def test_enable_by_symbol(self): + """messages can be controlled by symbolic names. + + The state is consistent across symbols and numbers. + """ + linter = self.init_linter() + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('unreachable')) + self.assertTrue(linter.is_message_enabled('W0102')) + self.assertTrue(linter.is_message_enabled('dangerous-default-value')) + linter.disable('unreachable', scope='package') + linter.disable('dangerous-default-value', scope='module', line=1) + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertFalse(linter.is_message_enabled('unreachable')) + self.assertFalse(linter.is_message_enabled('W0102', 1)) + self.assertFalse(linter.is_message_enabled('dangerous-default-value', 1)) + linter.set_current_module('tutu') + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertFalse(linter.is_message_enabled('unreachable')) + self.assertTrue(linter.is_message_enabled('W0102')) + self.assertTrue(linter.is_message_enabled('dangerous-default-value')) + linter.enable('unreachable', scope='package') + linter.enable('dangerous-default-value', scope='module', line=1) + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('unreachable')) + self.assertTrue(linter.is_message_enabled('W0102', 1)) + self.assertTrue(linter.is_message_enabled('dangerous-default-value', 1)) + + def test_lint_ext_module_with_file_output(self): + self.linter.set_reporter(text.TextReporter()) + if sys.version_info < (3, 0): + strio = 'StringIO' + else: + strio = 'io' + self.linter.config.files_output = True + pylint_strio = 'pylint_%s.txt' % strio + files = [pylint_strio, 'pylint_global.txt'] + for file in files: + self.addCleanup(remove, file) + + self.linter.check(strio) + self.linter.generate_reports() + for f in files: + self.assertTrue(os.path.exists(f)) + + def test_enable_report(self): + self.assertEqual(self.linter.report_is_enabled('RP0001'), True) + self.linter.disable('RP0001') + self.assertEqual(self.linter.report_is_enabled('RP0001'), False) + self.linter.enable('RP0001') + self.assertEqual(self.linter.report_is_enabled('RP0001'), True) + + def test_report_output_format_aliased(self): + text.register(self.linter) + self.linter.set_option('output-format', 'text') + self.assertEqual(self.linter.reporter.__class__.__name__, 'TextReporter') + + def test_report_output_format_custom(self): + this_module = sys.modules[__name__] + class TestReporter(object): + pass + this_module.TestReporter = TestReporter + class_name = ".".join((this_module.__name__, 'TestReporter')) + self.linter.set_option('output-format', class_name) + self.assertEqual(self.linter.reporter.__class__.__name__, 'TestReporter') + + def test_set_option_1(self): + linter = self.linter + linter.set_option('disable', 'C0111,W0234') + self.assertFalse(linter.is_message_enabled('C0111')) + self.assertFalse(linter.is_message_enabled('W0234')) + self.assertTrue(linter.is_message_enabled('W0113')) + self.assertFalse(linter.is_message_enabled('missing-docstring')) + self.assertFalse(linter.is_message_enabled('non-iterator-returned')) + + def test_set_option_2(self): + linter = self.linter + linter.set_option('disable', ('C0111', 'W0234') ) + self.assertFalse(linter.is_message_enabled('C0111')) + self.assertFalse(linter.is_message_enabled('W0234')) + self.assertTrue(linter.is_message_enabled('W0113')) + self.assertFalse(linter.is_message_enabled('missing-docstring')) + self.assertFalse(linter.is_message_enabled('non-iterator-returned')) + + def test_enable_checkers(self): + self.linter.disable('design') + self.assertFalse('design' in [c.name for c in self.linter.prepare_checkers()]) + self.linter.enable('design') + self.assertTrue('design' in [c.name for c in self.linter.prepare_checkers()]) + + def test_errors_only(self): + linter = self.linter + self.linter.error_mode() + checkers = self.linter.prepare_checkers() + checker_names = set(c.name for c in checkers) + should_not = set(('design', 'format', 'metrics', + 'miscellaneous', 'similarities')) + self.assertSetEqual(set(), should_not & checker_names) + + def test_disable_similar(self): + self.linter.set_option('disable', 'RP0801') + self.linter.set_option('disable', 'R0801') + self.assertFalse('similarities' in [c.name for c in self.linter.prepare_checkers()]) + + def test_disable_alot(self): + """check that we disabled a lot of checkers""" + self.linter.set_option('reports', False) + self.linter.set_option('disable', 'R,C,W') + checker_names = [c.name for c in self.linter.prepare_checkers()] + for cname in ('design', 'metrics', 'similarities'): + self.assertFalse(cname in checker_names, cname) + + def test_addmessage(self): + self.linter.set_reporter(TestReporter()) + self.linter.open() + self.linter.set_current_module('0123') + self.linter.add_message('C0301', line=1, args=(1, 2)) + self.linter.add_message('line-too-long', line=2, args=(3, 4)) + self.assertEqual( + ['C: 1: Line too long (1/2)', 'C: 2: Line too long (3/4)'], + self.linter.reporter.messages) + + def test_init_hooks_called_before_load_plugins(self): + self.assertRaises(RuntimeError, + Run, ['--load-plugins', 'unexistant', '--init-hook', 'raise RuntimeError']) + self.assertRaises(RuntimeError, + Run, ['--init-hook', 'raise RuntimeError', '--load-plugins', 'unexistant']) + + + def test_analyze_explicit_script(self): + self.linter.set_reporter(TestReporter()) + self.linter.check(os.path.join(os.path.dirname(__file__), 'data', 'ascript')) + self.assertEqual( + ['C: 2: Line too long (175/100)'], + self.linter.reporter.messages) + + def test_html_reporter_missing_files(self): + output = six.StringIO() + self.linter.set_reporter(html.HTMLReporter(output)) + self.linter.set_option('output-format', 'html') + self.linter.check('troppoptop.py') + self.linter.generate_reports() + value = output.getvalue() + self.assertIn('troppoptop.py', value) + self.assertIn('fatal', value) + + def test_python3_checker_disabled(self): + checker_names = [c.name for c in self.linter.prepare_checkers()] + self.assertNotIn('python3', checker_names) + + self.linter.set_option('enable', 'python3') + checker_names = [c.name for c in self.linter.prepare_checkers()] + self.assertIn('python3', checker_names) + + +class ConfigTC(unittest.TestCase): + + def setUp(self): + os.environ.pop('PYLINTRC', None) + + def test_pylint_home(self): + uhome = os.path.expanduser('~') + if uhome == '~': + expected = '.pylint.d' + else: + expected = os.path.join(uhome, '.pylint.d') + self.assertEqual(config.PYLINT_HOME, expected) + + try: + pylintd = join(tempfile.gettempdir(), '.pylint.d') + os.environ['PYLINTHOME'] = pylintd + try: + reload_module(config) + self.assertEqual(config.PYLINT_HOME, pylintd) + finally: + try: + os.remove(pylintd) + except: + pass + finally: + del os.environ['PYLINTHOME'] + + def test_pylintrc(self): + with fake_home(): + try: + self.assertEqual(config.find_pylintrc(), None) + os.environ['PYLINTRC'] = join(tempfile.gettempdir(), + '.pylintrc') + self.assertEqual(config.find_pylintrc(), None) + os.environ['PYLINTRC'] = '.' + self.assertEqual(config.find_pylintrc(), None) + finally: + reload_module(config) + + def test_pylintrc_parentdir(self): + with tempdir() as chroot: + + create_files(['a/pylintrc', 'a/b/__init__.py', 'a/b/pylintrc', + 'a/b/c/__init__.py', 'a/b/c/d/__init__.py', + 'a/b/c/d/e/.pylintrc']) + with fake_home(): + self.assertEqual(config.find_pylintrc(), None) + results = {'a' : join(chroot, 'a', 'pylintrc'), + 'a/b' : join(chroot, 'a', 'b', 'pylintrc'), + 'a/b/c' : join(chroot, 'a', 'b', 'pylintrc'), + 'a/b/c/d' : join(chroot, 'a', 'b', 'pylintrc'), + 'a/b/c/d/e' : join(chroot, 'a', 'b', 'c', 'd', 'e', '.pylintrc'), + } + for basedir, expected in results.items(): + os.chdir(join(chroot, basedir)) + self.assertEqual(config.find_pylintrc(), expected) + + def test_pylintrc_parentdir_no_package(self): + with tempdir() as chroot: + with fake_home(): + create_files(['a/pylintrc', 'a/b/pylintrc', 'a/b/c/d/__init__.py']) + self.assertEqual(config.find_pylintrc(), None) + results = {'a' : join(chroot, 'a', 'pylintrc'), + 'a/b' : join(chroot, 'a', 'b', 'pylintrc'), + 'a/b/c' : None, + 'a/b/c/d' : None, + } + for basedir, expected in results.items(): + os.chdir(join(chroot, basedir)) + self.assertEqual(config.find_pylintrc(), expected) + + +class PreprocessOptionsTC(unittest.TestCase): + def _callback(self, name, value): + self.args.append((name, value)) + + def test_value_equal(self): + self.args = [] + preprocess_options(['--foo', '--bar=baz', '--qu=ux'], + {'foo' : (self._callback, False), + 'qu' : (self._callback, True)}) + self.assertEqual( + [('foo', None), ('qu', 'ux')], self.args) + + def test_value_space(self): + self.args = [] + preprocess_options(['--qu', 'ux'], + {'qu' : (self._callback, True)}) + self.assertEqual( + [('qu', 'ux')], self.args) + + def test_error_missing_expected_value(self): + self.assertRaises( + ArgumentPreprocessingError, + preprocess_options, + ['--foo', '--bar', '--qu=ux'], + {'bar' : (None, True)}) + self.assertRaises( + ArgumentPreprocessingError, + preprocess_options, + ['--foo', '--bar'], + {'bar' : (None, True)}) + + def test_error_unexpected_value(self): + self.assertRaises( + ArgumentPreprocessingError, + preprocess_options, + ['--foo', '--bar=spam', '--qu=ux'], + {'bar' : (None, False)}) + + +class MessagesStoreTC(unittest.TestCase): + def setUp(self): + self.store = MessagesStore() + class Checker(object): + name = 'achecker' + msgs = { + 'W1234': ('message', 'msg-symbol', 'msg description.', + {'old_names': [('W0001', 'old-symbol')]}), + 'E1234': ('Duplicate keyword argument %r in %s call', + 'duplicate-keyword-arg', + 'Used when a function call passes the same keyword argument multiple times.', + {'maxversion': (2, 6)}), + } + self.store.register_messages(Checker()) + + def _compare_messages(self, desc, msg, checkerref=False): + self.assertMultiLineEqual(desc, msg.format_help(checkerref=checkerref)) + + def test_check_message_id(self): + self.assertIsInstance(self.store.check_message_id('W1234'), + MessageDefinition) + self.assertRaises(UnknownMessage, + self.store.check_message_id, 'YB12') + + def test_message_help(self): + msg = self.store.check_message_id('W1234') + self._compare_messages( + ''':msg-symbol (W1234): *message* + msg description. This message belongs to the achecker checker.''', + msg, checkerref=True) + self._compare_messages( + ''':msg-symbol (W1234): *message* + msg description.''', + msg, checkerref=False) + + def test_message_help_minmax(self): + # build the message manually to be python version independant + msg = self.store.check_message_id('E1234') + self._compare_messages( + ''':duplicate-keyword-arg (E1234): *Duplicate keyword argument %r in %s call* + Used when a function call passes the same keyword argument multiple times. + This message belongs to the achecker checker. It can't be emitted when using + Python >= 2.6.''', + msg, checkerref=True) + self._compare_messages( + ''':duplicate-keyword-arg (E1234): *Duplicate keyword argument %r in %s call* + Used when a function call passes the same keyword argument multiple times. + This message can't be emitted when using Python >= 2.6.''', + msg, checkerref=False) + + def test_list_messages(self): + sys.stdout = six.StringIO() + try: + self.store.list_messages() + output = sys.stdout.getvalue() + finally: + sys.stdout = sys.__stdout__ + # cursory examination of the output: we're mostly testing it completes + self.assertIn(':msg-symbol (W1234): *message*', output) + + def test_add_renamed_message(self): + self.store.add_renamed_message('W1234', 'old-bad-name', 'msg-symbol') + self.assertEqual('msg-symbol', + self.store.check_message_id('W1234').symbol) + self.assertEqual('msg-symbol', + self.store.check_message_id('old-bad-name').symbol) + + def test_renamed_message_register(self): + self.assertEqual('msg-symbol', + self.store.check_message_id('W0001').symbol) + self.assertEqual('msg-symbol', + self.store.check_message_id('old-symbol').symbol) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_pyreverse_diadefs.py b/pymode/libs/pylint/test/unittest_pyreverse_diadefs.py new file mode 100644 index 00000000..0df6ac4f --- /dev/null +++ b/pymode/libs/pylint/test/unittest_pyreverse_diadefs.py @@ -0,0 +1,171 @@ +# Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" +unittest for the extensions.diadefslib modules +""" + +import unittest +import sys + +import six + +import astroid +from astroid import MANAGER + +from pylint.pyreverse.inspector import Linker +from pylint.pyreverse.diadefslib import * + +from unittest_pyreverse_writer import Config, get_project + +PROJECT = get_project('data') +HANDLER = DiadefsHandler(Config()) + +def _process_classes(classes): + """extract class names of a list""" + return sorted([(isinstance(c.node, astroid.ClassDef), c.title) for c in classes]) + +def _process_relations(relations): + """extract relation indices from a relation list""" + result = [] + for rel_type, rels in six.iteritems(relations): + for rel in rels: + result.append( (rel_type, rel.from_object.title, + rel.to_object.title) ) + result.sort() + return result + + +class DiaDefGeneratorTC(unittest.TestCase): + def test_option_values(self): + """test for ancestor, associated and module options""" + handler = DiadefsHandler(Config()) + df_h = DiaDefGenerator(Linker(PROJECT), handler) + cl_config = Config() + cl_config.classes = ['Specialization'] + cl_h = DiaDefGenerator(Linker(PROJECT), DiadefsHandler(cl_config) ) + self.assertEqual( (0, 0), df_h._get_levels()) + self.assertEqual( False, df_h.module_names) + self.assertEqual( (-1, -1), cl_h._get_levels()) + self.assertEqual( True, cl_h.module_names) + for hndl in [df_h, cl_h]: + hndl.config.all_ancestors = True + hndl.config.all_associated = True + hndl.config.module_names = True + hndl._set_default_options() + self.assertEqual( (-1, -1), hndl._get_levels()) + self.assertEqual( True, hndl.module_names) + handler = DiadefsHandler( Config()) + df_h = DiaDefGenerator(Linker(PROJECT), handler) + cl_config = Config() + cl_config.classes = ['Specialization'] + cl_h = DiaDefGenerator(Linker(PROJECT), DiadefsHandler(cl_config) ) + for hndl in [df_h, cl_h]: + hndl.config.show_ancestors = 2 + hndl.config.show_associated = 1 + hndl.config.module_names = False + hndl._set_default_options() + self.assertEqual( (2, 1), hndl._get_levels()) + self.assertEqual( False, hndl.module_names) + #def test_default_values(self): + """test efault values for package or class diagrams""" + # TODO : should test difference between default values for package + # or class diagrams + +class DefaultDiadefGeneratorTC(unittest.TestCase): + def test_known_values1(self): + dd = DefaultDiadefGenerator(Linker(PROJECT), HANDLER).visit(PROJECT) + self.assertEqual(len(dd), 2) + keys = [d.TYPE for d in dd] + self.assertEqual(keys, ['package', 'class']) + pd = dd[0] + self.assertEqual(pd.title, 'packages No Name') + modules = sorted([(isinstance(m.node, astroid.Module), m.title) + for m in pd.objects]) + self.assertEqual(modules, [(True, 'data'), + (True, 'data.clientmodule_test'), + (True, 'data.suppliermodule_test')]) + cd = dd[1] + self.assertEqual(cd.title, 'classes No Name') + classes = _process_classes(cd.objects) + self.assertEqual(classes, [(True, 'Ancestor'), + (True, 'DoNothing'), + (True, 'Interface'), + (True, 'Specialization')] + ) + + _should_rels = [('association', 'DoNothing', 'Ancestor'), + ('association', 'DoNothing', 'Specialization'), + ('implements', 'Ancestor', 'Interface'), + ('specialization', 'Specialization', 'Ancestor')] + def test_exctract_relations(self): + """test extract_relations between classes""" + cd = DefaultDiadefGenerator(Linker(PROJECT), HANDLER).visit(PROJECT)[1] + cd.extract_relationships() + relations = _process_relations(cd.relationships) + self.assertEqual(relations, self._should_rels) + + def test_functional_relation_extraction(self): + """functional test of relations extraction; + different classes possibly in different modules""" + # XXX should be catching pyreverse environnement problem but doesn't + # pyreverse doesn't extracts the relations but this test ok + project = get_project('data') + handler = DiadefsHandler(Config()) + diadefs = handler.get_diadefs(project, Linker(project, tag=True) ) + cd = diadefs[1] + relations = _process_relations(cd.relationships) + self.assertEqual(relations, self._should_rels) + + def test_known_values2(self): + project = get_project('data.clientmodule_test') + dd = DefaultDiadefGenerator(Linker(project), HANDLER).visit(project) + self.assertEqual(len(dd), 1) + keys = [d.TYPE for d in dd] + self.assertEqual(keys, ['class']) + cd = dd[0] + self.assertEqual(cd.title, 'classes No Name') + classes = _process_classes(cd.objects) + self.assertEqual(classes, [(True, 'Ancestor'), + (True, 'DoNothing'), + (True, 'Specialization')] + ) + +class ClassDiadefGeneratorTC(unittest.TestCase): + def test_known_values1(self): + HANDLER.config.classes = ['Specialization'] + cdg = ClassDiadefGenerator(Linker(PROJECT), HANDLER) + special = 'data.clientmodule_test.Specialization' + cd = cdg.class_diagram(PROJECT, special) + self.assertEqual(cd.title, special) + classes = _process_classes(cd.objects) + self.assertEqual(classes, [(True, 'data.clientmodule_test.Ancestor'), + (True, special), + (True, 'data.suppliermodule_test.DoNothing'), + ]) + + def test_known_values2(self): + HANDLER.config.module_names = False + cd = ClassDiadefGenerator(Linker(PROJECT), HANDLER).class_diagram(PROJECT, 'data.clientmodule_test.Specialization') + self.assertEqual(cd.title, 'data.clientmodule_test.Specialization') + classes = _process_classes(cd.objects) + self.assertEqual(classes, [(True, 'Ancestor'), + (True, 'DoNothing'), + (True, 'Specialization') + ]) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_pyreverse_inspector.py b/pymode/libs/pylint/test/unittest_pyreverse_inspector.py new file mode 100644 index 00000000..f709fe65 --- /dev/null +++ b/pymode/libs/pylint/test/unittest_pyreverse_inspector.py @@ -0,0 +1,136 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser 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. +""" + for the visitors.diadefs module +""" +import os +import unittest + +import astroid +from astroid import nodes +from astroid import bases +from astroid import manager +from astroid import test_utils + +from pylint.pyreverse import inspector +from unittest_pyreverse_writer import get_project + +MANAGER = manager.AstroidManager() + +def astroid_wrapper(func, modname): + return func(modname) + + +class LinkerTest(unittest.TestCase): + + def setUp(self): + super(LinkerTest, self).setUp() + self.project = get_project('data', 'data') + self.linker = inspector.Linker(self.project) + self.linker.visit(self.project) + + def test_class_implements(self): + klass = self.project.get_module('data.clientmodule_test')['Ancestor'] + self.assertTrue(hasattr(klass, 'implements')) + self.assertEqual(len(klass.implements), 1) + self.assertTrue(isinstance(klass.implements[0], nodes.ClassDef)) + self.assertEqual(klass.implements[0].name, "Interface") + klass = self.project.get_module('data.clientmodule_test')['Specialization'] + self.assertTrue(hasattr(klass, 'implements')) + self.assertEqual(len(klass.implements), 0) + + def test_locals_assignment_resolution(self): + klass = self.project.get_module('data.clientmodule_test')['Specialization'] + self.assertTrue(hasattr(klass, 'locals_type')) + type_dict = klass.locals_type + self.assertEqual(len(type_dict), 2) + keys = sorted(type_dict.keys()) + self.assertEqual(keys, ['TYPE', 'top']) + self.assertEqual(len(type_dict['TYPE']), 1) + self.assertEqual(type_dict['TYPE'][0].value, 'final class') + self.assertEqual(len(type_dict['top']), 1) + self.assertEqual(type_dict['top'][0].value, 'class') + + def test_instance_attrs_resolution(self): + klass = self.project.get_module('data.clientmodule_test')['Specialization'] + self.assertTrue(hasattr(klass, 'instance_attrs_type')) + type_dict = klass.instance_attrs_type + self.assertEqual(len(type_dict), 2) + keys = sorted(type_dict.keys()) + self.assertEqual(keys, ['_id', 'relation']) + self.assertTrue(isinstance(type_dict['relation'][0], bases.Instance), + type_dict['relation']) + self.assertEqual(type_dict['relation'][0].name, 'DoNothing') + self.assertIs(type_dict['_id'][0], astroid.YES) + + def test_concat_interfaces(self): + cls = test_utils.extract_node(''' + class IMachin: pass + + class Correct2: + """docstring""" + __implements__ = (IMachin,) + + class BadArgument: + """docstring""" + __implements__ = (IMachin,) + + class InterfaceCanNowBeFound: #@ + """docstring""" + __implements__ = BadArgument.__implements__ + Correct2.__implements__ + ''') + interfaces = inspector.interfaces(cls) + self.assertEqual([i.name for i in interfaces], ['IMachin']) + + def test_interfaces(self): + module = astroid.parse(''' + class Interface(object): pass + class MyIFace(Interface): pass + class AnotherIFace(Interface): pass + class Concrete0(object): + __implements__ = MyIFace + class Concrete1: + __implements__ = (MyIFace, AnotherIFace) + class Concrete2: + __implements__ = (MyIFace, AnotherIFace) + class Concrete23(Concrete1): pass + ''') + + for klass, interfaces in (('Concrete0', ['MyIFace']), + ('Concrete1', ['MyIFace', 'AnotherIFace']), + ('Concrete2', ['MyIFace', 'AnotherIFace']), + ('Concrete23', ['MyIFace', 'AnotherIFace'])): + klass = module[klass] + self.assertEqual([i.name for i in inspector.interfaces(klass)], + interfaces) + + + def test_from_directory(self): + expected = os.path.join('pylint', 'test', 'data', '__init__.py') + self.assertEqual(self.project.name, 'data') + self.assertTrue(self.project.path.endswith(expected), self.project.path) + + def test_project_node(self): + expected = [ + 'data', 'data.clientmodule_test', + 'data.suppliermodule_test', + ] + self.assertListEqual(sorted(self.project.keys()), expected) + + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_pyreverse_writer.py b/pymode/libs/pylint/test/unittest_pyreverse_writer.py new file mode 100644 index 00000000..e473627f --- /dev/null +++ b/pymode/libs/pylint/test/unittest_pyreverse_writer.py @@ -0,0 +1,134 @@ +# Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" +unittest for visitors.diadefs and extensions.diadefslib modules +""" + + +import os +import sys +import codecs +from os.path import join, dirname, abspath +from difflib import unified_diff +import unittest + +from astroid import MANAGER + +from pylint.pyreverse.inspector import Linker, project_from_files +from pylint.pyreverse.diadefslib import DefaultDiadefGenerator, DiadefsHandler +from pylint.pyreverse.writer import DotWriter +from pylint.pyreverse.utils import get_visibility + + +_DEFAULTS = { + 'all_ancestors': None, 'show_associated': None, + 'module_names': None, + 'output_format': 'dot', 'diadefs_file': None, 'quiet': 0, + 'show_ancestors': None, 'classes': (), 'all_associated': None, + 'mode': 'PUB_ONLY', 'show_builtin': False, 'only_classnames': False + } + +class Config(object): + """config object for tests""" + def __init__(self): + for attr, value in _DEFAULTS.items(): + setattr(self, attr, value) + + +def _file_lines(path): + # we don't care about the actual encoding, but python3 forces us to pick one + with codecs.open(path, encoding='latin1') as stream: + lines = [line.strip() for line in stream.readlines() + if (line.find('squeleton generated by ') == -1 and + not line.startswith('__revision__ = "$Id:'))] + return [line for line in lines if line] + +def get_project(module, name="No Name"): + """return a astroid project representation""" + def _astroid_wrapper(func, modname): + return func(modname) + return project_from_files([module], _astroid_wrapper, + project_name=name) + +CONFIG = Config() + +class DotWriterTC(unittest.TestCase): + + @classmethod + def setUpClass(cls): + project = get_project(os.path.join(os.path.dirname(__file__), 'data')) + linker = Linker(project) + handler = DiadefsHandler(CONFIG) + dd = DefaultDiadefGenerator(linker, handler).visit(project) + for diagram in dd: + diagram.extract_relationships() + writer = DotWriter(CONFIG) + writer.write(dd) + + @classmethod + def tearDownClass(cls): + for fname in ('packages_No_Name.dot', 'classes_No_Name.dot',): + try: + os.remove(fname) + except: + continue + + def _test_same_file(self, generated_file): + expected_file = os.path.join(os.path.dirname(__file__), 'data', generated_file) + generated = _file_lines(generated_file) + expected = _file_lines(expected_file) + generated = '\n'.join(generated) + expected = '\n'.join(expected) + files = "\n *** expected : %s, generated : %s \n" % ( + expected_file, generated_file) + self.assertEqual(expected, generated, '%s%s' % ( + files, '\n'.join(line for line in unified_diff( + expected.splitlines(), generated.splitlines() ))) ) + os.remove(generated_file) + + def test_package_diagram(self): + self._test_same_file('packages_No_Name.dot') + + def test_class_diagram(self): + self._test_same_file('classes_No_Name.dot') + + + +class GetVisibilityTC(unittest.TestCase): + + def test_special(self): + for name in ["__reduce_ex__", "__setattr__"]: + self.assertEqual(get_visibility(name), 'special') + + def test_private(self): + for name in ["__g_", "____dsf", "__23_9"]: + got = get_visibility(name) + self.assertEqual(got, 'private', + 'got %s instead of private for value %s' % (got, name)) + + def test_public(self): + self.assertEqual(get_visibility('simple'), 'public') + + def test_protected(self): + for name in ["_","__", "___", "____", "_____", "___e__", + "_nextsimple", "_filter_it_"]: + got = get_visibility(name) + self.assertEqual(got, 'protected', + 'got %s instead of protected for value %s' % (got, name)) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_reporters_json.py b/pymode/libs/pylint/test/unittest_reporters_json.py new file mode 100644 index 00000000..c5514307 --- /dev/null +++ b/pymode/libs/pylint/test/unittest_reporters_json.py @@ -0,0 +1,62 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""Test for the JSON reporter.""" + +import json +import unittest + +import six + +from pylint.lint import PyLinter +from pylint import checkers +from pylint.reporters.json import JSONReporter + + +class TestJSONReporter(unittest.TestCase): + + def test_simple_json_output(self): + output = six.StringIO() + + reporter = JSONReporter() + linter = PyLinter(reporter=reporter) + checkers.initialize(linter) + + linter.config.persistent = 0 + linter.reporter.set_output(output) + linter.open() + linter.set_current_module('0123') + linter.add_message('line-too-long', line=1, args=(1, 2)) + + # we call this method because we didn't actually run the checkers + reporter.display_messages(None) + + expected_result = [[ + ("column", 0), + ("line", 1), + ("message", "Line too long (1/2)"), + ("module", "0123"), + ("obj", ""), + ("path", "0123"), + ("symbol", "line-too-long"), + ("type", "convention"), + ]] + report_result = json.loads(output.getvalue()) + report_result = [sorted(report_result[0].items(), + key=lambda item: item[0])] + self.assertEqual(report_result, expected_result) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_reporting.py b/pymode/libs/pylint/test/unittest_reporting.py new file mode 100644 index 00000000..38dcf620 --- /dev/null +++ b/pymode/libs/pylint/test/unittest_reporting.py @@ -0,0 +1,194 @@ +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import os +from os.path import join, dirname, abspath +import unittest +import warnings + +import six + +from pylint import __pkginfo__ +from pylint.lint import PyLinter +from pylint import checkers +from pylint.reporters import BaseReporter +from pylint.reporters.text import TextReporter, ParseableTextReporter +from pylint.reporters.html import HTMLReporter +from pylint.reporters.ureports.nodes import Section + + +HERE = abspath(dirname(__file__)) +INPUTDIR = join(HERE, 'input') + +class PyLinterTC(unittest.TestCase): + + def setUp(self): + self.linter = PyLinter(reporter=TextReporter()) + self.linter.disable('I') + self.linter.config.persistent = 0 + # register checkers + checkers.initialize(self.linter) + os.environ.pop('PYLINTRC', None) + + def test_add_message_is_deprecated(self): + if __pkginfo__.numversion >= (1, 6, 0): + with self.assertRaises(AttributeError): + BaseReporter().add_message + + with warnings.catch_warnings(record=True) as cm: + warnings.simplefilter("always") + BaseReporter().add_message(None, None, None) + + self.assertEqual(len(cm), 1) + self.assertIsInstance(cm[0].message, DeprecationWarning) + msg = ('This method is deprecated, use handle_message ' + 'instead. It will be removed in Pylint 1.6.') + self.assertEqual(str(cm[0].message), msg) + + def test_template_option(self): + output = six.StringIO() + self.linter.reporter.set_output(output) + self.linter.set_option('msg-template', '{msg_id}:{line:03d}') + self.linter.open() + self.linter.set_current_module('0123') + self.linter.add_message('C0301', line=1, args=(1, 2)) + self.linter.add_message('line-too-long', line=2, args=(3, 4)) + self.assertMultiLineEqual(output.getvalue(), + '************* Module 0123\n' + 'C0301:001\n' + 'C0301:002\n') + + def test_parseable_output_deprecated(self): + with warnings.catch_warnings(record=True) as cm: + warnings.simplefilter("always") + ParseableTextReporter() + + self.assertEqual(len(cm), 1) + self.assertIsInstance(cm[0].message, DeprecationWarning) + + def test_parseable_output_regression(self): + output = six.StringIO() + with warnings.catch_warnings(record=True): + linter = PyLinter(reporter=ParseableTextReporter()) + + checkers.initialize(linter) + linter.config.persistent = 0 + linter.reporter.set_output(output) + linter.set_option('output-format', 'parseable') + linter.open() + linter.set_current_module('0123') + linter.add_message('line-too-long', line=1, args=(1, 2)) + self.assertMultiLineEqual(output.getvalue(), + '************* Module 0123\n' + '0123:1: [C0301(line-too-long), ] ' + 'Line too long (1/2)\n') + + def test_html_reporter_msg_template(self): + expected = ''' + + +
+
+

Messages

+ + + + + + + + + +
categorymsg_id
warningW0332
+
+
+ +'''.strip().splitlines() + output = six.StringIO() + linter = PyLinter(reporter=HTMLReporter()) + checkers.initialize(linter) + linter.config.persistent = 0 + linter.reporter.set_output(output) + linter.set_option('msg-template', '{category}{msg_id}') + linter.open() + linter.set_current_module('0123') + linter.add_message('lowercase-l-suffix', line=1) + linter.reporter.display_messages(Section()) + self.assertEqual(output.getvalue().splitlines(), expected) + + @unittest.expectedFailure + def test_html_reporter_type(self): + # Integration test for issue #263 + # https://bitbucket.org/logilab/pylint/issue/263/html-report-type-problems + expected = ''' + +
+
+

Messages

+ + + + + + + + + + + + + + + + + +
typemoduleobjectlinecol_offsetmessage
convention0123 10Exactly one space required before comparison +a< 5: print "zero"
+
+
+ + +''' + output = six.StringIO() + linter = PyLinter(reporter=HTMLReporter()) + checkers.initialize(linter) + linter.config.persistent = 0 + linter.reporter.set_output(output) + linter.open() + linter.set_current_module('0123') + linter.add_message('bad-whitespace', line=1, + args=('Exactly one', 'required', 'before', + 'comparison', 'a< 5: print "zero"')) + linter.reporter.display_reports(Section()) + self.assertMultiLineEqual(output.getvalue(), expected) + + def test_display_results_is_renamed(self): + class CustomReporter(TextReporter): + def _display(self, layout): + return None + + reporter = CustomReporter() + if __pkginfo__.numversion >= (2, 0): + with self.assertRaises(AttributeError): + reporter.display_results + else: + with warnings.catch_warnings(record=True) as cm: + warnings.simplefilter("always") + reporter.display_results(Section()) + + self.assertEqual(len(cm), 1) + self.assertIsInstance(cm[0].message, DeprecationWarning) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/test/unittest_utils.py b/pymode/libs/pylint/test/unittest_utils.py new file mode 100644 index 00000000..8fff282e --- /dev/null +++ b/pymode/libs/pylint/test/unittest_utils.py @@ -0,0 +1,94 @@ +# Copyright 2013 Google Inc. +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import unittest +import warnings + +import astroid + +from pylint import __pkginfo__ +from pylint import utils +from pylint import interfaces +from pylint.checkers.utils import check_messages + + +class PyLintASTWalkerTest(unittest.TestCase): + class MockLinter(object): + def __init__(self, msgs): + self._msgs = msgs + + def is_message_enabled(self, msgid): + return self._msgs.get(msgid, True) + + class Checker(object): + def __init__(self): + self.called = set() + + @check_messages('first-message') + def visit_module(self, module): + self.called.add('module') + + @check_messages('second-message') + def visit_call(self, module): + raise NotImplementedError + + @check_messages('second-message', 'third-message') + def visit_assignname(self, module): + self.called.add('assname') + + @check_messages('second-message') + def leave_assignname(self, module): + raise NotImplementedError + + def test_check_messages(self): + linter = self.MockLinter({'first-message': True, + 'second-message': False, + 'third-message': True}) + walker = utils.PyLintASTWalker(linter) + checker = self.Checker() + walker.add_checker(checker) + walker.walk(astroid.parse("x = func()")) + self.assertEqual(set(['module', 'assname']), checker.called) + + def test_deprecated_methods(self): + class Checker(object): + def __init__(self): + self.called = False + + @check_messages('first-message') + def visit_assname(self, node): + self.called = True + + linter = self.MockLinter({'first-message': True}) + walker = utils.PyLintASTWalker(linter) + checker = Checker() + walker.add_checker(checker) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + walker.walk(astroid.parse("x = 1")) + + if __pkginfo__.numversion < (2, 0): + expected = ('Implemented method visit_assname instead of ' + 'visit_assignname. This will be supported until ' + 'Pylint 2.0.') + self.assertEqual(len(w), 1) + self.assertIsInstance(w[0].message, PendingDeprecationWarning) + self.assertEqual(str(w[0].message), expected) + self.assertTrue(checker.called) + else: + self.assertNotEqual(len(w), 1) + self.assertFalse(checker.called) + + +if __name__ == '__main__': + unittest.main() diff --git a/pymode/libs/pylint/testutils.py b/pymode/libs/pylint/testutils.py index 2ea440d6..f680493c 100644 --- a/pymode/libs/pylint/testutils.py +++ b/pymode/libs/pylint/testutils.py @@ -19,27 +19,26 @@ import collections import contextlib import functools +from glob import glob import os +from os import linesep, getcwd, sep +from os.path import abspath, basename, dirname, isdir, join, splitext import sys import re import unittest import tempfile import tokenize -from glob import glob -from os import linesep, getcwd, sep -from os.path import abspath, basename, dirname, isdir, join, splitext - -from astroid import test_utils +import six +from six.moves import StringIO +import astroid from pylint import checkers from pylint.utils import PyLintASTWalker from pylint.reporters import BaseReporter from pylint.interfaces import IReporter from pylint.lint import PyLinter -import six -from six.moves import StringIO # Utils @@ -126,9 +125,11 @@ def finalize(self): self.reset() return result - def display_results(self, layout): + def display_reports(self, layout): """ignore layouts""" + _display = None + class Message(collections.namedtuple('Message', ['msg_id', 'line', 'node', 'args'])): @@ -168,9 +169,9 @@ def options_providers(self): def set_config(**kwargs): """Decorator for setting config values on a checker.""" - def _Wrapper(fun): + def _wrapper(fun): @functools.wraps(fun) - def _Forward(self): + def _forward(self): for key, value in six.iteritems(kwargs): setattr(self.checker.config, key, value) if isinstance(self, CheckerTestCase): @@ -178,8 +179,8 @@ def _Forward(self): self.checker.open() fun(self) - return _Forward - return _Wrapper + return _forward + return _wrapper class CheckerTestCase(unittest.TestCase): @@ -228,7 +229,6 @@ def walk(self, node): linter.set_reporter(test_reporter) linter.config.persistent = 0 checkers.initialize(linter) -linter.global_set_option('required-attributes', ('__revision__',)) if linesep != '\n': LINE_RGX = re.compile(linesep) @@ -271,6 +271,7 @@ def shortDescription(self): def test_functionality(self): tocheck = [self.package+'.'+self.module] + # pylint: disable=not-an-iterable; can't handle boolean checks for now if self.depends: tocheck += [self.package+'.%s' % name.replace('.py', '') for name, _ in self.depends] @@ -316,8 +317,9 @@ def test_functionality(self): if not isdir(importable): importable += '.py' tocheck = [importable] + # pylint: disable=not-an-iterable; can't handle boolean checks for now if self.depends: - tocheck += [join(self.INPUT_DIR, name) for name, _file in self.depends] + tocheck += [join(self.INPUT_DIR, name) for name, _ in self.depends] self._test(tocheck) class LintTestUpdate(LintTestUsingModule): @@ -363,7 +365,7 @@ def make_tests(input_dir, msg_dir, filter_rgx, callbacks): for module_file, messages_file in ( get_tests_info(input_dir, msg_dir, 'func_', '') ): - if not is_to_run(module_file) or module_file.endswith('.pyc'): + if not is_to_run(module_file) or module_file.endswith(('.pyc', "$py.class")): continue base = module_file.replace('func_', '').replace('.py', '') @@ -390,23 +392,23 @@ def create_tempfile(content=None): # Can't use tempfile.NamedTemporaryFile here # because on Windows the file must be closed before writing to it, # see http://bugs.python.org/issue14243 - fd, tmp = tempfile.mkstemp() + file_handle, tmp = tempfile.mkstemp() if content: if sys.version_info >= (3, 0): # erff - os.write(fd, bytes(content, 'ascii')) + os.write(file_handle, bytes(content, 'ascii')) else: - os.write(fd, content) + os.write(file_handle, content) try: yield tmp finally: - os.close(fd) + os.close(file_handle) os.remove(tmp) @contextlib.contextmanager def create_file_backed_module(code): """Create an astroid module for the given code, backed by a real file.""" with create_tempfile() as temp: - module = test_utils.build_module(code) + module = astroid.parse(code) module.file = temp yield module diff --git a/pymode/libs/pylint/utils.py b/pymode/libs/pylint/utils.py index 6c5b9754..d2bca89e 100644 --- a/pymode/libs/pylint/utils.py +++ b/pymode/libs/pylint/utils.py @@ -19,26 +19,24 @@ from __future__ import print_function import collections +import itertools import os +from os.path import dirname, basename, splitext, exists, isdir, join, normpath import re import sys import tokenize import warnings -from os.path import dirname, basename, splitext, exists, isdir, join, normpath +import textwrap import six from six.moves import zip # pylint: disable=redefined-builtin -from logilab.common.interface import implements -from logilab.common.textutils import normalize_text -from logilab.common.configuration import rest_format_section -from logilab.common.ureports import Section - from astroid import nodes, Module from astroid.modutils import modpath_from_file, get_module_files, \ file_from_modpath, load_module_from_file -from pylint.interfaces import IRawChecker, ITokenChecker, UNDEFINED +from pylint.interfaces import IRawChecker, ITokenChecker, UNDEFINED, implements +from pylint.reporters.ureports.nodes import Section class UnknownMessage(Exception): @@ -67,12 +65,29 @@ class EmptyReport(Exception): 'F' : 1 } +DEPRECATED_ALIASES = { + # New name, deprecated name. + 'repr': 'backquote', + 'expr': 'discard', + 'assignname': 'assname', + 'assignattr': 'assattr', + 'attribute': 'getattr', + 'call': 'callfunc', + 'importfrom': 'from', + 'classdef': 'class', + 'functiondef': 'function', + 'generatorexp': 'genexpr', +} + _MSG_ORDER = 'EWRCIF' MSG_STATE_SCOPE_CONFIG = 0 MSG_STATE_SCOPE_MODULE = 1 MSG_STATE_CONFIDENCE = 2 -OPTION_RGX = re.compile(r'\s*#.*\bpylint:(.*)') +# Allow stopping after the first semicolon encountered, +# so that an option can be continued with the reasons +# why it is active or disabled. +OPTION_RGX = re.compile(r'\s*#.*\bpylint:\s*([^;]+);{0,1}') # The line/node distinction does not apply to fatal errors and reports. _SCOPE_EXEMPT = 'FR' @@ -128,9 +143,8 @@ def category_id(cid): return MSG_TYPES_LONG.get(cid) -def _decoding_readline(stream, module): - return lambda: stream.readline().decode(module.file_encoding, - 'replace') +def _decoding_readline(stream, encoding): + return lambda: stream.readline().decode(encoding, 'replace') def tokenize_module(module): @@ -138,7 +152,8 @@ def tokenize_module(module): readline = stream.readline if sys.version_info < (3, 0): if module.file_encoding is not None: - readline = _decoding_readline(stream, module) + readline = _decoding_readline(stream, module.file_encoding) + return list(tokenize.generate_tokens(readline)) return list(tokenize.tokenize(readline)) @@ -209,7 +224,7 @@ def format_help(self, checkerref=False): desc += " It can't be emitted when using Python %s." % restr else: desc += " This message can't be emitted when using Python %s." % restr - desc = normalize_text(' '.join(desc.split()), indent=' ') + desc = _normalize_text(' '.join(desc.split()), indent=' ') if title != '%s': title = title.splitlines()[0] return ':%s: *%s*\n%s' % (msgid, title, desc) @@ -275,12 +290,32 @@ def disable(self, msgid, scope='package', line=None, ignore_unknown=False): msgs = self._msgs_state msgs[msg.msgid] = False # sync configuration object - self.config.disable = [mid for mid, val in six.iteritems(msgs) + self.config.disable = [self._message_symbol(mid) + for mid, val in six.iteritems(msgs) if not val] + def _message_symbol(self, msgid): + """Get the message symbol of the given message id + + Return the original message id if the message does not + exist. + """ + try: + return self.msgs_store.check_message_id(msgid).symbol + except UnknownMessage: + return msgid + def enable(self, msgid, scope='package', line=None, ignore_unknown=False): """reenable message of the given id""" assert scope in ('package', 'module') + if msgid == 'all': + for msgid_ in MSG_TYPES: + self.enable(msgid_, scope=scope, line=line) + if not self._python3_porting_mode: + # Don't activate the python 3 porting checker if it + # wasn't activated explicitly. + self.disable('python3') + return catid = category_id(msgid) # msgid is a category? if catid is not None: @@ -428,7 +463,7 @@ def print_full_documentation(self): title = '%s options' % section.capitalize() print(title) print('~' * len(title)) - rest_format_section(sys.stdout, None, options) + _rest_format_section(sys.stdout, None, options) print("") else: try: @@ -463,7 +498,7 @@ def print_full_documentation(self): title = 'Options' print(title) print('^' * len(title)) - rest_format_section(sys.stdout, None, options) + _rest_format_section(sys.stdout, None, options) print("") if msgs: title = 'Messages' @@ -526,36 +561,39 @@ def _collect_block_lines(self, msgs_store, node, msg_state): # # this is necessary to disable locally messages applying to class / # function using their fromlineno - if isinstance(node, (nodes.Module, nodes.Class, nodes.Function)) and node.body: + if (isinstance(node, (nodes.Module, nodes.ClassDef, nodes.FunctionDef)) + and node.body): firstchildlineno = node.body[0].fromlineno else: firstchildlineno = last for msgid, lines in six.iteritems(msg_state): for lineno, state in list(lines.items()): original_lineno = lineno - if first <= lineno <= last: - # Set state for all lines for this block, if the - # warning is applied to nodes. - if msgs_store.check_message_id(msgid).scope == WarningScope.NODE: - if lineno > firstchildlineno: - state = True - first_, last_ = node.block_range(lineno) - else: - first_ = lineno - last_ = last - for line in range(first_, last_+1): - # do not override existing entries - if not line in self._module_msgs_state.get(msgid, ()): - if line in lines: # state change in the same block - state = lines[line] - original_lineno = line - if not state: - self._suppression_mapping[(msgid, line)] = original_lineno - try: - self._module_msgs_state[msgid][line] = state - except KeyError: - self._module_msgs_state[msgid] = {line: state} - del lines[lineno] + if first > lineno or last < lineno: + continue + # Set state for all lines for this block, if the + # warning is applied to nodes. + if msgs_store.check_message_id(msgid).scope == WarningScope.NODE: + if lineno > firstchildlineno: + state = True + first_, last_ = node.block_range(lineno) + else: + first_ = lineno + last_ = last + for line in range(first_, last_+1): + # do not override existing entries + if line in self._module_msgs_state.get(msgid, ()): + continue + if line in lines: # state change in the same block + state = lines[line] + original_lineno = line + if not state: + self._suppression_mapping[(msgid, line)] = original_lineno + try: + self._module_msgs_state[msgid][line] = state + except KeyError: + self._module_msgs_state[msgid] = {line: state} + del lines[lineno] def set_msg_status(self, msg, line, status): """Set status (enabled/disable) for a given message at a given line""" @@ -789,7 +827,6 @@ def expand_modules(files_or_modules, black_list): try: filepath = file_from_modpath(modname.split('.')) if filepath is None: - errors.append({'key' : 'ignored-builtin-module', 'mod': modname}) continue except (ImportError, SyntaxError) as ex: # FIXME p3k : the SyntaxError is a Python bug and should be @@ -815,7 +852,7 @@ class PyLintASTWalker(object): def __init__(self, linter): # callbacks per node types - self.nbstatements = 1 + self.nbstatements = 0 self.visit_events = collections.defaultdict(list) self.leave_events = collections.defaultdict(list) self.linter = linter @@ -864,15 +901,42 @@ def walk(self, astroid): its children, then leave events. """ cid = astroid.__class__.__name__.lower() + + # Detect if the node is a new name for a deprecated alias. + # In this case, favour the methods for the deprecated + # alias if any, in order to maintain backwards + # compatibility. + old_cid = DEPRECATED_ALIASES.get(cid) + visit_events = () + leave_events = () + + if old_cid: + visit_events = self.visit_events.get(old_cid, ()) + leave_events = self.leave_events.get(old_cid, ()) + if visit_events or leave_events: + msg = ("Implemented method {meth}_{old} instead of {meth}_{new}. " + "This will be supported until Pylint 2.0.") + if visit_events: + warnings.warn(msg.format(meth="visit", old=old_cid, new=cid), + PendingDeprecationWarning) + if leave_events: + warnings.warn(msg.format(meth="leave", old=old_cid, new=cid), + PendingDeprecationWarning) + + visit_events = itertools.chain(visit_events, + self.visit_events.get(cid, ())) + leave_events = itertools.chain(leave_events, + self.leave_events.get(cid, ())) + if astroid.is_statement: self.nbstatements += 1 # generate events for this node on each checker - for cb in self.visit_events.get(cid, ()): + for cb in visit_events or (): cb(astroid) # recurse on children for child in astroid.get_children(): self.walk(child) - for cb in self.leave_events.get(cid, ()): + for cb in leave_events or (): cb(astroid) @@ -922,3 +986,163 @@ def get_global_option(checker, option, default=None): if options[0] == option: return getattr(provider.config, option.replace("-", "_")) return default + + +def deprecated_option(shortname=None, opt_type=None, help_msg=None): + def _warn_deprecated(option, optname, *args): # pylint: disable=unused-argument + msg = ("Warning: option %s is obsolete and " + "it is slated for removal in Pylint 1.6.\n") + sys.stderr.write(msg % (optname,)) + + option = { + 'help': help_msg, + 'hide': True, + 'type': opt_type, + 'action': 'callback', + 'callback': _warn_deprecated, + 'deprecated': True + } + if shortname: + option['shortname'] = shortname + return option + + +def _splitstrip(string, sep=','): + """return a list of stripped string by splitting the string given as + argument on `sep` (',' by default). Empty string are discarded. + + >>> _splitstrip('a, b, c , 4,,') + ['a', 'b', 'c', '4'] + >>> _splitstrip('a') + ['a'] + >>> + + :type string: str or unicode + :param string: a csv line + + :type sep: str or unicode + :param sep: field separator, default to the comma (',') + + :rtype: str or unicode + :return: the unquoted string (or the input string if it wasn't quoted) + """ + return [word.strip() for word in string.split(sep) if word.strip()] + + +def _unquote(string): + """remove optional quotes (simple or double) from the string + + :type string: str or unicode + :param string: an optionally quoted string + + :rtype: str or unicode + :return: the unquoted string (or the input string if it wasn't quoted) + """ + if not string: + return string + if string[0] in '"\'': + string = string[1:] + if string[-1] in '"\'': + string = string[:-1] + return string + + +def _normalize_text(text, line_len=80, indent=''): + """Wrap the text on the given line length.""" + return '\n'.join(textwrap.wrap(text, width=line_len, initial_indent=indent, + subsequent_indent=indent)) + + +def _check_csv(value): + if isinstance(value, (list, tuple)): + return value + return _splitstrip(value) + + +if six.PY2: + def _encode(string, encoding): + # pylint: disable=undefined-variable + if isinstance(string, unicode): + return string.encode(encoding) + return str(string) +else: + def _encode(string, _): + return str(string) + +def _get_encoding(encoding, stream): + encoding = encoding or getattr(stream, 'encoding', None) + if not encoding: + import locale + encoding = locale.getpreferredencoding() + return encoding + + +def _comment(string): + """return string as a comment""" + lines = [line.strip() for line in string.splitlines()] + return '# ' + ('%s# ' % os.linesep).join(lines) + + +def _format_option_value(optdict, value): + """return the user input's value from a 'compiled' value""" + if isinstance(value, (list, tuple)): + value = ','.join(value) + elif isinstance(value, dict): + value = ','.join('%s:%s' % (k, v) for k, v in value.items()) + elif hasattr(value, 'match'): # optdict.get('type') == 'regexp' + # compiled regexp + value = value.pattern + elif optdict.get('type') == 'yn': + value = value and 'yes' or 'no' + elif isinstance(value, six.string_types) and value.isspace(): + value = "'%s'" % value + return value + + +def _ini_format_section(stream, section, options, encoding=None, doc=None): + """format an options section using the INI format""" + encoding = _get_encoding(encoding, stream) + if doc: + print(_encode(_comment(doc), encoding), file=stream) + print('[%s]' % section, file=stream) + _ini_format(stream, options, encoding) + + +def _ini_format(stream, options, encoding): + """format options using the INI format""" + for optname, optdict, value in options: + value = _format_option_value(optdict, value) + help = optdict.get('help') + if help: + help = _normalize_text(help, line_len=79, indent='# ') + print(file=stream) + print(_encode(help, encoding), file=stream) + else: + print(file=stream) + if value is None: + print('#%s=' % optname, file=stream) + else: + value = _encode(value, encoding).strip() + print('%s=%s' % (optname, value), file=stream) + +format_section = _ini_format_section + + +def _rest_format_section(stream, section, options, encoding=None, doc=None): + """format an options section using as ReST formatted output""" + encoding = _get_encoding(encoding, stream) + if section: + print('%s\n%s' % (section, "'"*len(section)), file=stream) + if doc: + print(_encode(_normalize_text(doc, line_len=79, indent=''), encoding), file=stream) + print(file=stream) + for optname, optdict, value in options: + help = optdict.get('help') + print(':%s:' % optname, file=stream) + if help: + help = _normalize_text(help, line_len=79, indent=' ') + print(_encode(help, encoding), file=stream) + if value: + value = _encode(_format_option_value(optdict, value), encoding) + print(file=stream) + print(' Default: ``%s``' % value.replace("`` ", "```` ``"), file=stream) From 0462bb58a0c8f7232810b991d799e69a53a79d0e Mon Sep 17 00:00:00 2001 From: Hannes Doyle Date: Thu, 14 Apr 2016 16:04:21 +0200 Subject: [PATCH 2/2] Folding fix for async def in python 3.5.1 --- plugin/pymode.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 26541ae2..82dd25cb 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -39,7 +39,7 @@ call pymode#default("g:pymode_folding", 1) " Maximum file length to check for nested class/def statements call pymode#default("g:pymode_folding_nest_limit", 1000) " Change for folding customization (by example enable fold for 'if', 'for') -call pymode#default("g:pymode_folding_regex", '^\s*\%(class\|def\) \w\+') +call pymode#default("g:pymode_folding_regex", '^\s*\%(class\|def\|async\s\+def\) \w\+') " Enable/disable python motion operators call pymode#default("g:pymode_motion", 1)