1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
import re
import sys
from collections import defaultdict
import pycodestyle
from flake8.formatting.default import Pylint
from flake8.style_guide import Violation
#: This is a dict that maps:
#: filename pattern ->
#: flake8 exemption code ->
#: list of patterns, for which matching lines should have codes applied.
#:
#: For each file, if the filename pattern matches, we'll add per-line
#: exemptions if any patterns in the sub-dict match.
pattern_exemptions = {
# exemptions applied only to package.py files.
r"package.py$": {
# Allow 'from spack.package import *' in packages, but no other wildcards
"F403": [
r"^from spack.package import \*$",
r"^from spack.package_defs import \*$",
],
# Exempt '@when' decorated functions from redefinition errors.
"F811": [
r"^\s*@when\(.*\)",
],
},
# exemptions applied to all files.
r".py$": {
"E501": [
r"(ssh|https?|ftp|file)\:", # URLs
r'([\'"])[0-9a-fA-F]{32,}\1', # long hex checksums
]
},
}
# compile all regular expressions.
pattern_exemptions = dict(
(
re.compile(file_pattern),
dict((code, [re.compile(p) for p in patterns]) for code, patterns in error_dict.items()),
)
for file_pattern, error_dict in pattern_exemptions.items()
)
class SpackFormatter(Pylint):
def __init__(self, options):
self.spack_errors = {}
self.error_seen = False
super().__init__(options)
def after_init(self): # type: () -> None
"""Overriding to keep format string from being unset in Default"""
pass
def beginning(self, filename):
self.filename = filename
self.file_lines = None
self.spack_errors = defaultdict(list)
for file_pattern, errors in pattern_exemptions.items():
if file_pattern.search(filename):
for code, pat_arr in errors.items():
self.spack_errors[code].extend(pat_arr)
def handle(self, error): # type: (Violation) -> None
"""Handle an error reported by Flake8.
This defaults to calling :meth:`format`, :meth:`show_source`, and
then :meth:`write`. This version implements the pattern-based ignore
behavior from `spack flake8` as a native flake8 plugin.
:param error:
This will be an instance of
:class:`~flake8.style_guide.Violation`.
:type error:
flake8.style_guide.Violation
"""
# print(error.code)
# print(error.physical_line)
# get list of patterns for this error code
pats = self.spack_errors.get(error.code, None)
# if any pattern matches, skip line
if pats is not None and any((pat.search(error.physical_line) for pat in pats)):
return
# Special F811 handling
# Prior to Python 3.8, `noqa: F811` needed to be placed on the `@when`
# line
# Starting with Python 3.8, it must be placed on the `def` line
# https://gitlab.com/pycqa/flake8/issues/583
# we can only determine if F811 should be ignored given the previous
# line, so get the previous line and check it
if self.spack_errors.get("F811", False) and error.code == "F811" and error.line_number > 1:
if self.file_lines is None:
if self.filename in {"stdin", "-", "(none)", None}:
self.file_lines = pycodestyle.stdin_get_value().splitlines(True)
else:
self.file_lines = pycodestyle.readlines(self.filename)
for pat in self.spack_errors["F811"]:
if pat.search(self.file_lines[error.line_number - 2]):
return
self.error_seen = True
line = self.format(error)
source = self.show_source(error)
self.write(line, source)
def stop(self):
"""Override stop to check whether any errors we consider to be errors
were reported.
This is a hack, but it makes flake8 behave the desired way.
"""
if not self.error_seen:
sys.exit(0)
|