diff options
Diffstat (limited to 'tools/lint/python')
-rw-r--r-- | tools/lint/python/__init__.py | 3 | ||||
-rw-r--r-- | tools/lint/python/black.py | 179 | ||||
-rw-r--r-- | tools/lint/python/black_requirements.in | 4 | ||||
-rw-r--r-- | tools/lint/python/black_requirements.txt | 118 | ||||
-rw-r--r-- | tools/lint/python/flake8.py | 215 | ||||
-rw-r--r-- | tools/lint/python/flake8_requirements.in | 4 | ||||
-rw-r--r-- | tools/lint/python/flake8_requirements.txt | 41 | ||||
-rw-r--r-- | tools/lint/python/isort.py | 138 | ||||
-rw-r--r-- | tools/lint/python/isort_requirements.in | 1 | ||||
-rw-r--r-- | tools/lint/python/isort_requirements.txt | 10 | ||||
-rw-r--r-- | tools/lint/python/l10n_lint.py | 171 | ||||
-rw-r--r-- | tools/lint/python/pylint.py | 133 | ||||
-rw-r--r-- | tools/lint/python/pylint_requirements.in | 5 | ||||
-rw-r--r-- | tools/lint/python/pylint_requirements.txt | 136 |
14 files changed, 1158 insertions, 0 deletions
diff --git a/tools/lint/python/__init__.py b/tools/lint/python/__init__.py new file mode 100644 index 0000000000..c580d191c1 --- /dev/null +++ b/tools/lint/python/__init__.py @@ -0,0 +1,3 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/tools/lint/python/black.py b/tools/lint/python/black.py new file mode 100644 index 0000000000..8c44a56951 --- /dev/null +++ b/tools/lint/python/black.py @@ -0,0 +1,179 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import platform +import re +import signal +import subprocess +import sys + +import mozpack.path as mozpath +from mozfile import which +from mozlint import result +from mozlint.pathutils import expand_exclusions +from mozprocess import ProcessHandler + +here = os.path.abspath(os.path.dirname(__file__)) +BLACK_REQUIREMENTS_PATH = os.path.join(here, "black_requirements.txt") + +BLACK_INSTALL_ERROR = """ +Unable to install correct version of black +Try to install it manually with: + $ pip install -U --require-hashes -r {} +""".strip().format( + BLACK_REQUIREMENTS_PATH +) + + +def default_bindir(): + # We use sys.prefix to find executables as that gets modified with + # virtualenv's activate_this.py, whereas sys.executable doesn't. + if platform.system() == "Windows": + return os.path.join(sys.prefix, "Scripts") + else: + return os.path.join(sys.prefix, "bin") + + +def get_black_version(binary): + """ + Returns found binary's version + """ + try: + output = subprocess.check_output( + [binary, "--version"], + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + except subprocess.CalledProcessError as e: + output = e.output + try: + # Accept `black.EXE, version ...` on Windows. + # for old version of black, the output is + # black, version 21.4b2 + # From black 21.11b1, the output is like + # black, 21.11b1 (compiled: no) + return re.match(r"black.*,( version)? (\S+)", output)[2] + except TypeError as e: + print("Could not parse the version '{}'".format(output)) + print("Error: {}".format(e)) + + +def parse_issues(config, output, paths, *, log): + would_reformat = re.compile("^would reformat (.*)$", re.I) + reformatted = re.compile("^reformatted (.*)$", re.I) + cannot_reformat = re.compile("^error: cannot format (.*?): (.*)$", re.I) + results = [] + for line in output: + line = line.decode("utf-8") + if line.startswith("All done!") or line.startswith("Oh no!"): + break + + match = would_reformat.match(line) + if match: + res = {"path": match.group(1), "level": "error"} + results.append(result.from_config(config, **res)) + continue + + match = reformatted.match(line) + if match: + res = {"path": match.group(1), "level": "warning", "message": "reformatted"} + results.append(result.from_config(config, **res)) + continue + + match = cannot_reformat.match(line) + if match: + res = {"path": match.group(1), "level": "error", "message": match.group(2)} + results.append(result.from_config(config, **res)) + continue + + log.debug("Unhandled line", line) + return results + + +class BlackProcess(ProcessHandler): + def __init__(self, config, *args, **kwargs): + self.config = config + kwargs["stream"] = False + ProcessHandler.__init__(self, *args, **kwargs) + + def run(self, *args, **kwargs): + orig = signal.signal(signal.SIGINT, signal.SIG_IGN) + ProcessHandler.run(self, *args, **kwargs) + signal.signal(signal.SIGINT, orig) + + +def run_process(config, cmd): + proc = BlackProcess(config, cmd) + proc.run() + try: + proc.wait() + except KeyboardInterrupt: + proc.kill() + + return proc.output + + +def setup(root, **lintargs): + log = lintargs["log"] + virtualenv_bin_path = lintargs.get("virtualenv_bin_path") + # Using `which` searches multiple directories and handles `.exe` on Windows. + binary = which("black", path=(virtualenv_bin_path, default_bindir())) + + if binary and os.path.exists(binary): + binary = mozpath.normsep(binary) + log.debug("Looking for black at {}".format(binary)) + version = get_black_version(binary) + versions = [ + line.split()[0].strip() + for line in open(BLACK_REQUIREMENTS_PATH).readlines() + if line.startswith("black==") + ] + if ["black=={}".format(version)] == versions: + log.debug("Black is present with expected version {}".format(version)) + return 0 + else: + log.debug("Black is present but unexpected version {}".format(version)) + + log.debug("Black needs to be installed or updated") + virtualenv_manager = lintargs["virtualenv_manager"] + try: + virtualenv_manager.install_pip_requirements(BLACK_REQUIREMENTS_PATH, quiet=True) + except subprocess.CalledProcessError: + print(BLACK_INSTALL_ERROR) + return 1 + + +def run_black(config, paths, fix=None, *, log, virtualenv_bin_path): + fixed = 0 + binary = os.path.join(virtualenv_bin_path or default_bindir(), "black") + + log.debug("Black version {}".format(get_black_version(binary))) + + cmd_args = [binary] + if not fix: + cmd_args.append("--check") + + base_command = cmd_args + paths + log.debug("Command: {}".format(" ".join(base_command))) + output = parse_issues(config, run_process(config, base_command), paths, log=log) + + # black returns an issue for fixed files as well + for eachIssue in output: + if eachIssue.message == "reformatted": + fixed += 1 + + return {"results": output, "fixed": fixed} + + +def lint(paths, config, fix=None, **lintargs): + files = list(expand_exclusions(paths, config, lintargs["root"])) + + return run_black( + config, + files, + fix=fix, + log=lintargs["log"], + virtualenv_bin_path=lintargs.get("virtualenv_bin_path"), + ) diff --git a/tools/lint/python/black_requirements.in b/tools/lint/python/black_requirements.in new file mode 100644 index 0000000000..e5efa47492 --- /dev/null +++ b/tools/lint/python/black_requirements.in @@ -0,0 +1,4 @@ +black==21.11b1 +typing-extensions==3.10.0.2 +dataclasses==0.6 + diff --git a/tools/lint/python/black_requirements.txt b/tools/lint/python/black_requirements.txt new file mode 100644 index 0000000000..944e5a83ec --- /dev/null +++ b/tools/lint/python/black_requirements.txt @@ -0,0 +1,118 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile --generate-hashes --output-file=tools/lint/python/black_requirements.txt tools/lint/python/black_requirements.in +# +black==21.11b1 \ + --hash=sha256:802c6c30b637b28645b7fde282ed2569c0cd777dbe493a41b6a03c1d903f99ac \ + --hash=sha256:a042adbb18b3262faad5aff4e834ff186bb893f95ba3a8013f09de1e5569def2 + # via -r tools/lint/python/black_requirements.in +click==8.0.3 \ + --hash=sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3 \ + --hash=sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b + # via black +dataclasses==0.6 \ + --hash=sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f \ + --hash=sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84 + # via -r tools/lint/python/black_requirements.in +mypy-extensions==0.4.3 \ + --hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \ + --hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8 + # via black +pathspec==0.9.0 \ + --hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a \ + --hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1 + # via black +platformdirs==2.4.0 \ + --hash=sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2 \ + --hash=sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d + # via black +regex==2021.11.10 \ + --hash=sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f \ + --hash=sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc \ + --hash=sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4 \ + --hash=sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4 \ + --hash=sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8 \ + --hash=sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f \ + --hash=sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a \ + --hash=sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef \ + --hash=sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f \ + --hash=sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc \ + --hash=sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50 \ + --hash=sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d \ + --hash=sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d \ + --hash=sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733 \ + --hash=sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36 \ + --hash=sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345 \ + --hash=sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0 \ + --hash=sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12 \ + --hash=sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646 \ + --hash=sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667 \ + --hash=sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244 \ + --hash=sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29 \ + --hash=sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec \ + --hash=sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf \ + --hash=sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4 \ + --hash=sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449 \ + --hash=sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a \ + --hash=sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d \ + --hash=sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb \ + --hash=sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e \ + --hash=sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83 \ + --hash=sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e \ + --hash=sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a \ + --hash=sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94 \ + --hash=sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc \ + --hash=sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e \ + --hash=sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965 \ + --hash=sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0 \ + --hash=sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36 \ + --hash=sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec \ + --hash=sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23 \ + --hash=sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7 \ + --hash=sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe \ + --hash=sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6 \ + --hash=sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b \ + --hash=sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb \ + --hash=sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b \ + --hash=sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30 \ + --hash=sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e + # via black +tomli==1.2.2 \ + --hash=sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee \ + --hash=sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade + # via black +typed-ast==1.5.2 \ + --hash=sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e \ + --hash=sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344 \ + --hash=sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266 \ + --hash=sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a \ + --hash=sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd \ + --hash=sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d \ + --hash=sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837 \ + --hash=sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098 \ + --hash=sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e \ + --hash=sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27 \ + --hash=sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b \ + --hash=sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596 \ + --hash=sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76 \ + --hash=sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30 \ + --hash=sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4 \ + --hash=sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78 \ + --hash=sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca \ + --hash=sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985 \ + --hash=sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb \ + --hash=sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88 \ + --hash=sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7 \ + --hash=sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5 \ + --hash=sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e \ + --hash=sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7 + # via black +typing-extensions==3.10.0.2 \ + --hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \ + --hash=sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7 \ + --hash=sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34 + # via + # -r tools/lint/python/black_requirements.in + # black diff --git a/tools/lint/python/flake8.py b/tools/lint/python/flake8.py new file mode 100644 index 0000000000..88fec87822 --- /dev/null +++ b/tools/lint/python/flake8.py @@ -0,0 +1,215 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import json +import os +import platform +import subprocess +import sys + +import mozfile +import mozpack.path as mozpath +from mozlint import result +from mozlint.pathutils import expand_exclusions + +here = os.path.abspath(os.path.dirname(__file__)) +FLAKE8_REQUIREMENTS_PATH = os.path.join(here, "flake8_requirements.txt") + +FLAKE8_NOT_FOUND = """ +Could not find flake8! Install flake8 and try again. + + $ pip install -U --require-hashes -r {} +""".strip().format( + FLAKE8_REQUIREMENTS_PATH +) + + +FLAKE8_INSTALL_ERROR = """ +Unable to install correct version of flake8 +Try to install it manually with: + $ pip install -U --require-hashes -r {} +""".strip().format( + FLAKE8_REQUIREMENTS_PATH +) + +LINE_OFFSETS = { + # continuation line under-indented for hanging indent + "E121": (-1, 2), + # continuation line missing indentation or outdented + "E122": (-1, 2), + # continuation line over-indented for hanging indent + "E126": (-1, 2), + # continuation line over-indented for visual indent + "E127": (-1, 2), + # continuation line under-indented for visual indent + "E128": (-1, 2), + # continuation line unaligned for hanging indend + "E131": (-1, 2), + # expected 1 blank line, found 0 + "E301": (-1, 2), + # expected 2 blank lines, found 1 + "E302": (-2, 3), +} +"""Maps a flake8 error to a lineoffset tuple. + +The offset is of the form (lineno_offset, num_lines) and is passed +to the lineoffset property of an `Issue`. +""" + + +def default_bindir(): + # We use sys.prefix to find executables as that gets modified with + # virtualenv's activate_this.py, whereas sys.executable doesn't. + if platform.system() == "Windows": + return os.path.join(sys.prefix, "Scripts") + else: + return os.path.join(sys.prefix, "bin") + + +class NothingToLint(Exception): + """Exception used to bail out of flake8's internals if all the specified + files were excluded. + """ + + +def setup(root, **lintargs): + virtualenv_manager = lintargs["virtualenv_manager"] + try: + virtualenv_manager.install_pip_requirements( + FLAKE8_REQUIREMENTS_PATH, quiet=True + ) + except subprocess.CalledProcessError: + print(FLAKE8_INSTALL_ERROR) + return 1 + + +def lint(paths, config, **lintargs): + + root = lintargs["root"] + virtualenv_bin_path = lintargs.get("virtualenv_bin_path") + config_path = os.path.join(root, ".flake8") + + results = run(paths, config, **lintargs) + fixed = 0 + + if lintargs.get("fix"): + # fix and run again to count remaining issues + fixed = len(results) + fix_cmd = [ + os.path.join(virtualenv_bin_path or default_bindir(), "autopep8"), + "--global-config", + config_path, + "--in-place", + "--recursive", + ] + + if config.get("exclude"): + fix_cmd.extend(["--exclude", ",".join(config["exclude"])]) + + subprocess.call(fix_cmd + paths) + + results = run(paths, config, **lintargs) + + fixed = fixed - len(results) + + return {"results": results, "fixed": fixed} + + +def run(paths, config, **lintargs): + from flake8 import __version__ as flake8_version + from flake8.main.application import Application + + log = lintargs["log"] + root = lintargs["root"] + config_path = os.path.join(root, ".flake8") + + # Run flake8. + app = Application() + log.debug("flake8 version={}".format(flake8_version)) + + output_file = mozfile.NamedTemporaryFile(mode="r") + flake8_cmd = [ + "--config", + config_path, + "--output-file", + output_file.name, + "--format", + '{"path":"%(path)s","lineno":%(row)s,' + '"column":%(col)s,"rule":"%(code)s","message":"%(text)s"}', + "--filename", + ",".join(["*.{}".format(e) for e in config["extensions"]]), + ] + log.debug("Command: {}".format(" ".join(flake8_cmd))) + + orig_make_file_checker_manager = app.make_file_checker_manager + + def wrap_make_file_checker_manager(self): + """Flake8 is very inefficient when it comes to applying exclusion + rules, using `expand_exclusions` to turn directories into a list of + relevant python files is an order of magnitude faster. + + Hooking into flake8 here also gives us a convenient place to merge the + `exclude` rules specified in the root .flake8 with the ones added by + tools/lint/mach_commands.py. + """ + # Ignore exclude rules if `--no-filter` was passed in. + config.setdefault("exclude", []) + if lintargs.get("use_filters", True): + config["exclude"].extend(map(mozpath.normpath, self.options.exclude)) + + # Since we use the root .flake8 file to store exclusions, we haven't + # properly filtered the paths through mozlint's `filterpaths` function + # yet. This mimics that though there could be other edge cases that are + # different. Maybe we should call `filterpaths` directly, though for + # now that doesn't appear to be necessary. + filtered = [ + p for p in paths if not any(p.startswith(e) for e in config["exclude"]) + ] + + self.options.filenames = self.options.filenames + list( + expand_exclusions(filtered, config, root) + ) + + if not self.options.filenames: + raise NothingToLint + return orig_make_file_checker_manager() + + app.make_file_checker_manager = wrap_make_file_checker_manager.__get__( + app, Application + ) + + # Make sure to run from repository root so exclusions are joined to the + # repository root and not the current working directory. + oldcwd = os.getcwd() + os.chdir(root) + try: + app.run(flake8_cmd) + except NothingToLint: + pass + finally: + os.chdir(oldcwd) + + results = [] + + WARNING_RULES = set(config.get("warning-rules", [])) + + def process_line(line): + # Escape slashes otherwise JSON conversion will not work + line = line.replace("\\", "\\\\") + try: + res = json.loads(line) + except ValueError: + print("Non JSON output from linter, will not be processed: {}".format(line)) + return + + if res.get("code") in LINE_OFFSETS: + res["lineoffset"] = LINE_OFFSETS[res["code"]] + + if res["rule"] in WARNING_RULES: + res["level"] = "warning" + + results.append(result.from_config(config, **res)) + + list(map(process_line, output_file.readlines())) + return results diff --git a/tools/lint/python/flake8_requirements.in b/tools/lint/python/flake8_requirements.in new file mode 100644 index 0000000000..0a9262b9c6 --- /dev/null +++ b/tools/lint/python/flake8_requirements.in @@ -0,0 +1,4 @@ +flake8==5.0.4 +zipp==0.5 +autopep8==1.7.0 +typing-extensions==3.10.0.2 diff --git a/tools/lint/python/flake8_requirements.txt b/tools/lint/python/flake8_requirements.txt new file mode 100644 index 0000000000..36879d20c8 --- /dev/null +++ b/tools/lint/python/flake8_requirements.txt @@ -0,0 +1,41 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile --generate-hashes tools/lint/python/flake8_requirements.in +# +autopep8==1.7.0 \ + --hash=sha256:6f09e90a2be784317e84dc1add17ebfc7abe3924239957a37e5040e27d812087 \ + --hash=sha256:ca9b1a83e53a7fad65d731dc7a2a2d50aa48f43850407c59f6a1a306c4201142 + # via -r tools/lint/python/flake8_requirements.in +flake8==5.0.4 \ + --hash=sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db \ + --hash=sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248 + # via -r tools/lint/python/flake8_requirements.in +mccabe==0.7.0 \ + --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \ + --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e + # via flake8 +pycodestyle==2.9.1 \ + --hash=sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785 \ + --hash=sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b + # via + # autopep8 + # flake8 +pyflakes==2.5.0 \ + --hash=sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2 \ + --hash=sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3 + # via flake8 +toml==0.10.2 \ + --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ + --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f + # via autopep8 +typing-extensions==3.10.0.2 \ + --hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \ + --hash=sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7 \ + --hash=sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34 + # via -r tools/lint/python/flake8_requirements.in +zipp==0.5 \ + --hash=sha256:46dfd547d9ccbf8bdc26ecea52818046bb28509f12bb6a0de1cd66ab06e9a9be \ + --hash=sha256:d7ac25f895fb65bff937b381353c14eb1fa23d35f40abd72a5342cd57eb57fd1 + # via -r tools/lint/python/flake8_requirements.in diff --git a/tools/lint/python/isort.py b/tools/lint/python/isort.py new file mode 100644 index 0000000000..9af0e42d10 --- /dev/null +++ b/tools/lint/python/isort.py @@ -0,0 +1,138 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import configparser +import os +import platform +import re +import signal +import subprocess +import sys + +import mozpack.path as mozpath +from mozlint import result +from mozlint.pathutils import expand_exclusions +from mozprocess import ProcessHandler + +here = os.path.abspath(os.path.dirname(__file__)) +ISORT_REQUIREMENTS_PATH = os.path.join(here, "isort_requirements.txt") + +ISORT_INSTALL_ERROR = """ +Unable to install correct version of isort +Try to install it manually with: + $ pip install -U --require-hashes -r {} +""".strip().format( + ISORT_REQUIREMENTS_PATH +) + + +def default_bindir(): + # We use sys.prefix to find executables as that gets modified with + # virtualenv's activate_this.py, whereas sys.executable doesn't. + if platform.system() == "Windows": + return os.path.join(sys.prefix, "Scripts") + return os.path.join(sys.prefix, "bin") + + +def parse_issues(config, output, *, log): + would_sort = re.compile( + "^ERROR: (.*?) Imports are incorrectly sorted and/or formatted.$", re.I + ) + sorted = re.compile("^Fixing (.*)$", re.I) + results = [] + for line in output: + line = line.decode("utf-8") + + match = would_sort.match(line) + if match: + res = {"path": match.group(1)} + results.append(result.from_config(config, **res)) + continue + + match = sorted.match(line) + if match: + res = {"path": match.group(1), "message": "sorted"} + results.append(result.from_config(config, **res)) + continue + + log.debug("Unhandled line", line) + return results + + +class IsortProcess(ProcessHandler): + def __init__(self, config, *args, **kwargs): + self.config = config + kwargs["stream"] = False + ProcessHandler.__init__(self, *args, **kwargs) + + def run(self, *args, **kwargs): + orig = signal.signal(signal.SIGINT, signal.SIG_IGN) + ProcessHandler.run(self, *args, **kwargs) + signal.signal(signal.SIGINT, orig) + + +def run_process(config, cmd): + proc = IsortProcess(config, cmd) + proc.run() + try: + proc.wait() + except KeyboardInterrupt: + proc.kill() + + return proc.output + + +def setup(root, **lintargs): + virtualenv_manager = lintargs["virtualenv_manager"] + try: + virtualenv_manager.install_pip_requirements(ISORT_REQUIREMENTS_PATH, quiet=True) + except subprocess.CalledProcessError: + print(ISORT_INSTALL_ERROR) + return 1 + + +def lint(paths, config, **lintargs): + from isort import __version__ as isort_version + + binary = os.path.join( + lintargs.get("virtualenv_bin_path") or default_bindir(), "isort" + ) + + log = lintargs["log"] + root = lintargs["root"] + + log.debug("isort version {}".format(isort_version)) + + cmd_args = [ + binary, + "--resolve-all-configs", + "--config-root", + root, + ] + if not lintargs.get("fix"): + cmd_args.append("--check-only") + + # We merge exclusion rules from .flake8 to avoid having to repeat the same exclusions twice. + flake8_config_path = os.path.join(root, ".flake8") + flake8_config = configparser.ConfigParser() + flake8_config.read(flake8_config_path) + config["exclude"].extend( + mozpath.normpath(p.strip()) + for p in flake8_config.get("flake8", "exclude").split(",") + ) + + paths = list(expand_exclusions(paths, config, lintargs["root"])) + if len(paths) == 0: + return {"results": [], "fixed": 0} + + base_command = cmd_args + paths + log.debug("Command: {}".format(" ".join(base_command))) + + output = run_process(config, base_command) + + results = parse_issues(config, output, log=log) + + fixed = sum(1 for issue in results if issue.message == "sorted") + + return {"results": results, "fixed": fixed} diff --git a/tools/lint/python/isort_requirements.in b/tools/lint/python/isort_requirements.in new file mode 100644 index 0000000000..8eeb146b1a --- /dev/null +++ b/tools/lint/python/isort_requirements.in @@ -0,0 +1 @@ +isort==5.10.1 diff --git a/tools/lint/python/isort_requirements.txt b/tools/lint/python/isort_requirements.txt new file mode 100644 index 0000000000..84df6d2093 --- /dev/null +++ b/tools/lint/python/isort_requirements.txt @@ -0,0 +1,10 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile --generate-hashes --output-file=tools/lint/python/isort_requirements.txt tools/lint/python/isort_requirements.in +# +isort==5.10.1 \ + --hash=sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7 \ + --hash=sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951 + # via -r tools/lint/python/isort_requirements.in diff --git a/tools/lint/python/l10n_lint.py b/tools/lint/python/l10n_lint.py new file mode 100644 index 0000000000..ef3269ef2a --- /dev/null +++ b/tools/lint/python/l10n_lint.py @@ -0,0 +1,171 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +from datetime import datetime, timedelta + +import mozversioncontrol.repoupdate +from compare_locales import parser +from compare_locales.lint.linter import L10nLinter +from compare_locales.lint.util import l10n_base_reference_and_tests +from compare_locales.paths import ProjectFiles, TOMLParser +from mach import util as mach_util +from mozlint import pathutils, result +from mozpack import path as mozpath + +LOCALE = "gecko-strings" +STRINGS_REPO = "https://hg.mozilla.org/l10n/gecko-strings" + +PULL_AFTER = timedelta(days=2) + +# Wrapper to call lint_strings with mozilla-central configuration +# comm-central defines its own wrapper since comm-central strings are +# in separate repositories +def lint(paths, lintconfig, **lintargs): + return lint_strings(LOCALE, paths, lintconfig, **lintargs) + + +def lint_strings(locale, paths, lintconfig, **lintargs): + l10n_base = mach_util.get_state_dir() + root = lintargs["root"] + exclude = lintconfig.get("exclude") + extensions = lintconfig.get("extensions") + + # Load l10n.toml configs + l10nconfigs = load_configs(lintconfig, root, l10n_base, locale) + + # Check include paths in l10n.yml if it's in our given paths + # Only the l10n.yml will show up here, but if the l10n.toml files + # change, we also get the l10n.yml as the toml files are listed as + # support files. + if lintconfig["path"] in paths: + results = validate_linter_includes(lintconfig, l10nconfigs, lintargs) + paths.remove(lintconfig["path"]) + else: + results = [] + + all_files = [] + for p in paths: + fp = pathutils.FilterPath(p) + if fp.isdir: + for _, fileobj in fp.finder: + all_files.append(fileobj.path) + if fp.isfile: + all_files.append(p) + # Filter again, our directories might have picked up files the + # explicitly excluded in the l10n.yml configuration. + # `browser/locales/en-US/firefox-l10n.js` is a good example. + all_files, _ = pathutils.filterpaths( + lintargs["root"], + all_files, + lintconfig["include"], + exclude=exclude, + extensions=extensions, + ) + # These should be excluded in l10n.yml + skips = {p for p in all_files if not parser.hasParser(p)} + results.extend( + result.from_config( + lintconfig, + level="warning", + path=path, + message="file format not supported in compare-locales", + ) + for path in skips + ) + all_files = [p for p in all_files if p not in skips] + files = ProjectFiles(locale, l10nconfigs) + + get_reference_and_tests = l10n_base_reference_and_tests(files) + + linter = MozL10nLinter(lintconfig) + results += linter.lint(all_files, get_reference_and_tests) + return results + + +# Similar to the lint/lint_strings wrapper setup, for comm-central support. +def gecko_strings_setup(**lint_args): + return strings_repo_setup(STRINGS_REPO, LOCALE) + + +def strings_repo_setup(repo, locale): + gs = mozpath.join(mach_util.get_state_dir(), locale) + marker = mozpath.join(gs, ".hg", "l10n_pull_marker") + try: + last_pull = datetime.fromtimestamp(os.stat(marker).st_mtime) + skip_clone = datetime.now() < last_pull + PULL_AFTER + except OSError: + skip_clone = False + if skip_clone: + return + try: + hg = mozversioncontrol.get_tool_path("hg") + except mozversioncontrol.MissingVCSTool: + if os.environ.get("MOZ_AUTOMATION"): + raise + print("warning: l10n linter requires Mercurial but was unable to find 'hg'") + return 1 + mozversioncontrol.repoupdate.update_mercurial_repo(hg, repo, gs) + with open(marker, "w") as fh: + fh.flush() + + +def load_configs(lintconfig, root, l10n_base, locale): + """Load l10n configuration files specified in the linter configuration.""" + configs = [] + env = {"l10n_base": l10n_base} + for toml in lintconfig["l10n_configs"]: + cfg = TOMLParser().parse( + mozpath.join(root, toml), env=env, ignore_missing_includes=True + ) + cfg.set_locales([locale], deep=True) + configs.append(cfg) + return configs + + +def validate_linter_includes(lintconfig, l10nconfigs, lintargs): + """Check l10n.yml config against l10n.toml configs.""" + reference_paths = set( + mozpath.relpath(p["reference"].prefix, lintargs["root"]) + for project in l10nconfigs + for config in project.configs + for p in config.paths + ) + # Just check for directories + reference_dirs = sorted(p for p in reference_paths if os.path.isdir(p)) + missing_in_yml = [ + refd for refd in reference_dirs if refd not in lintconfig["include"] + ] + # These might be subdirectories in the config, though + missing_in_yml = [ + d + for d in missing_in_yml + if not any(d.startswith(parent + "/") for parent in lintconfig["include"]) + ] + if missing_in_yml: + dirs = ", ".join(missing_in_yml) + return [ + result.from_config( + lintconfig, + path=lintconfig["path"], + message="l10n.yml out of sync with l10n.toml, add: " + dirs, + ) + ] + return [] + + +class MozL10nLinter(L10nLinter): + """Subclass linter to generate the right result type.""" + + def __init__(self, lintconfig): + super(MozL10nLinter, self).__init__() + self.lintconfig = lintconfig + + def lint(self, files, get_reference_and_tests): + return [ + result.from_config(self.lintconfig, **result_data) + for result_data in super(MozL10nLinter, self).lint( + files, get_reference_and_tests + ) + ] diff --git a/tools/lint/python/pylint.py b/tools/lint/python/pylint.py new file mode 100644 index 0000000000..8bb0c68b87 --- /dev/null +++ b/tools/lint/python/pylint.py @@ -0,0 +1,133 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import json +import os +import signal +import subprocess + +from mach.site import InstallPipRequirementsException +from mozlint import result +from mozlint.pathutils import expand_exclusions +from mozprocess import ProcessHandler + +here = os.path.abspath(os.path.dirname(__file__)) +PYLINT_REQUIREMENTS_PATH = os.path.join(here, "pylint_requirements.txt") + +PYLINT_NOT_FOUND = """ +Could not find pylint! Install pylint and try again. + + $ pip install -U --require-hashes -r {} +""".strip().format( + PYLINT_REQUIREMENTS_PATH +) + + +PYLINT_INSTALL_ERROR = """ +Unable to install correct version of pylint +Try to install it manually with: + $ pip install -U --require-hashes -r {} +""".strip().format( + PYLINT_REQUIREMENTS_PATH +) + + +class PylintProcess(ProcessHandler): + def __init__(self, config, *args, **kwargs): + self.config = config + kwargs["stream"] = False + kwargs["universal_newlines"] = True + ProcessHandler.__init__(self, *args, **kwargs) + + def run(self, *args, **kwargs): + orig = signal.signal(signal.SIGINT, signal.SIG_IGN) + ProcessHandler.run(self, *args, **kwargs) + signal.signal(signal.SIGINT, orig) + + +def setup(root, **lintargs): + virtualenv_manager = lintargs["virtualenv_manager"] + try: + virtualenv_manager.install_pip_requirements( + PYLINT_REQUIREMENTS_PATH, + quiet=True, + ) + except (subprocess.CalledProcessError, InstallPipRequirementsException): + print(PYLINT_INSTALL_ERROR) + return 1 + + +def get_pylint_binary(): + return "pylint" + + +def run_process(config, cmd): + proc = PylintProcess(config, cmd) + proc.run() + try: + proc.wait() + except KeyboardInterrupt: + proc.kill() + + return proc.output + + +def parse_issues(log, config, issues_json, path): + results = [] + + try: + issues = json.loads(issues_json) + except json.decoder.JSONDecodeError: + log.debug("Could not parse the output:") + log.debug("pylint output: {}".format(issues_json)) + return [] + + for issue in issues: + res = { + "path": issue["path"], + "level": issue["type"], + "lineno": issue["line"], + "column": issue["column"], + "message": issue["message"], + "rule": issue["message-id"], + } + results.append(result.from_config(config, **res)) + return results + + +def get_pylint_version(binary): + return subprocess.check_output( + [binary, "--version"], + universal_newlines=True, + stderr=subprocess.STDOUT, + ) + + +def lint(paths, config, **lintargs): + log = lintargs["log"] + + binary = get_pylint_binary() + + log = lintargs["log"] + paths = list(expand_exclusions(paths, config, lintargs["root"])) + + cmd_args = [binary] + results = [] + + # list from https://code.visualstudio.com/docs/python/linting#_pylint + # And ignore a bit more elements + cmd_args += [ + "-fjson", + "--disable=all", + "--enable=F,E,unreachable,duplicate-key,unnecessary-semicolon,global-variable-not-assigned,unused-variable,binary-op-exception,bad-format-string,anomalous-backslash-in-string,bad-open-mode,no-else-return", # NOQA: E501 + "--disable=import-error,no-member", + ] + + base_command = cmd_args + paths + log.debug("Command: {}".format(" ".join(cmd_args))) + log.debug("pylint version: {}".format(get_pylint_version(binary))) + output = " ".join(run_process(config, base_command)) + results = parse_issues(log, config, str(output), []) + + return results diff --git a/tools/lint/python/pylint_requirements.in b/tools/lint/python/pylint_requirements.in new file mode 100644 index 0000000000..c584cdf2a6 --- /dev/null +++ b/tools/lint/python/pylint_requirements.in @@ -0,0 +1,5 @@ +pylint==2.15.8 +dill==0.3.4 +tomli==1.2.2 +typing-extensions==3.10.0.2 +tomlkit==0.10.1 diff --git a/tools/lint/python/pylint_requirements.txt b/tools/lint/python/pylint_requirements.txt new file mode 100644 index 0000000000..093b1ce402 --- /dev/null +++ b/tools/lint/python/pylint_requirements.txt @@ -0,0 +1,136 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile --generate-hashes tools/lint/python/pylint_requirements.in +# +astroid==2.12.13 \ + --hash=sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907 \ + --hash=sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7 + # via pylint +dill==0.3.4 \ + --hash=sha256:7e40e4a70304fd9ceab3535d36e58791d9c4a776b38ec7f7ec9afc8d3dca4d4f \ + --hash=sha256:9f9734205146b2b353ab3fec9af0070237b6ddae78452af83d2fca84d739e675 + # via + # -r tools/lint/python/pylint_requirements.in + # pylint +isort==5.11.2 \ + --hash=sha256:dd8bbc5c0990f2a095d754e50360915f73b4c26fc82733eb5bfc6b48396af4d2 \ + --hash=sha256:e486966fba83f25b8045f8dd7455b0a0d1e4de481e1d7ce4669902d9fb85e622 + # via pylint +lazy-object-proxy==1.8.0 \ + --hash=sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada \ + --hash=sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d \ + --hash=sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7 \ + --hash=sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe \ + --hash=sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd \ + --hash=sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c \ + --hash=sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858 \ + --hash=sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288 \ + --hash=sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec \ + --hash=sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f \ + --hash=sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891 \ + --hash=sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c \ + --hash=sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25 \ + --hash=sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156 \ + --hash=sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8 \ + --hash=sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f \ + --hash=sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e \ + --hash=sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0 \ + --hash=sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b + # via astroid +mccabe==0.7.0 \ + --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \ + --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e + # via pylint +platformdirs==2.6.0 \ + --hash=sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca \ + --hash=sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e + # via pylint +pylint==2.15.8 \ + --hash=sha256:ea82cd6a1e11062dc86d555d07c021b0fb65afe39becbe6fe692efd6c4a67443 \ + --hash=sha256:ec4a87c33da054ab86a6c79afa6771dc8765cb5631620053e727fcf3ef8cbed7 + # via -r tools/lint/python/pylint_requirements.in +tomli==1.2.2 \ + --hash=sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee \ + --hash=sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade + # via + # -r tools/lint/python/pylint_requirements.in + # pylint +tomlkit==0.10.1 \ + --hash=sha256:3c517894eadef53e9072d343d37e4427b8f0b6200a70b7c9a19b2ebd1f53b951 \ + --hash=sha256:3eba517439dcb2f84cf39f4f85fd2c3398309823a3c75ac3e73003638daf7915 + # via + # -r tools/lint/python/pylint_requirements.in + # pylint +typing-extensions==3.10.0.2 \ + --hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \ + --hash=sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7 \ + --hash=sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34 + # via -r tools/lint/python/pylint_requirements.in +wrapt==1.14.1 \ + --hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \ + --hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \ + --hash=sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4 \ + --hash=sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2 \ + --hash=sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656 \ + --hash=sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3 \ + --hash=sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff \ + --hash=sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310 \ + --hash=sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a \ + --hash=sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57 \ + --hash=sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069 \ + --hash=sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383 \ + --hash=sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe \ + --hash=sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87 \ + --hash=sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d \ + --hash=sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b \ + --hash=sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907 \ + --hash=sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f \ + --hash=sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0 \ + --hash=sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28 \ + --hash=sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1 \ + --hash=sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853 \ + --hash=sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc \ + --hash=sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3 \ + --hash=sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3 \ + --hash=sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164 \ + --hash=sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1 \ + --hash=sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c \ + --hash=sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1 \ + --hash=sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7 \ + --hash=sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1 \ + --hash=sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320 \ + --hash=sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed \ + --hash=sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1 \ + --hash=sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248 \ + --hash=sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c \ + --hash=sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456 \ + --hash=sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77 \ + --hash=sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef \ + --hash=sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1 \ + --hash=sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7 \ + --hash=sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86 \ + --hash=sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4 \ + --hash=sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d \ + --hash=sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d \ + --hash=sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8 \ + --hash=sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5 \ + --hash=sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471 \ + --hash=sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00 \ + --hash=sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68 \ + --hash=sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3 \ + --hash=sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d \ + --hash=sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735 \ + --hash=sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d \ + --hash=sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569 \ + --hash=sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7 \ + --hash=sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59 \ + --hash=sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5 \ + --hash=sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb \ + --hash=sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b \ + --hash=sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f \ + --hash=sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462 \ + --hash=sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015 \ + --hash=sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af + # via astroid |