diff --git a/doc/development_guide/api/epylint.rst b/doc/development_guide/api/epylint.rst deleted file mode 100644 index 8359587ed5..0000000000 --- a/doc/development_guide/api/epylint.rst +++ /dev/null @@ -1,22 +0,0 @@ -======= -epylint -======= - -To silently run epylint on a ``module_name.py`` module, and get its standard output and error: - -.. sourcecode:: python - - from pylint import epylint as lint - - (pylint_stdout, pylint_stderr) = lint.py_run('module_name.py', return_std=True) - -It is also possible to include additional Pylint options in the first argument to ``py_run``: - -.. sourcecode:: python - - from pylint import epylint as lint - - (pylint_stdout, pylint_stderr) = lint.py_run('module_name.py --disable C0114', return_std=True) - -The options ``--msg-template="{path}:{line}: {category} ({msg_id}, {symbol}, {obj}) {msg}"`` and -``--reports=n`` are set implicitly inside the ``epylint`` module. diff --git a/doc/development_guide/api/index.rst b/doc/development_guide/api/index.rst index 00e6e1a9f2..ac4e969000 100644 --- a/doc/development_guide/api/index.rst +++ b/doc/development_guide/api/index.rst @@ -2,7 +2,7 @@ API ### -You can call ``Pylint``, ``epylint``, ``symilar`` and ``pyreverse`` from another +You can call ``Pylint``, ``symilar`` and ``pyreverse`` from another Python program thanks to their APIs: .. sourcecode:: python @@ -19,4 +19,3 @@ Python program thanks to their APIs: :hidden: pylint - epylint diff --git a/doc/user_guide/installation/ide_integration/flymake-emacs.rst b/doc/user_guide/installation/ide_integration/flymake-emacs.rst index 79310ff59d..64aad8a66d 100644 --- a/doc/user_guide/installation/ide_integration/flymake-emacs.rst +++ b/doc/user_guide/installation/ide_integration/flymake-emacs.rst @@ -4,75 +4,6 @@ Using Pylint through Flymake in Emacs ===================================== .. warning:: - The Emacs package now has its own repository and is looking for a maintainer. - If you're reading this doc and are interested in maintaining this package or - are actually using flymake please open an issue at - https://github.com/emacsorphanage/pylint/issues/new/choose - -To enable Flymake for Python, insert the following into your .emacs: - -.. sourcecode:: common-lisp - - ;; Configure Flymake for Python - (when (load "flymake" t) - (defun flymake-pylint-init () - (let* ((temp-file (flymake-init-create-temp-buffer-copy - 'flymake-create-temp-inplace)) - (local-file (file-relative-name - temp-file - (file-name-directory buffer-file-name)))) - (list "epylint" (list local-file)))) - (add-to-list 'flymake-allowed-file-name-masks - '("\\.py\\'" flymake-pylint-init))) - - ;; Set as a minor mode for Python - (add-hook 'python-mode-hook '(lambda () (flymake-mode))) - -Above stuff is in ``pylint/elisp/pylint-flymake.el``, which should be automatically -installed on Debian systems, in which cases you don't have to put it in your ``.emacs`` file. - -Other things you may find useful to set: - -.. sourcecode:: common-lisp - - ;; Configure to wait a bit longer after edits before starting - (setq-default flymake-no-changes-timeout '3) - - ;; Keymaps to navigate to the errors - (add-hook 'python-mode-hook '(lambda () (define-key python-mode-map "\C-cn" 'flymake-goto-next-error))) - (add-hook 'python-mode-hook '(lambda () (define-key python-mode-map "\C-cp" 'flymake-goto-prev-error))) - - -Finally, by default Flymake only displays the extra information about the error when you -hover the mouse over the highlighted line. The following will use the minibuffer to display -messages when you the cursor is on the line. - -.. sourcecode:: common-lisp - - ;; To avoid having to mouse hover for the error message, these functions make Flymake error messages - ;; appear in the minibuffer - (defun show-fly-err-at-point () - "If the cursor is sitting on a Flymake error, display the message in the minibuffer" - (require 'cl) - (interactive) - (let ((line-no (line-number-at-pos))) - (dolist (elem flymake-err-info) - (if (eq (car elem) line-no) - (let ((err (car (second elem)))) - (message "%s" (flymake-ler-text err))))))) - - (add-hook 'post-command-hook 'show-fly-err-at-point) - - -Alternative, if you only wish to pollute the minibuffer after an explicit flymake-goto-* then use -the following instead of a post-command-hook - -.. sourcecode:: common-lisp - - (defadvice flymake-goto-next-error (after display-message activate compile) - "Display the error in the mini-buffer rather than having to mouse over it" - (show-fly-err-at-point)) - - (defadvice flymake-goto-prev-error (after display-message activate compile) - "Display the error in the mini-buffer rather than having to mouse over it" - (show-fly-err-at-point)) + epylint was deprecated in 2.16.0 and targeted for deletion in 3.0.0. + All emacs and flymake related files were removed and their support will + now happen in an external repository: https://github.com/emacsorphanage/pylint. diff --git a/doc/whatsnew/fragments/7737.user_action b/doc/whatsnew/fragments/7737.user_action new file mode 100644 index 0000000000..1a992f563d --- /dev/null +++ b/doc/whatsnew/fragments/7737.user_action @@ -0,0 +1,3 @@ +epylint was removed. It now lives at: https://github.com/emacsorphanage/pylint. + +Refs #7737 diff --git a/pylint/__init__.py b/pylint/__init__.py index 2cc7edadbb..1e9d34ee08 100644 --- a/pylint/__init__.py +++ b/pylint/__init__.py @@ -9,14 +9,12 @@ "version", "modify_sys_path", "run_pylint", - "run_epylint", "run_symilar", "run_pyreverse", ] import os import sys -import warnings from collections.abc import Sequence from typing import NoReturn @@ -48,22 +46,6 @@ def _run_pylint_config(argv: Sequence[str] | None = None) -> None: _PylintConfigRun(argv or sys.argv[1:]) -def run_epylint(argv: Sequence[str] | None = None) -> NoReturn: - """Run epylint. - - argv can be a list of strings normally supplied as arguments on the command line - """ - from pylint.epylint import Run as EpylintRun - - warnings.warn( - "'run_epylint' will be removed in pylint 3.0, use " - "https://github.com/emacsorphanage/pylint instead.", - DeprecationWarning, - stacklevel=1, - ) - EpylintRun(argv) - - def run_pyreverse(argv: Sequence[str] | None = None) -> NoReturn: """Run pyreverse. diff --git a/pylint/epylint.py b/pylint/epylint.py deleted file mode 100755 index dd23b450be..0000000000 --- a/pylint/epylint.py +++ /dev/null @@ -1,224 +0,0 @@ -# mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -# -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4 - -# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt - -"""Emacs and Flymake compatible Pylint. - -This script is for integration with Emacs and is compatible with Flymake mode. - -epylint walks out of python packages before invoking pylint. This avoids -reporting import errors that occur when a module within a package uses the -absolute import path to get another module within this package. - -For example: - - Suppose a package is structured as - - a/__init__.py - a/b/x.py - a/c/y.py - - - Then if y.py imports x as "from a.b import x" the following produces pylint - errors - - cd a/c; pylint y.py - - - The following obviously doesn't - - pylint a/c/y.py - - - As this script will be invoked by Emacs within the directory of the file - we are checking we need to go out of it to avoid these false positives. - -You may also use py_run to run pylint with desired options and get back (or not) -its output. -""" - -from __future__ import annotations - -import os -import shlex -import sys -import warnings -from collections.abc import Sequence -from io import StringIO -from subprocess import PIPE, Popen -from typing import NoReturn, TextIO, overload - -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal - - -def _get_env() -> dict[str, str]: - """Extracts the environment PYTHONPATH and appends the current 'sys.path' - to it. - """ - env = dict(os.environ) - env["PYTHONPATH"] = os.pathsep.join(sys.path) - return env - - -def lint(filename: str, options: Sequence[str] = ()) -> int: - """Pylint the given file. - - When run from Emacs we will be in the directory of a file, and passed its - filename. If this file is part of a package and is trying to import other - modules from within its own package or another package rooted in a directory - below it, pylint will classify it as a failed import. - - To get around this, we traverse down the directory tree to find the root of - the package this module is in. We then invoke pylint from this directory. - - Finally, we must correct the filenames in the output generated by pylint so - Emacs doesn't become confused (it will expect just the original filename, - while pylint may extend it with extra directories if we've traversed down - the tree) - """ - # traverse downwards until we are out of a python package - full_path = os.path.abspath(filename) - parent_path = os.path.dirname(full_path) - child_path = os.path.basename(full_path) - - while parent_path != "/" and os.path.exists( - os.path.join(parent_path, "__init__.py") - ): - child_path = os.path.join(os.path.basename(parent_path), child_path) - parent_path = os.path.dirname(parent_path) - - # Start pylint - # Ensure we use the python and pylint associated with the running epylint - run_cmd = "import sys; from pylint.lint import Run; Run(sys.argv[1:])" - cmd = ( - [sys.executable, "-c", run_cmd] - + [ - "--msg-template", - "{path}:{line}: {category} ({msg_id}, {symbol}, {obj}) {msg}", - "-r", - "n", - child_path, - ] - + list(options) - ) - - with Popen( - cmd, stdout=PIPE, cwd=parent_path, env=_get_env(), universal_newlines=True - ) as process: - for line in process.stdout: # type: ignore[union-attr] - # remove pylintrc warning - if line.startswith("No config file found"): - continue - - # modify the file name that's put out to reverse the path traversal we made - parts = line.split(":") - if parts and parts[0] == child_path: - line = ":".join([filename] + parts[1:]) - print(line, end=" ") - - process.wait() - return process.returncode - - -@overload -def py_run( - command_options: str = ..., - return_std: Literal[False] = ..., - stdout: TextIO | int | None = ..., - stderr: TextIO | int | None = ..., -) -> None: - ... - - -@overload -def py_run( - command_options: str, - return_std: Literal[True], - stdout: TextIO | int | None = ..., - stderr: TextIO | int | None = ..., -) -> tuple[StringIO, StringIO]: - ... - - -def py_run( - command_options: str = "", - return_std: bool = False, - stdout: TextIO | int | None = None, - stderr: TextIO | int | None = None, -) -> tuple[StringIO, StringIO] | None: - """Run pylint from python. - - ``command_options`` is a string containing ``pylint`` command line options; - ``return_std`` (boolean) indicates return of created standard output - and error (see below); - ``stdout`` and ``stderr`` are 'file-like' objects in which standard output - could be written. - - Calling agent is responsible for stdout/err management (creation, close). - Default standard output and error are those from sys, - or standalone ones (``subprocess.PIPE``) are used - if they are not set and ``return_std``. - - If ``return_std`` is set to ``True``, this function returns a 2-uple - containing standard output and error related to created process, - as follows: ``(stdout, stderr)``. - - To silently run Pylint on a module, and get its standard output and error: - >>> (pylint_stdout, pylint_stderr) = py_run( 'module_name.py', True) - """ - warnings.warn( - "'epylint' will be removed in pylint 3.0, use https://github.com/emacsorphanage/pylint instead.", - DeprecationWarning, - stacklevel=2, - ) - # Detect if we use Python as executable or not, else default to `python` - executable = sys.executable if "python" in sys.executable else "python" - - # Create command line to call pylint - epylint_part = [executable, "-c", "from pylint import epylint;epylint.Run()"] - options = shlex.split(command_options, posix=not sys.platform.startswith("win")) - cli = epylint_part + options - - # Providing standard output and/or error if not set - if stdout is None: - stdout = PIPE if return_std else sys.stdout - if stderr is None: - stderr = PIPE if return_std else sys.stderr - # Call pylint in a sub-process - with Popen( - cli, - shell=False, - stdout=stdout, - stderr=stderr, - env=_get_env(), - universal_newlines=True, - ) as process: - proc_stdout, proc_stderr = process.communicate() - # Return standard output and error - if return_std: - return StringIO(proc_stdout), StringIO(proc_stderr) - return None - - -def Run(argv: Sequence[str] | None = None) -> NoReturn: - warnings.warn( - "'epylint' will be removed in pylint 3.0, use https://github.com/emacsorphanage/pylint instead.", - DeprecationWarning, - stacklevel=2, - ) - if not argv and len(sys.argv) == 1: - print(f"Usage: {sys.argv[0]} [options]") - sys.exit(1) - - argv = argv or sys.argv[1:] - if not os.path.exists(argv[0]): - print(f"{argv[0]} does not exist") - sys.exit(1) - else: - sys.exit(lint(argv[0], argv[1:])) - - -if __name__ == "__main__": - Run() diff --git a/pyproject.toml b/pyproject.toml index 333d2856ed..0c2b2a817a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,6 @@ spelling = ["pyenchant~=3.2"] [project.scripts] pylint = "pylint:run_pylint" pylint-config = "pylint:_run_pylint_config" -epylint = "pylint:run_epylint" pyreverse = "pylint:run_pyreverse" symilar = "pylint:run_symilar" diff --git a/tests/test_epylint.py b/tests/test_epylint.py deleted file mode 100644 index 7e9116e99a..0000000000 --- a/tests/test_epylint.py +++ /dev/null @@ -1,68 +0,0 @@ -# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html -# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE -# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt - -"""Test for issue https://github.com/PyCQA/pylint/issues/4286.""" -# pylint: disable=redefined-outer-name -from pathlib import PosixPath - -import pytest - -from pylint import epylint as lint - - -@pytest.fixture() -def example_path(tmp_path: PosixPath) -> PosixPath: - content = """class IvrAudioApp: - - def run(self): - self.hassan() - """ - path = tmp_path / "my_app.py" - with open(path, "w", encoding="utf-8") as f: - f.write(content) - return path - - -def test_epylint_good_command(example_path: PosixPath) -> None: - with pytest.warns(DeprecationWarning): - out, _ = lint.py_run( - f"{example_path} -E --disable=E1111 --msg-template " - "'{category} {module} {obj} {line} {column} {msg}'", - return_std=True, - ) - msg = out.read() - assert ( - msg - == """\ -************* Module my_app - error my_app IvrAudioApp.run 4 8 Instance of 'IvrAudioApp' has no 'hassan' member - """ - ) - - -def test_epylint_strange_command(example_path: PosixPath) -> None: - with pytest.warns(DeprecationWarning): - out, _ = lint.py_run( - # pylint: disable-next=consider-using-f-string - "%s -E --disable=E1111 --msg-template={category} {module} {obj} {line} {column} {msg}" - % example_path, - return_std=True, - ) - assert ( - out.read() - == """\ -************* Module {module} - fatal - ************* Module {obj} - fatal - ************* Module {line} - fatal - ************* Module {column} - fatal - ************* Module {msg} - fatal - ************* Module my_app - error - """ - ) diff --git a/tests/test_pylint_runners.py b/tests/test_pylint_runners.py index 6a55db9ebe..dab1dc025b 100644 --- a/tests/test_pylint_runners.py +++ b/tests/test_pylint_runners.py @@ -17,7 +17,7 @@ import pytest -from pylint import run_epylint, run_pylint, run_pyreverse, run_symilar +from pylint import run_pylint, run_pyreverse, run_symilar from pylint.testutils import GenericTestReporter as Reporter from pylint.testutils._run import _Run as Run from pylint.testutils.utils import _test_cwd @@ -44,17 +44,6 @@ def test_runner(runner: _RunCallable, tmp_path: pathlib.Path) -> None: assert err.value.code == 0 -def test_epylint(tmp_path: pathlib.Path) -> None: - """TODO: 3.0 delete with epylint.""" - filepath = os.path.abspath(__file__) - with _test_cwd(tmp_path): - with patch.object(sys, "argv", ["", filepath]): - with pytest.raises(SystemExit) as err: - with pytest.warns(DeprecationWarning): - run_epylint() - assert err.value.code == 0 - - @pytest.mark.parametrize("runner", [run_pylint, run_pyreverse, run_symilar]) def test_runner_with_arguments(runner: _RunCallable, tmp_path: pathlib.Path) -> None: """Check the runners with arguments as parameter instead of sys.argv.""" @@ -66,17 +55,6 @@ def test_runner_with_arguments(runner: _RunCallable, tmp_path: pathlib.Path) -> assert err.value.code == 0 -def test_epylint_with_arguments(tmp_path: pathlib.Path) -> None: - """TODO: 3.0 delete with epylint.""" - filepath = os.path.abspath(__file__) - testargs = [filepath] - with _test_cwd(tmp_path): - with pytest.raises(SystemExit) as err: - with pytest.warns(DeprecationWarning): - run_epylint(testargs) - assert err.value.code == 0 - - def test_pylint_argument_deduplication( tmp_path: pathlib.Path, tests_directory: pathlib.Path ) -> None: