| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- """Statistic collection logic for Flake8."""
- from __future__ import annotations
- from typing import Generator
- from typing import NamedTuple
- from flake8.violation import Violation
- class Statistics:
- """Manager of aggregated statistics for a run of Flake8."""
- def __init__(self) -> None:
- """Initialize the underlying dictionary for our statistics."""
- self._store: dict[Key, Statistic] = {}
- def error_codes(self) -> list[str]:
- """Return all unique error codes stored.
- :returns:
- Sorted list of error codes.
- """
- return sorted({key.code for key in self._store})
- def record(self, error: Violation) -> None:
- """Add the fact that the error was seen in the file.
- :param error:
- The Violation instance containing the information about the
- violation.
- """
- key = Key.create_from(error)
- if key not in self._store:
- self._store[key] = Statistic.create_from(error)
- self._store[key].increment()
- def statistics_for(
- self, prefix: str, filename: str | None = None
- ) -> Generator[Statistic, None, None]:
- """Generate statistics for the prefix and filename.
- If you have a :class:`Statistics` object that has recorded errors,
- you can generate the statistics for a prefix (e.g., ``E``, ``E1``,
- ``W50``, ``W503``) with the optional filter of a filename as well.
- .. code-block:: python
- >>> stats = Statistics()
- >>> stats.statistics_for('E12',
- filename='src/flake8/statistics.py')
- <generator ...>
- >>> stats.statistics_for('W')
- <generator ...>
- :param prefix:
- The error class or specific error code to find statistics for.
- :param filename:
- (Optional) The filename to further filter results by.
- :returns:
- Generator of instances of :class:`Statistic`
- """
- matching_errors = sorted(
- key for key in self._store if key.matches(prefix, filename)
- )
- for error_code in matching_errors:
- yield self._store[error_code]
- class Key(NamedTuple):
- """Simple key structure for the Statistics dictionary.
- To make things clearer, easier to read, and more understandable, we use a
- namedtuple here for all Keys in the underlying dictionary for the
- Statistics object.
- """
- filename: str
- code: str
- @classmethod
- def create_from(cls, error: Violation) -> Key:
- """Create a Key from :class:`flake8.violation.Violation`."""
- return cls(filename=error.filename, code=error.code)
- def matches(self, prefix: str, filename: str | None) -> bool:
- """Determine if this key matches some constraints.
- :param prefix:
- The error code prefix that this key's error code should start with.
- :param filename:
- The filename that we potentially want to match on. This can be
- None to only match on error prefix.
- :returns:
- True if the Key's code starts with the prefix and either filename
- is None, or the Key's filename matches the value passed in.
- """
- return self.code.startswith(prefix) and (
- filename is None or self.filename == filename
- )
- class Statistic:
- """Simple wrapper around the logic of each statistic.
- Instead of maintaining a simple but potentially hard to reason about
- tuple, we create a class which has attributes and a couple
- convenience methods on it.
- """
- def __init__(
- self, error_code: str, filename: str, message: str, count: int
- ) -> None:
- """Initialize our Statistic."""
- self.error_code = error_code
- self.filename = filename
- self.message = message
- self.count = count
- @classmethod
- def create_from(cls, error: Violation) -> Statistic:
- """Create a Statistic from a :class:`flake8.violation.Violation`."""
- return cls(
- error_code=error.code,
- filename=error.filename,
- message=error.text,
- count=0,
- )
- def increment(self) -> None:
- """Increment the number of times we've seen this error in this file."""
- self.count += 1
|