]>
crepu.dev Git - config.git/blob - djavu-asus/emacs/elpy/rpc-venv/lib/python3.11/site-packages/flake8/style_guide.py
a409484036dfce831233b73a05fedfb630bfeb28
1 """Implementation of the StyleGuide used by Flake8."""
2 from __future__
import annotations
10 from typing
import Generator
11 from typing
import Sequence
13 from flake8
import defaults
14 from flake8
import statistics
15 from flake8
import utils
16 from flake8
.formatting
import base
as base_formatter
17 from flake8
.violation
import Violation
19 __all__
= ("StyleGuide",)
21 LOG
= logging
.getLogger(__name__
)
24 class Selected(enum
.Enum
):
25 """Enum representing an explicitly or implicitly selected code."""
27 Explicitly
= "explicitly selected"
28 Implicitly
= "implicitly selected"
31 class Ignored(enum
.Enum
):
32 """Enum representing an explicitly or implicitly ignored code."""
34 Explicitly
= "explicitly ignored"
35 Implicitly
= "implicitly ignored"
38 class Decision(enum
.Enum
):
39 """Enum representing whether a code should be ignored or selected."""
41 Ignored
= "ignored error"
42 Selected
= "selected error"
45 def _explicitly_chosen(
47 option
: list[str] |
None,
48 extend
: list[str] |
None,
50 ret
= [*(option
or []), *(extend
or [])]
51 return tuple(sorted(ret
, reverse
=True))
56 option
: list[str] |
None,
57 default
: tuple[str, ...],
58 extended_default
: list[str],
59 extend
: list[str] |
None,
61 # option was explicitly set, ignore the default and extended default
62 if option
is not None:
63 ret
= [*option
, *(extend
or [])]
65 ret
= [*default
, *extended_default
, *(extend
or [])]
66 return tuple(sorted(ret
, reverse
=True))
70 """A class for managing the decision process around violations.
72 This contains the logic for whether a violation should be reported or
76 def __init__(self
, options
: argparse
.Namespace
) -> None:
77 """Initialize the engine."""
78 self
.cache
: dict[str, Decision
] = {}
80 self
.selected_explicitly
= _explicitly_chosen(
81 option
=options
.select
,
82 extend
=options
.extend_select
,
84 self
.ignored_explicitly
= _explicitly_chosen(
85 option
=options
.ignore
,
86 extend
=options
.extend_ignore
,
89 self
.selected
= _select_ignore(
90 option
=options
.select
,
92 extended_default
=options
.extended_default_select
,
93 extend
=options
.extend_select
,
95 self
.ignored
= _select_ignore(
96 option
=options
.ignore
,
97 default
=defaults
.IGNORE
,
98 extended_default
=options
.extended_default_ignore
,
99 extend
=options
.extend_ignore
,
102 def was_selected(self
, code
: str) -> Selected | Ignored
:
103 """Determine if the code has been selected by the user.
105 :param code: The code for the check that has been run.
107 Selected.Implicitly if the selected list is empty,
108 Selected.Explicitly if the selected list is not empty and a match
110 Ignored.Implicitly if the selected list is not empty but no match
113 if code
.startswith(self
.selected_explicitly
):
114 return Selected
.Explicitly
115 elif code
.startswith(self
.selected
):
116 return Selected
.Implicitly
118 return Ignored
.Implicitly
120 def was_ignored(self
, code
: str) -> Selected | Ignored
:
121 """Determine if the code has been ignored by the user.
124 The code for the check that has been run.
126 Selected.Implicitly if the ignored list is empty,
127 Ignored.Explicitly if the ignored list is not empty and a match was
129 Selected.Implicitly if the ignored list is not empty but no match
132 if code
.startswith(self
.ignored_explicitly
):
133 return Ignored
.Explicitly
134 elif code
.startswith(self
.ignored
):
135 return Ignored
.Implicitly
137 return Selected
.Implicitly
139 def make_decision(self
, code
: str) -> Decision
:
140 """Decide if code should be ignored or selected."""
141 selected
= self
.was_selected(code
)
142 ignored
= self
.was_ignored(code
)
144 "The user configured %r to be %r, %r",
150 if isinstance(selected
, Selected
) and isinstance(ignored
, Selected
):
151 return Decision
.Selected
152 elif isinstance(selected
, Ignored
) and isinstance(ignored
, Ignored
):
153 return Decision
.Ignored
155 selected
is Selected
.Explicitly
156 and ignored
is not Ignored
.Explicitly
158 return Decision
.Selected
160 selected
is not Selected
.Explicitly
161 and ignored
is Ignored
.Explicitly
163 return Decision
.Ignored
164 elif selected
is Ignored
.Implicitly
and ignored
is Selected
.Implicitly
:
165 return Decision
.Ignored
167 selected
is Selected
.Explicitly
and ignored
is Ignored
.Explicitly
169 selected
is Selected
.Implicitly
and ignored
is Ignored
.Implicitly
171 # we only get here if it was in both lists: longest prefix wins
172 select
= next(s
for s
in self
.selected
if code
.startswith(s
))
173 ignore
= next(s
for s
in self
.ignored
if code
.startswith(s
))
174 if len(select
) > len(ignore
):
175 return Decision
.Selected
177 return Decision
.Ignored
179 raise AssertionError(f
"unreachable {code} {selected} {ignored}")
181 def decision_for(self
, code
: str) -> Decision
:
182 """Return the decision for a specific code.
184 This method caches the decisions for codes to avoid retracing the same
185 logic over and over again. We only care about the select and ignore
186 rules as specified by the user in their configuration files and
189 This method does not look at whether the specific line is being
190 ignored in the file itself.
192 :param code: The code for the check that has been run.
194 decision
= self
.cache
.get(code
)
196 decision
= self
.make_decision(code
)
197 self
.cache
[code
] = decision
198 LOG
.debug('"%s" will be "%s"', code
, decision
)
202 class StyleGuideManager
:
203 """Manage multiple style guides for a single run."""
207 options
: argparse
.Namespace
,
208 formatter
: base_formatter
.BaseFormatter
,
209 decider
: DecisionEngine |
None = None,
211 """Initialize our StyleGuide.
213 .. todo:: Add parameter documentation.
215 self
.options
= options
216 self
.formatter
= formatter
217 self
.stats
= statistics
.Statistics()
218 self
.decider
= decider
or DecisionEngine(options
)
219 self
.style_guides
: list[StyleGuide
] = []
220 self
.default_style_guide
= StyleGuide(
221 options
, formatter
, self
.stats
, decider
=decider
223 self
.style_guides
= [
224 self
.default_style_guide
,
225 *self
.populate_style_guides_with(options
),
228 self
.style_guide_for
= functools
.lru_cache(maxsize
=None)(
229 self
._style
_guide
_for
232 def populate_style_guides_with(
233 self
, options
: argparse
.Namespace
234 ) -> Generator
[StyleGuide
, None, None]:
235 """Generate style guides from the per-file-ignores option.
238 The original options parsed from the CLI and config file.
240 A copy of the default style guide with overridden values.
242 per_file
= utils
.parse_files_to_codes_mapping(options
.per_file_ignores
)
243 for filename
, violations
in per_file
:
244 yield self
.default_style_guide
.copy(
245 filename
=filename
, extend_ignore_with
=violations
248 def _style_guide_for(self
, filename
: str) -> StyleGuide
:
249 """Find the StyleGuide for the filename in particular."""
251 (g
for g
in self
.style_guides
if g
.applies_to(filename
)),
252 key
=lambda g
: len(g
.filename
or ""),
255 @contextlib.contextmanager
258 ) -> Generator
[StyleGuide
, None, None]:
259 """Record the fact that we're processing the file's results."""
260 guide
= self
.style_guide_for(filename
)
261 with guide
.processing_file(filename
):
271 physical_line
: str |
None = None,
273 """Handle an error reported by a check.
276 The error code found, e.g., E123.
278 The file in which the error was found.
280 The line number (where counting starts at 1) at which the error
282 :param column_number:
283 The column number (where counting starts at 1) at which the error
286 The text of the error message.
287 :param physical_line:
288 The actual physical line causing the error.
290 1 if the error was reported. 0 if it was ignored. This is to allow
291 for counting of the number of errors found that were not ignored.
293 guide
= self
.style_guide_for(filename
)
294 return guide
.handle_error(
295 code
, filename
, line_number
, column_number
, text
, physical_line
300 """Manage a Flake8 user's style guide."""
304 options
: argparse
.Namespace
,
305 formatter
: base_formatter
.BaseFormatter
,
306 stats
: statistics
.Statistics
,
307 filename
: str |
None = None,
308 decider
: DecisionEngine |
None = None,
310 """Initialize our StyleGuide.
312 .. todo:: Add parameter documentation.
314 self
.options
= options
315 self
.formatter
= formatter
317 self
.decider
= decider
or DecisionEngine(options
)
318 self
.filename
= filename
320 self
.filename
= utils
.normalize_path(self
.filename
)
322 def __repr__(self
) -> str:
323 """Make it easier to debug which StyleGuide we're using."""
324 return f
"<StyleGuide [{self.filename}]>"
328 filename
: str |
None = None,
329 extend_ignore_with
: Sequence
[str] |
None = None,
331 """Create a copy of this style guide with different values."""
332 filename
= filename
or self
.filename
333 options
= copy
.deepcopy(self
.options
)
334 options
.extend_ignore
= options
.extend_ignore
or []
335 options
.extend_ignore
.extend(extend_ignore_with
or [])
337 options
, self
.formatter
, self
.stats
, filename
=filename
340 @contextlib.contextmanager
343 ) -> Generator
[StyleGuide
, None, None]:
344 """Record the fact that we're processing the file's results."""
345 self
.formatter
.beginning(filename
)
347 self
.formatter
.finished(filename
)
349 def applies_to(self
, filename
: str) -> bool:
350 """Check if this StyleGuide applies to the file.
353 The name of the file with violations that we're potentially
354 applying this StyleGuide to.
356 True if this applies, False otherwise
358 if self
.filename
is None:
360 return utils
.matches_filename(
362 patterns
=[self
.filename
],
363 log_message
=f
'{self!r} does %(whether)smatch "%(path)s"',
367 def should_report_error(self
, code
: str) -> Decision
:
368 """Determine if the error code should be reported or ignored.
370 This method only cares about the select and ignore rules as specified
371 by the user in their configuration files and command-line flags.
373 This method does not look at whether the specific line is being
374 ignored in the file itself.
377 The code for the check that has been run.
379 return self
.decider
.decision_for(code
)
388 physical_line
: str |
None = None,
390 """Handle an error reported by a check.
393 The error code found, e.g., E123.
395 The file in which the error was found.
397 The line number (where counting starts at 1) at which the error
399 :param column_number:
400 The column number (where counting starts at 1) at which the error
403 The text of the error message.
404 :param physical_line:
405 The actual physical line causing the error.
407 1 if the error was reported. 0 if it was ignored. This is to allow
408 for counting of the number of errors found that were not ignored.
410 disable_noqa
= self
.options
.disable_noqa
411 # NOTE(sigmavirus24): Apparently we're provided with 0-indexed column
412 # numbers so we have to offset that here.
413 if not column_number
:
423 error_is_selected
= (
424 self
.should_report_error(error
.code
) is Decision
.Selected
426 is_not_inline_ignored
= error
.is_inline_ignored(disable_noqa
) is False
427 if error_is_selected
and is_not_inline_ignored
:
428 self
.formatter
.handle(error
)
429 self
.stats
.record(error
)