diff --git a/README.md b/README.md index 0e5200d..c8bfa42 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ PyLint with checks for common mistakes and issues in Python code specifically in * [`C8915`: `spark-outside-function`](#c8915-spark-outside-function) * [`C8917`: `use-display-instead-of-show`](#c8917-use-display-instead-of-show) * [`W8916`: `no-spark-argument-in-function`](#w8916-no-spark-argument-in-function) + * [`readability` checker](#readability-checker) + * [`R8923`: `rewrite-as-for-loop`](#r8923-rewrite-as-for-loop) * [`mocking` checker](#mocking-checker) * [`R8918`: `explicit-dependency-required`](#r8918-explicit-dependency-required) * [`R8919`: `obscure-mock`](#r8919-obscure-mock) @@ -295,6 +297,21 @@ To disable this check on a specific line, add `# pylint: disable=no-spark-argume [[back to top](#pylint-plugin-for-databricks)] +## `readability` checker +To use this checker, add `databricks.labs.pylint.readability` to `load-plugins` configuration in your `pylintrc` or `pyproject.toml` file. + +[[back to top](#pylint-plugin-for-databricks)] + +### `R8923`: `rewrite-as-for-loop` + +List comprehension spans multiple lines, rewrite as for loop. List comprehensions in Python are typically used to create new lists by iterating over an existing + iterable in a concise, one-line syntax. However, when a list comprehension becomes too complex or spans + multiple lines, it may lose its readability and clarity, which are key advantages of Python's syntax. + +To disable this check on a specific line, add `# pylint: disable=rewrite-as-for-loop` at the end of it. + +[[back to top](#pylint-plugin-for-databricks)] + ## `mocking` checker To use this checker, add `databricks.labs.pylint.mocking` to `load-plugins` configuration in your `pylintrc` or `pyproject.toml` file. @@ -372,7 +389,7 @@ To disable this check on a specific line, add `# pylint: disable=dead-code` at t To test this plugin in isolation, you can use the following command: ```bash -pylint --load-plugins=databricks.labs.pylint.all --disable=all --enable=missing-data-security-mode,unsupported-runtime,dbutils-fs-cp,dbutils-fs-head,dbutils-fs-ls,dbutils-fs-mount,dbutils-credentials,dbutils-notebook-run,pat-token-leaked,internal-api,legacy-cli,incompatible-with-uc,notebooks-too-many-cells,notebooks-percent-run,spark-outside-function,use-display-instead-of-show,no-spark-argument-in-function,explicit-dependency-required,obscure-mock,mock-no-assign,mock-no-usage,dead-code . +pylint --load-plugins=databricks.labs.pylint.all --disable=all --enable=missing-data-security-mode,unsupported-runtime,dbutils-fs-cp,dbutils-fs-head,dbutils-fs-ls,dbutils-fs-mount,dbutils-credentials,dbutils-notebook-run,pat-token-leaked,internal-api,legacy-cli,incompatible-with-uc,notebooks-too-many-cells,notebooks-percent-run,spark-outside-function,use-display-instead-of-show,no-spark-argument-in-function,rewrite-as-for-loop,explicit-dependency-required,obscure-mock,mock-no-assign,mock-no-usage,dead-code . ``` [[back to top](#pylint-plugin-for-databricks)] diff --git a/scripts/docs.py b/scripts/docs.py index 518b736..ab24117 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -9,6 +9,7 @@ from databricks.labs.pylint.legacy import LegacyChecker from databricks.labs.pylint.mocking import MockingChecker from databricks.labs.pylint.notebooks import NotebookChecker +from databricks.labs.pylint.readability import ReadabilityChecker from databricks.labs.pylint.spark import SparkChecker @@ -23,6 +24,7 @@ def do_something(): LegacyChecker(linter), NotebookChecker(linter), SparkChecker(linter), + ReadabilityChecker(linter), MockingChecker(linter), EradicateChecker(linter), ]: diff --git a/src/databricks/labs/pylint/all.py b/src/databricks/labs/pylint/all.py index 40f3f8d..e85cd9b 100644 --- a/src/databricks/labs/pylint/all.py +++ b/src/databricks/labs/pylint/all.py @@ -4,6 +4,7 @@ from databricks.labs.pylint.legacy import LegacyChecker from databricks.labs.pylint.mocking import MockingChecker from databricks.labs.pylint.notebooks import NotebookChecker +from databricks.labs.pylint.readability import ReadabilityChecker from databricks.labs.pylint.spark import SparkChecker @@ -15,3 +16,4 @@ def register(linter): linter.register_checker(SparkChecker(linter)) linter.register_checker(MockingChecker(linter)) linter.register_checker(EradicateChecker(linter)) + linter.register_checker(ReadabilityChecker(linter)) diff --git a/src/databricks/labs/pylint/readability.py b/src/databricks/labs/pylint/readability.py new file mode 100644 index 0000000..c1879b7 --- /dev/null +++ b/src/databricks/labs/pylint/readability.py @@ -0,0 +1,23 @@ +from astroid import nodes # type: ignore +from pylint.checkers import BaseChecker + + +class ReadabilityChecker(BaseChecker): + name = "readability" + msgs = { + "R8923": ( + "List comprehension spans multiple lines, rewrite as for loop", + "rewrite-as-for-loop", + """List comprehensions in Python are typically used to create new lists by iterating over an existing + iterable in a concise, one-line syntax. However, when a list comprehension becomes too complex or spans + multiple lines, it may lose its readability and clarity, which are key advantages of Python's syntax.""", + ), + } + + def visit_listcomp(self, node: nodes.ListComp) -> None: + if node.lineno != node.end_lineno: + self.add_message("rewrite-as-for-loop", node=node) + + +def register(linter): + linter.register_checker(ReadabilityChecker(linter)) diff --git a/tests/test_readability.py b/tests/test_readability.py new file mode 100644 index 0000000..b0e3a4e --- /dev/null +++ b/tests/test_readability.py @@ -0,0 +1,16 @@ +from databricks.labs.pylint.readability import ReadabilityChecker + + +def test_single_line_list_comprehensions_allowed(lint_with): + messages = lint_with(ReadabilityChecker) << """[x for x in range(10) if x % 2 == 0]""" + assert not messages + + +def test_multi_line_list_comprehensions_not_allowed(lint_with): + messages = ( + lint_with(ReadabilityChecker) + << """[ + x for x in range(10) if x % 2 == 0 + ]""" + ) + assert messages == {"[rewrite-as-for-loop] List comprehension spans multiple lines, rewrite as for loop"}