diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 000000000..c6c315de6 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,211 @@ +# A settings file for running pylint on Rope. + +# Run pylint from the top-level Rope directory: `python -m pylint rope` + +# This file tested on pylint 2.14.4, Python 3.10.5, as reported by `python -m pylint --version`. + +[MASTER] + +# Add to the black list. A base name, not a path. +ignore= + .git + +# Files or directories matching regex, in Windows or Posix format. +ignore-paths= + +# Pickle collected data for later comparisons. +persistent=yes + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). +# enable= + +# Disable the message, report, category or checker with the given id(s). +disable= + + # Catagories to disable: expensive or make-work + # typecheck + + basic, # Check on names. Unimportant. + classes, # Unimportant. + design, # Make work. + format, # Use black + imports, # Creates import graph. Expensive? + similarities, # Expensive. + typecheck, # Unimportant + variables, # Unimportant. + + # Black. Suppress checks that conflict with Black's conventions. + + line-too-long, + + # Rope: Suppress checks that conflict with Rope's conventions. + + bad-classmethod-argument, + eval-used, + function-redefined, + inconsistent-return-statements, + invalid-name, + no-else-return, + no-self-argument, + signature-differs, # Maybe. + not-callable, # Maybe. + unexpected-special-method-signature, + + # Rope: potentially serious warnings. + + arguments-differ, + dangerous-default-value, + modified-iterating-list, + used-before-assignment, + unsubscriptable-object, + unpacking-non-sequence, + + # Rope: to be removed. + + duplicate-string-formatting-argument, + no-else-raise, # Unnecessary "else" after "raise", remove the "else" and de-indent the code inside it. + reimported, + superfluous-parens, + unused-import, + unused-wildcard-import, + useless-super-delegation, + + useless-else-on-loop, #Else clause on loop without a break statement, remove the else and de-indent all the code inside it. + unidiomatic-typecheck, + wildcard-import, + wrong-import-order, + + # Rope: Additional (temporary?) suppressions. + + abstract-method, + arguments-differ, + consider-merging-isinstance, + consider-using-enumerate, + deprecated-method, + implicit-str-concat, + logging-fstring-interpolation, + no-member, + no-staticmethod-decorator, # Consider using a decorator instead of calling staticmethod. + non-parent-init-called, + raise-missing-from, + super-init-not-called, + unnecessary-lambda, + unsupported-assignment-operation, + + # Leo: Standard suppressions. + + arguments-renamed, # cursesGui2.py + assignment-from-no-return, # Causes problems when base class return None. + assignment-from-none, # Causes problems when base class return None. + attribute-defined-outside-init, + broad-except, # except Exception is justified if followed by g.es_exception. + c-extension-no-member, # Too many errors re pyQt6. + condition-evals-to-constant, + consider-iterating-dictionary, + consider-using-from-import, + consider-using-f-string, # complains about regex's! + consider-using-in, + consider-using-dict-comprehension, + consider-using-dict-items, + consider-using-generator, + consider-using-max-builtin, + consider-using-set-comprehension, + consider-using-ternary, + consider-using-with, + cyclic-import, + deprecated-module, + exec-used, + f-string-without-interpolation, # Useful for concatenated f-strings. + global-statement, # Assume we know what we are doing. + global-variable-not-assigned, # not helpful. + import-error, # Ignore imports of optional packages. + import-outside-toplevel, # Requires substantial code changes. + keyword-arg-before-vararg, # See https://github.com/PyCQA/pylint/issues/2027 + missing-docstring, # Instead, use Leo's find-missing-docstrings command. + no-else-break, # **Possible pylint bug** + protected-access, + redeclared-assigned-name, + redefined-argument-from-local, # I do this all the time. + redefined-builtin, # all, next, etc. so what? + redefined-outer-name, + too-few-public-methods, + trailing-whitespace, # Too picky. No need to constantly do clean-all-lines. + unnecessary-comprehension, + unnecessary-dunder-call, # pylint for python 3.10 only. + unnecessary-lambda-assignment, + unnecessary-pass, # Can be pedantic in some situations. + unspecified-encoding, # Huh? + unused-argument, + unused-private-member, # too many false positives. + unused-variable, # way too many false positives, esp. tuple unpacking. + use-dict-literal, + use-list-literal, + use-maxsplit-arg, # What is this?? + using-constant-test, + + # Good warnings. Don't suppress these. + + # bad-option-value, # obsolete pylint option. + # unrecognized-option, # newer python. + + # bad-builtin, + # bad-continuation, + # bad-option-value, # obsolete pylint option. + # chained-comparison, + # len-as-condition, + # import-error, # Warns when an import fails: useful now that we are only using python 3. + # literal-comparison, + # locally-disabled, + # multiple-statements, + # no-else-raise, + # no-init, + # no-value-for-parameter, + # not-an-iterable, + # old-style-class, # Probably not an issue. + # simplifiable-if-statement, + # singleton-comparison, + # superfluous-parens, + # trailing-comma-tuple, + # unsupported-assignment-operation, + # unsupported-delete-operation, + # unsubscriptable-object, + # useless-object-inheritance, # class x(object): + # useless-return, + +[REPORTS] + +# Set the output format. Multiple values are allowed. +output-format=text # colorized, text, parseable, msvs (visual studio) + +reports=no # Display only messages. + +score=no # Deactivate the evaluation score. + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +# notes=FIXME,XXX,TODO +notes= + +# The following sections are all suppressed above. + +[BASIC] + +[CLASSES] + +[DESIGN] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=10 # Apparently, this can't be suppressed! + +[FORMAT] + +[IMPORTS] + +[SIMILARITIES] + +[TYPECHECK] + +[VARIABLES] diff --git a/rope/base/fscommands.py b/rope/base/fscommands.py index 85d7a2396..1357beaf7 100644 --- a/rope/base/fscommands.py +++ b/rope/base/fscommands.py @@ -103,7 +103,7 @@ def __init__(self, root): traceback=False, report_untrusted=False, ) - except: + except Exception: self.ui = self.hg.ui.ui() self.ui.setconfig("ui", "interactive", "no") self.ui.setconfig("ui", "debug", "no") diff --git a/rope/base/oi/doa.py b/rope/base/oi/doa.py index 92dbd5140..2ec6a7fc8 100644 --- a/rope/base/oi/doa.py +++ b/rope/base/oi/doa.py @@ -188,7 +188,7 @@ def receive_data(self): buf_digest = base64.b64decode(buf[:digest_end]) buf_data = buf[digest_end + 1 : -1] decoded_buf_data = base64.b64decode(buf_data) - except: + except Exception: # Corrupted data; the payload cannot be trusted and just has # to be dropped. See CVE-2014-3539. continue diff --git a/rope/base/project.py b/rope/base/project.py index a643dbdd1..8e7124c05 100644 --- a/rope/base/project.py +++ b/rope/base/project.py @@ -273,7 +273,7 @@ def _init_prefs(self, prefs): def _init_other_parts(self): # Forcing the creation of `self.pycore` to register observers - self.pycore + self.pycore # pylint: disable=pointless-statement def is_ignored(self, resource): return self.ignored.does_match(resource) diff --git a/rope/base/pycore.py b/rope/base/pycore.py index d41aaf117..7428bc1ee 100644 --- a/rope/base/pycore.py +++ b/rope/base/pycore.py @@ -1,6 +1,5 @@ import bisect import difflib -import sys import warnings import rope.base.libutils diff --git a/rope/base/pyobjectsdef.py b/rope/base/pyobjectsdef.py index abab073ed..05f83c340 100644 --- a/rope/base/pyobjectsdef.py +++ b/rope/base/pyobjectsdef.py @@ -106,7 +106,7 @@ def get_kind(self): @property def decorators(self): try: - return getattr(self.ast_node, "decorator_list") + return self.ast_node.decorator_list except AttributeError: return getattr(self.ast_node, "decorators", None) @@ -455,7 +455,7 @@ def _AugAssign(self, node): pass def _For(self, node): - self._update_evaluated(node.target, node.iter, ".__iter__().next()") # noqa + self._update_evaluated(node.target, node.iter, ".__iter__().next()") for child in node.body + node.orelse: ast.walk(child, self) diff --git a/rope/base/pyscopes.py b/rope/base/pyscopes.py index 9f13bc40c..74e16b415 100644 --- a/rope/base/pyscopes.py +++ b/rope/base/pyscopes.py @@ -328,14 +328,14 @@ def find_scope_end(self, scope): body_indents = self._get_scope_indents(scope) + 4 else: body_indents = self._get_body_indents(scope) - for l in self.logical_lines.generate_starts( + for line_start in self.logical_lines.generate_starts( min(end + 1, self.lines.length()), self.lines.length() + 1 ): - if not self._is_empty_line(l): - if self.get_indents(l) < body_indents: + if not self._is_empty_line(line_start): + if self.get_indents(line_start) < body_indents: return end else: - end = l + end = line_start return end @property diff --git a/rope/refactor/extract.py b/rope/refactor/extract.py index dfa56a298..7c9edda64 100644 --- a/rope/refactor/extract.py +++ b/rope/refactor/extract.py @@ -902,7 +902,7 @@ def _flatten_nested_tuple_of_names(self, node): elif isinstance(node, ast.Name): yield node.id else: - assert False, "Unexpected node type in list comprehension target: %s" % node + assert False, f"Unexpected node type in list comprehension target: {node!r}" def _If(self, node): self._handle_conditional_node(node) diff --git a/rope/refactor/importutils/module_imports.py b/rope/refactor/importutils/module_imports.py index 67a42f2ce..39cb718f6 100644 --- a/rope/refactor/importutils/module_imports.py +++ b/rope/refactor/importutils/module_imports.py @@ -574,7 +574,7 @@ def _get_names(self, alias_names): def find_import_statements(self): nodes = self.pymodule.get_ast().body - for index, node in enumerate(nodes): + for node in nodes: if isinstance(node, (ast.Import, ast.ImportFrom)): lines = self.pymodule.logical_lines end_line = lines.logical_line_in(node.lineno)[1] + 1 diff --git a/rope/refactor/move.py b/rope/refactor/move.py index 4e9aea1dd..c4ccd8970 100644 --- a/rope/refactor/move.py +++ b/rope/refactor/move.py @@ -706,9 +706,9 @@ def rename_in_module(self, new_name, pymodule=None, imports=False, resource=None def occurs_in_module(self, pymodule=None, resource=None, imports=True): finder = self._create_finder(imports) - for occurrence in finder.find_occurrences(pymodule=pymodule, resource=resource): - return True - return False + occurrences = finder.find_occurrences(pymodule=pymodule, resource=resource) + sentinel = object() + return next(occurrences, sentinel) is not sentinel def _create_finder(self, imports): return occurrences.create_finder( diff --git a/rope/refactor/patchedast.py b/rope/refactor/patchedast.py index d8277e196..c545f1a6f 100644 --- a/rope/refactor/patchedast.py +++ b/rope/refactor/patchedast.py @@ -536,7 +536,7 @@ def _alias(self, node): def _handle_function_def_node(self, node, is_async): children = [] try: - decorators = getattr(node, "decorator_list") + decorators = node.decorator_list except AttributeError: decorators = getattr(node, "decorators", None) if decorators: @@ -922,7 +922,7 @@ def consume(self, token, skip_comment=True): break else: self._skip_comment() - except (ValueError, TypeError) as e: + except (ValueError, TypeError) as e: # noqa raise MismatchedTokenError( f"Token <{token}> at {self._get_location()} cannot be matched" ) diff --git a/rope/refactor/sourceutils.py b/rope/refactor/sourceutils.py index 27116b60a..d55aad4c9 100644 --- a/rope/refactor/sourceutils.py +++ b/rope/refactor/sourceutils.py @@ -20,15 +20,15 @@ def indent_lines(source_code, amount): return source_code lines = source_code.splitlines(True) result = [] - for l in lines: - if l.strip() == "": + for line in lines: + if line.strip() == "": result.append("\n") continue if amount < 0: - indents = codeanalyze.count_line_indents(l) - result.append(max(0, indents + amount) * " " + l.lstrip()) + indents = codeanalyze.count_line_indents(line) + result.append(max(0, indents + amount) * " " + line.lstrip()) else: - result.append(" " * amount + l) + result.append(" " * amount + line) return "".join(result) diff --git a/ropetest/advanced_oi_test.py b/ropetest/advanced_oi_test.py index 94374864f..c9ecf1916 100644 --- a/ropetest/advanced_oi_test.py +++ b/ropetest/advanced_oi_test.py @@ -1051,7 +1051,7 @@ def test_validation_problems_for_changing_builtin_types(self): l = {} v = l["key"] """)) - pymod1 = self.project.get_pymodule(mod1) # noqa + pymod1 = self.project.get_pymodule(mod1) var = pymod1["v"].get_object() # noqa def test_always_returning_containing_class_for_selfs(self): diff --git a/ropetest/contrib/autoimport/utilstest.py b/ropetest/contrib/autoimport/utilstest.py index 91e1622f4..0c818cd8c 100644 --- a/ropetest/contrib/autoimport/utilstest.py +++ b/ropetest/contrib/autoimport/utilstest.py @@ -1,9 +1,5 @@ """Tests for autoimport utility functions, written in pytest""" -from sys import platform - -import pytest - from rope.contrib.autoimport import utils from rope.contrib.autoimport.defs import Package, PackageType, Source @@ -59,5 +55,5 @@ def test_get_package_tuple_typing(typing_path): def test_get_package_tuple_compiled(compiled_lib): lib_name, lib_path = compiled_lib assert Package( - lib_name, Source.STANDARD, lib_path, PackageType.COMPILED + lib_name, Source.STANDARD, lib_path, PackageType.COMPILED ) == utils.get_package_tuple(lib_path) diff --git a/ropetest/refactor/patchedasttest.py b/ropetest/refactor/patchedasttest.py index a53c6eef2..d24f2c5aa 100644 --- a/ropetest/refactor/patchedasttest.py +++ b/ropetest/refactor/patchedasttest.py @@ -1584,7 +1584,6 @@ def test_match_node_with_match_class_match_as_capture_pattern_with_explicit_name ")", ]) - @testutils.only_for_versions_higher("3.10") def test_match_node_with_match_mapping_match_as(self): source = dedent("""\ diff --git a/ropetest/type_hinting_test.py b/ropetest/type_hinting_test.py index 739a0abde..af94352fb 100644 --- a/ropetest/type_hinting_test.py +++ b/ropetest/type_hinting_test.py @@ -1,5 +1,4 @@ import unittest -from textwrap import dedent from textwrap import dedent, indent import pytest @@ -173,7 +172,8 @@ class Sample(object): def a_method(self): self.a_attr.is_a""") offset = len(code) - code += dedent(f"""\ + # Note: the leading blank lines are required. + code += dedent("""\ class Other(object): diff --git a/setup.cfg b/setup.cfg index 84bae7b71..18cc2fd8a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,6 +8,14 @@ extend-ignore = # E203: whitespace before ':' # this rule contradicts black codestyle E203 + + # B007: Loop control variable 'keyword' not used within the loop body. + # If this is intended, start the name with an underscore. + B007 + + # B011: Do not call assert False since python -O removes these calls. + # Instead callers should raise AssertionError(). + B011 max-line-length = 110 diff --git a/setup.py b/setup.py index fc1f76c84..606849326 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,3 @@ from setuptools import setup -setup() \ No newline at end of file +setup()