mirror of
https://github.com/PyCQA/flake8.git
synced 2026-04-04 20:26:53 +00:00
Merge branch 'per-file-style-guide' into 'master'
Add support for per-file ignores in config Closes #156 See merge request pycqa/flake8!259
This commit is contained in:
commit
4439ea2025
9 changed files with 351 additions and 20 deletions
|
|
@ -15,7 +15,7 @@ import sys
|
|||
LOG = logging.getLogger(__name__)
|
||||
LOG.addHandler(logging.NullHandler())
|
||||
|
||||
__version__ = "3.6.0"
|
||||
__version__ = "3.7.0dev0"
|
||||
__version_info__ = tuple(
|
||||
int(i) for i in __version__.split(".") if i.isdigit()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import collections
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import tokenize
|
||||
|
|
@ -188,20 +187,12 @@ class Manager(object):
|
|||
return False
|
||||
path = self.options.stdin_display_name
|
||||
|
||||
exclude = self.options.exclude
|
||||
if not exclude:
|
||||
return False
|
||||
basename = os.path.basename(path)
|
||||
if utils.fnmatch(basename, exclude):
|
||||
LOG.debug('"%s" has been excluded', basename)
|
||||
return True
|
||||
|
||||
absolute_path = os.path.abspath(path)
|
||||
match = utils.fnmatch(absolute_path, exclude)
|
||||
LOG.debug(
|
||||
'"%s" has %sbeen excluded', absolute_path, "" if match else "not "
|
||||
return utils.matches_filename(
|
||||
path,
|
||||
patterns=self.options.exclude,
|
||||
log_message='"%(path)s" has %(whether)sbeen excluded',
|
||||
logger=LOG,
|
||||
)
|
||||
return match
|
||||
|
||||
def make_checkers(self, paths=None):
|
||||
# type: (List[str]) -> NoneType
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ class Application(object):
|
|||
self.formatter = None
|
||||
#: The :class:`flake8.plugins.notifier.Notifier` for listening plugins
|
||||
self.listener_trie = None
|
||||
#: The :class:`flake8.style_guide.StyleGuide` built from the user's
|
||||
#: options
|
||||
#: The :class:`flake8.style_guide.StyleGuideManager` built from the
|
||||
#: user's options
|
||||
self.guide = None
|
||||
#: The :class:`flake8.checker.Manager` that will handle running all of
|
||||
#: the checks selected by the user.
|
||||
|
|
@ -283,7 +283,7 @@ class Application(object):
|
|||
# type: () -> NoneType
|
||||
"""Initialize our StyleGuide."""
|
||||
if self.guide is None:
|
||||
self.guide = style_guide.StyleGuide(
|
||||
self.guide = style_guide.StyleGuideManager(
|
||||
self.options, self.listener_trie, self.formatter
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Contains the logic for all of the default options for Flake8."""
|
||||
from flake8 import defaults
|
||||
from flake8 import utils
|
||||
from flake8.main import debug
|
||||
from flake8.main import vcs
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ def register_default_options(option_manager):
|
|||
- ``--hang-closing``
|
||||
- ``--ignore``
|
||||
- ``--extend-ignore``
|
||||
- ``--per-file-ignores``
|
||||
- ``--max-line-length``
|
||||
- ``--select``
|
||||
- ``--disable-noqa``
|
||||
|
|
@ -142,6 +144,18 @@ def register_default_options(option_manager):
|
|||
" of ignored ones. For example, ``--extend-ignore=E4,E51,W234``.",
|
||||
)
|
||||
|
||||
add_option(
|
||||
"--per-file-ignores",
|
||||
parse_from_config=True,
|
||||
comma_separated_list=True,
|
||||
separator=utils.NEWLINE_SEPARATED_LIST_RE,
|
||||
help="A pairing of filenames and violation codes that defines which "
|
||||
"violations to ignore in a particular file. The filenames can be "
|
||||
"specified in a manner similar to the ``--exclude`` option and the "
|
||||
"violations work similarly to the ``--ignore`` and ``--select`` "
|
||||
"options.",
|
||||
)
|
||||
|
||||
add_option(
|
||||
"--max-line-length",
|
||||
type="int",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class Option(object):
|
|||
parse_from_config=False,
|
||||
comma_separated_list=False,
|
||||
normalize_paths=False,
|
||||
separator=None,
|
||||
):
|
||||
"""Initialize an Option instance wrapping optparse.Option.
|
||||
|
||||
|
|
@ -79,6 +80,8 @@ class Option(object):
|
|||
:param bool normalize_paths:
|
||||
Whether the option is expecting a path or list of paths and should
|
||||
attempt to normalize the paths to absolute paths.
|
||||
:param separator:
|
||||
The item that separates the "comma"-separated list.
|
||||
"""
|
||||
self.short_option_name = short_option_name
|
||||
self.long_option_name = long_option_name
|
||||
|
|
@ -107,6 +110,7 @@ class Option(object):
|
|||
self.parse_from_config = parse_from_config
|
||||
self.comma_separated_list = comma_separated_list
|
||||
self.normalize_paths = normalize_paths
|
||||
self.separator = separator or utils.COMMA_SEPARATED_LIST_RE
|
||||
|
||||
self.config_name = None
|
||||
if parse_from_config:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Implementation of the StyleGuide used by Flake8."""
|
||||
import collections
|
||||
import contextlib
|
||||
import copy
|
||||
import enum
|
||||
import functools
|
||||
import itertools
|
||||
|
|
@ -321,8 +322,8 @@ class DecisionEngine(object):
|
|||
return decision
|
||||
|
||||
|
||||
class StyleGuide(object):
|
||||
"""Manage a Flake8 user's style guide."""
|
||||
class StyleGuideManager(object):
|
||||
"""Manage multiple style guides for a single run."""
|
||||
|
||||
def __init__(self, options, listener_trie, formatter, decider=None):
|
||||
"""Initialize our StyleGuide.
|
||||
|
|
@ -334,8 +335,136 @@ class StyleGuide(object):
|
|||
self.formatter = formatter
|
||||
self.stats = statistics.Statistics()
|
||||
self.decider = decider or DecisionEngine(options)
|
||||
self.style_guides = []
|
||||
self.default_style_guide = StyleGuide(
|
||||
options, listener_trie, formatter, decider=decider
|
||||
)
|
||||
self.style_guides = list(
|
||||
itertools.chain(
|
||||
[self.default_style_guide],
|
||||
self.populate_style_guides_with(options),
|
||||
)
|
||||
)
|
||||
|
||||
def populate_style_guides_with(self, options):
|
||||
"""Generate style guides from the per-file-ignores option.
|
||||
|
||||
:param options:
|
||||
The original options parsed from the CLI and config file.
|
||||
:type options:
|
||||
:class:`~optparse.Values`
|
||||
:returns:
|
||||
A copy of the default style guide with overridden values.
|
||||
:rtype:
|
||||
:class:`~flake8.style_guide.StyleGuide`
|
||||
"""
|
||||
for value in options.per_file_ignores:
|
||||
filename, violations_str = value.split(":")
|
||||
violations = utils.parse_comma_separated_list(violations_str)
|
||||
yield self.default_style_guide.copy(
|
||||
filename=filename, extend_ignore_with=violations
|
||||
)
|
||||
|
||||
def style_guide_for(self, filename):
|
||||
"""Find the StyleGuide for the filename in particular."""
|
||||
guides = sorted(
|
||||
(g for g in self.style_guides if g.applies_to(filename)),
|
||||
key=lambda g: len(g.filename or ""),
|
||||
)
|
||||
if len(guides) > 1:
|
||||
return guides[-1]
|
||||
return guides[0]
|
||||
|
||||
@contextlib.contextmanager
|
||||
def processing_file(self, filename):
|
||||
"""Record the fact that we're processing the file's results."""
|
||||
guide = self.style_guide_for(filename)
|
||||
with guide.processing_file(filename):
|
||||
yield guide
|
||||
|
||||
def handle_error(
|
||||
self,
|
||||
code,
|
||||
filename,
|
||||
line_number,
|
||||
column_number,
|
||||
text,
|
||||
physical_line=None,
|
||||
):
|
||||
# type: (str, str, int, int, str) -> int
|
||||
"""Handle an error reported by a check.
|
||||
|
||||
:param str code:
|
||||
The error code found, e.g., E123.
|
||||
:param str filename:
|
||||
The file in which the error was found.
|
||||
:param int line_number:
|
||||
The line number (where counting starts at 1) at which the error
|
||||
occurs.
|
||||
:param int column_number:
|
||||
The column number (where counting starts at 1) at which the error
|
||||
occurs.
|
||||
:param str text:
|
||||
The text of the error message.
|
||||
:param str physical_line:
|
||||
The actual physical line causing the error.
|
||||
:returns:
|
||||
1 if the error was reported. 0 if it was ignored. This is to allow
|
||||
for counting of the number of errors found that were not ignored.
|
||||
:rtype:
|
||||
int
|
||||
"""
|
||||
guide = self.style_guide_for(filename)
|
||||
return guide.handle_error(
|
||||
code, filename, line_number, column_number, text, physical_line
|
||||
)
|
||||
|
||||
def add_diff_ranges(self, diffinfo):
|
||||
"""Update the StyleGuides to filter out information not in the diff.
|
||||
|
||||
This provides information to the underlying StyleGuides so that only
|
||||
the errors in the line number ranges are reported.
|
||||
|
||||
:param dict diffinfo:
|
||||
Dictionary mapping filenames to sets of line number ranges.
|
||||
"""
|
||||
for guide in self.style_guides.values():
|
||||
guide.add_diff_ranges(diffinfo)
|
||||
|
||||
|
||||
class StyleGuide(object):
|
||||
"""Manage a Flake8 user's style guide."""
|
||||
|
||||
def __init__(
|
||||
self, options, listener_trie, formatter, filename=None, decider=None
|
||||
):
|
||||
"""Initialize our StyleGuide.
|
||||
|
||||
.. todo:: Add parameter documentation.
|
||||
"""
|
||||
self.options = options
|
||||
self.listener = listener_trie
|
||||
self.formatter = formatter
|
||||
self.stats = statistics.Statistics()
|
||||
self.decider = decider or DecisionEngine(options)
|
||||
self.filename = filename
|
||||
if self.filename:
|
||||
self.filename = utils.normalize_path(self.filename)
|
||||
self._parsed_diff = {}
|
||||
|
||||
def __repr__(self):
|
||||
"""Make it easier to debug which StyleGuide we're using."""
|
||||
return "<StyleGuide [{}]>".format(self.filename)
|
||||
|
||||
def copy(self, filename=None, extend_ignore_with=None, **kwargs):
|
||||
"""Create a copy of this style guide with different values."""
|
||||
filename = filename or self.filename
|
||||
options = copy.deepcopy(self.options)
|
||||
options.ignore.extend(extend_ignore_with or [])
|
||||
return StyleGuide(
|
||||
options, self.listener, self.formatter, filename=filename
|
||||
)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def processing_file(self, filename):
|
||||
"""Record the fact that we're processing the file's results."""
|
||||
|
|
@ -343,6 +472,26 @@ class StyleGuide(object):
|
|||
yield self
|
||||
self.formatter.finished(filename)
|
||||
|
||||
def applies_to(self, filename):
|
||||
"""Check if this StyleGuide applies to the file.
|
||||
|
||||
:param str filename:
|
||||
The name of the file with violations that we're potentially
|
||||
applying this StyleGuide to.
|
||||
:returns:
|
||||
True if this applies, False otherwise
|
||||
:rtype:
|
||||
bool
|
||||
"""
|
||||
if self.filename is None:
|
||||
return True
|
||||
return utils.matches_filename(
|
||||
filename,
|
||||
patterns=[self.filename],
|
||||
log_message='{!r} does %(whether)smatch "%(path)s"'.format(self),
|
||||
logger=LOG,
|
||||
)
|
||||
|
||||
def should_report_error(self, code):
|
||||
# type: (str) -> Decision
|
||||
"""Determine if the error code should be reported or ignored.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import tokenize
|
|||
|
||||
DIFF_HUNK_REGEXP = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$")
|
||||
COMMA_SEPARATED_LIST_RE = re.compile(r"[,\s]")
|
||||
NEWLINE_SEPARATED_LIST_RE = re.compile(r"[\s]")
|
||||
LOCAL_PLUGIN_LIST_RE = re.compile(r"[,\t\n\r\f\v]")
|
||||
|
||||
|
||||
|
|
@ -335,6 +336,38 @@ def parameters_for(plugin):
|
|||
return parameters
|
||||
|
||||
|
||||
def matches_filename(path, patterns, log_message, logger):
|
||||
"""Use fnmatch to discern if a path exists in patterns.
|
||||
|
||||
:param str path:
|
||||
The path to the file under question
|
||||
:param patterns:
|
||||
The patterns to match the path against.
|
||||
:type patterns:
|
||||
list[str]
|
||||
:param str log_message:
|
||||
The message used for logging purposes.
|
||||
:returns:
|
||||
True if path matches patterns, False otherwise
|
||||
:rtype:
|
||||
bool
|
||||
"""
|
||||
if not patterns:
|
||||
return False
|
||||
basename = os.path.basename(path)
|
||||
if fnmatch(basename, patterns):
|
||||
logger.debug(log_message, {"path": basename, "whether": ""})
|
||||
return True
|
||||
|
||||
absolute_path = os.path.abspath(path)
|
||||
match = fnmatch(absolute_path, patterns)
|
||||
logger.debug(
|
||||
log_message,
|
||||
{"path": absolute_path, "whether": "" if match else "not "},
|
||||
)
|
||||
return match
|
||||
|
||||
|
||||
def get_python_version():
|
||||
"""Find and format the python implementation and version.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue