blob: 3b0462220dcff866243ed48a9e8ec82d25fc16d0 [file] [log] [blame]
Claudiu Popaec09cc72016-07-23 00:22:28 +03001# mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4
Mihai Balinte0f2c502015-02-28 17:13:21 +02002# -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4
Claudiu Popa83227812016-06-01 16:11:29 +01003
Claudiu Popaec09cc72016-07-23 00:22:28 +03004# Copyright (c) 2008-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
Claudiu Popa9ff44de2017-12-15 12:24:15 +01005# Copyright (c) 2014 Jakob Normark <jakobnormark@gmail.com>
6# Copyright (c) 2014 Brett Cannon <brett@python.org>
Claudiu Popaec09cc72016-07-23 00:22:28 +03007# Copyright (c) 2014 Manuel Vázquez Acosta <mva.led@gmail.com>
Claudiu Popa9ff44de2017-12-15 12:24:15 +01008# Copyright (c) 2014 Derek Harland <derek.harland@finq.co.nz>
9# Copyright (c) 2014 Arun Persaud <arun@nubati.net>
hippo911f7c29c2020-08-20 18:40:19 +020010# Copyright (c) 2015-2020 Claudiu Popa <pcmanticore@gmail.com>
Claudiu Popa9ff44de2017-12-15 12:24:15 +010011# Copyright (c) 2015 Mihai Balint <balint.mihai@gmail.com>
12# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
Pierre Sassoulasac852232021-02-21 15:13:06 +010013# Copyright (c) 2017, 2020 hippo91 <guillaume.peillex@gmail.com>
Claudiu Popa9ff44de2017-12-15 12:24:15 +010014# Copyright (c) 2017 Daniela Plascencia <daplascen@gmail.com>
Claudiu Popa3a8635a2018-07-15 11:36:36 +020015# Copyright (c) 2018 Sushobhit <31987769+sushobhit27@users.noreply.github.com>
16# Copyright (c) 2018 Ryan McGuire <ryan@enigmacurry.com>
17# Copyright (c) 2018 thernstig <30827238+thernstig@users.noreply.github.com>
18# Copyright (c) 2018 Radostin Stoyanov <rst0git@users.noreply.github.com>
Pierre Sassoulasac852232021-02-21 15:13:06 +010019# Copyright (c) 2019, 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
Claudiu Popa369d9522020-04-27 11:04:14 +020020# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
hippo911f7c29c2020-08-20 18:40:19 +020021# Copyright (c) 2020 Damien Baty <damien.baty@polyconseil.fr>
Claudiu Popa369d9522020-04-27 11:04:14 +020022# Copyright (c) 2020 Anthony Sottile <asottile@umich.edu>
Pierre Sassoulasaa688de2021-07-01 14:33:09 +020023# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
Pierre Sassoulas7dece5b2021-04-24 21:26:46 +020024# Copyright (c) 2021 Andreas Finkler <andi.finkler@gmail.com>
Claudiu Popaec09cc72016-07-23 00:22:28 +030025
Claudiu Popa83227812016-06-01 16:11:29 +010026# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
Marc Mueller21290862021-07-01 12:47:58 +020027# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
Claudiu Popa83227812016-06-01 16:11:29 +010028
Sylvain Thénault6a770632009-08-06 10:24:42 +020029"""Emacs and Flymake compatible Pylint.
Sylvain Thénault981405e2008-12-03 09:57:14 +010030
Sylvain Thénault6a770632009-08-06 10:24:42 +020031This script is for integration with emacs and is compatible with flymake mode.
32
Emile Anclin85f4f8a2010-12-15 19:14:21 +010033epylint walks out of python packages before invoking pylint. This avoids
34reporting import errors that occur when a module within a package uses the
35absolute import path to get another module within this package.
Sylvain Thénault6a770632009-08-06 10:24:42 +020036
37For example:
38 - Suppose a package is structured as
39
40 a/__init__.py
41 a/b/x.py
42 a/c/y.py
43
Sylvain Thénaultba979be2013-09-03 12:22:43 +020044 - Then if y.py imports x as "from a.b import x" the following produces pylint
45 errors
Sylvain Thénault6a770632009-08-06 10:24:42 +020046
47 cd a/c; pylint y.py
48
Sylvain Thénault8746b952009-11-23 15:15:26 +010049 - The following obviously doesn't
Sylvain Thénault6a770632009-08-06 10:24:42 +020050
51 pylint a/c/y.py
52
Emile Anclin85f4f8a2010-12-15 19:14:21 +010053 - As this script will be invoked by emacs within the directory of the file
54 we are checking we need to go out of it to avoid these false positives.
Sylvain Thénault6a770632009-08-06 10:24:42 +020055
Sylvain Thénault1c06c672009-12-18 11:34:04 +010056
Sylvain Thénaultba979be2013-09-03 12:22:43 +020057You may also use py_run to run pylint with desired options and get back (or not)
58its output.
Sylvain Thénault6a770632009-08-06 10:24:42 +020059"""
Claudiu Popa28bd68d2015-10-02 18:09:09 +030060import os
Claudiu Popabcd687e2016-07-19 16:23:00 +030061import shlex
Pierre Sassoulas5ab140a2019-03-09 11:22:36 +010062import sys
Sushobhit8c5256d2018-05-27 10:42:14 +053063from io import StringIO
Pierre Sassoulas5ab140a2019-03-09 11:22:36 +010064from subprocess import PIPE, Popen
Claudiu Popa1cdb8bc2016-03-28 18:46:48 +010065
66
Manuel Vázquez Acosta2aecb5d2014-01-06 16:07:33 -050067def _get_env():
Claudiu Popa3f284242018-09-16 17:33:50 +020068 """Extracts the environment PYTHONPATH and appends the current sys.path to
69 those."""
Manuel Vázquez Acosta2aecb5d2014-01-06 16:07:33 -050070 env = dict(os.environ)
Claudiu Popa3f284242018-09-16 17:33:50 +020071 env["PYTHONPATH"] = os.pathsep.join(sys.path)
Manuel Vázquez Acosta2aecb5d2014-01-06 16:07:33 -050072 return env
Sylvain Thénault6a770632009-08-06 10:24:42 +020073
Claudiu Popa3f284242018-09-16 17:33:50 +020074
Ryan McGuire39338dc2018-04-07 03:34:51 -040075def lint(filename, options=()):
Sylvain Thénault6a770632009-08-06 10:24:42 +020076 """Pylint the given file.
77
Sylvain Thénault177e7e52013-10-07 12:17:27 +020078 When run from emacs we will be in the directory of a file, and passed its
79 filename. If this file is part of a package and is trying to import other
80 modules from within its own package or another package rooted in a directory
81 below it, pylint will classify it as a failed import.
Sylvain Thénault6a770632009-08-06 10:24:42 +020082
Sylvain Thénault177e7e52013-10-07 12:17:27 +020083 To get around this, we traverse down the directory tree to find the root of
84 the package this module is in. We then invoke pylint from this directory.
Sylvain Thénault6a770632009-08-06 10:24:42 +020085
Sylvain Thénault177e7e52013-10-07 12:17:27 +020086 Finally, we must correct the filenames in the output generated by pylint so
87 Emacs doesn't become confused (it will expect just the original filename,
88 while pylint may extend it with extra directories if we've traversed down
89 the tree)
Sylvain Thénault6a770632009-08-06 10:24:42 +020090 """
91 # traverse downwards until we are out of a python package
Pierre Sassoulas3a2a5792021-03-30 23:40:38 +020092 full_path = os.path.abspath(filename)
93 parent_path = os.path.dirname(full_path)
94 child_path = os.path.basename(full_path)
Sylvain Thénault6a770632009-08-06 10:24:42 +020095
Pierre Sassoulas3a2a5792021-03-30 23:40:38 +020096 while parent_path != "/" and os.path.exists(
97 os.path.join(parent_path, "__init__.py")
98 ):
99 child_path = os.path.join(os.path.basename(parent_path), child_path)
100 parent_path = os.path.dirname(parent_path)
Sylvain Thénault6a770632009-08-06 10:24:42 +0200101
102 # Start pylint
Sylvain Thénaultc8e20f72012-01-09 21:25:10 +0100103 # Ensure we use the python and pylint associated with the running epylint
Claudiu Popa9aa5aa52016-06-23 16:25:44 +0100104 run_cmd = "import sys; from pylint.lint import Run; Run(sys.argv[1:])"
Claudiu Popa3f284242018-09-16 17:33:50 +0200105 cmd = (
106 [sys.executable, "-c", run_cmd]
107 + [
108 "--msg-template",
109 "{path}:{line}: {category} ({msg_id}, {symbol}, {obj}) {msg}",
110 "-r",
111 "n",
112 child_path,
113 ]
114 + list(options)
115 )
DudeNr33922f3892021-04-23 20:31:21 +0200116
117 with Popen(
Claudiu Popa3f284242018-09-16 17:33:50 +0200118 cmd, stdout=PIPE, cwd=parent_path, env=_get_env(), universal_newlines=True
DudeNr33922f3892021-04-23 20:31:21 +0200119 ) as process:
Sylvain Thénault6a770632009-08-06 10:24:42 +0200120
DudeNr33922f3892021-04-23 20:31:21 +0200121 for line in process.stdout:
122 # remove pylintrc warning
123 if line.startswith("No config file found"):
124 continue
Manuel Vázquez Acostaf8fdbc32014-04-14 16:57:25 -0400125
DudeNr33922f3892021-04-23 20:31:21 +0200126 # modify the file name thats output to reverse the path traversal we made
127 parts = line.split(":")
128 if parts and parts[0] == child_path:
129 line = ":".join([filename] + parts[1:])
130 print(line, end=" ")
Sylvain Thénault6a770632009-08-06 10:24:42 +0200131
DudeNr33922f3892021-04-23 20:31:21 +0200132 process.wait()
133 return process.returncode
Sylvain Thénault981405e2008-12-03 09:57:14 +0100134
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100135
Claudiu Popa3f284242018-09-16 17:33:50 +0200136def py_run(command_options="", return_std=False, stdout=None, stderr=None):
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200137 """Run pylint from python
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100138
139 ``command_options`` is a string containing ``pylint`` command line options;
Jakob Normark962ade12014-11-06 18:56:20 +0100140 ``return_std`` (boolean) indicates return of created standard output
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100141 and error (see below);
Jakob Normark962ade12014-11-06 18:56:20 +0100142 ``stdout`` and ``stderr`` are 'file-like' objects in which standard output
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100143 could be written.
144
145 Calling agent is responsible for stdout/err management (creation, close).
Jakob Normark962ade12014-11-06 18:56:20 +0100146 Default standard output and error are those from sys,
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100147 or standalone ones (``subprocess.PIPE``) are used
148 if they are not set and ``return_std``.
149
150 If ``return_std`` is set to ``True``, this function returns a 2-uple
Jakob Normark962ade12014-11-06 18:56:20 +0100151 containing standard output and error related to created process,
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100152 as follows: ``(stdout, stderr)``.
153
Jakob Normark962ade12014-11-06 18:56:20 +0100154 To silently run Pylint on a module, and get its standard output and error:
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100155 >>> (pylint_stdout, pylint_stderr) = py_run( 'module_name.py', True)
156 """
Claudiu Popa95232ca2019-03-28 09:33:20 +0100157 # Detect if we use Python as executable or not, else default to `python`
158 executable = sys.executable if "python" in sys.executable else "python"
159
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100160 # Create command line to call pylint
Claudiu Popa95232ca2019-03-28 09:33:20 +0100161 epylint_part = [executable, "-c", "from pylint import epylint;epylint.Run()"]
Claudiu Popa3f284242018-09-16 17:33:50 +0200162 options = shlex.split(command_options, posix=not sys.platform.startswith("win"))
Claudiu Popabcd687e2016-07-19 16:23:00 +0300163 cli = epylint_part + options
164
Jakob Normark962ade12014-11-06 18:56:20 +0100165 # Providing standard output and/or error if not set
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100166 if stdout is None:
167 if return_std:
168 stdout = PIPE
169 else:
170 stdout = sys.stdout
171 if stderr is None:
172 if return_std:
173 stderr = PIPE
174 else:
175 stderr = sys.stderr
176 # Call pylint in a subprocess
DudeNr33922f3892021-04-23 20:31:21 +0200177 with Popen(
Claudiu Popa3f284242018-09-16 17:33:50 +0200178 cli,
179 shell=False,
180 stdout=stdout,
181 stderr=stderr,
182 env=_get_env(),
183 universal_newlines=True,
DudeNr33922f3892021-04-23 20:31:21 +0200184 ) as process:
185 proc_stdout, proc_stderr = process.communicate()
186 # Return standard output and error
187 if return_std:
188 return StringIO(proc_stdout), StringIO(proc_stderr)
189 return None
Sylvain Thénault1c06c672009-12-18 11:34:04 +0100190
191
Sylvain Thénault4b316ba2012-09-14 13:25:22 +0200192def Run():
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200193 if len(sys.argv) == 1:
Brett Cannon29bb89f2014-08-29 11:16:29 -0400194 print("Usage: %s <filename> [options]" % sys.argv[0])
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200195 sys.exit(1)
Pierre Sassoulas3a2a5792021-03-30 23:40:38 +0200196 elif not os.path.exists(sys.argv[1]):
Brett Cannon29bb89f2014-08-29 11:16:29 -0400197 print("%s does not exist" % sys.argv[1])
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200198 sys.exit(1)
199 else:
Manuel Vázquez Acostaf8fdbc32014-04-14 16:57:25 -0400200 sys.exit(lint(sys.argv[1], sys.argv[2:]))
Sylvain Thénaultba979be2013-09-03 12:22:43 +0200201
Sylvain Thénault4b316ba2012-09-14 13:25:22 +0200202
Claudiu Popa3f284242018-09-16 17:33:50 +0200203if __name__ == "__main__":
Sylvain Thénault4b316ba2012-09-14 13:25:22 +0200204 Run()