errors.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. """ Don't duplicate same errors from different linters. """
  2. from __future__ import annotations
  3. import re
  4. from collections import defaultdict
  5. from typing import Any, DefaultDict, Dict, Generator, List, Set, Tuple
  6. PATTERN_NUMBER = re.compile(r"^\s*([A-Z]\d+)\s*", re.I)
  7. DUPLICATES: Dict[Tuple[str, str], Set] = {
  8. key: values # type: ignore
  9. for values in (
  10. # multiple statements on one line
  11. {("pycodestyle", "E701"), ("pylint", "C0321")},
  12. # unused variable
  13. {("pylint", "W0612"), ("pyflakes", "W0612")},
  14. # undefined variable
  15. {("pylint", "E0602"), ("pyflakes", "E0602")},
  16. # unused import
  17. {("pylint", "W0611"), ("pyflakes", "W0611")},
  18. # whitespace before ')'
  19. {("pylint", "C0326"), ("pycodestyle", "E202")},
  20. # whitespace before '('
  21. {("pylint", "C0326"), ("pycodestyle", "E211")},
  22. # multiple spaces after operator
  23. {("pylint", "C0326"), ("pycodestyle", "E222")},
  24. # missing whitespace around operator
  25. {("pylint", "C0326"), ("pycodestyle", "E225")},
  26. # unexpected spaces
  27. {("pylint", "C0326"), ("pycodestyle", "E251")},
  28. # long lines
  29. {("pylint", "C0301"), ("pycodestyle", "E501")},
  30. # statement ends with a semicolon
  31. {("pylint", "W0301"), ("pycodestyle", "E703")},
  32. # multiple statements on one line
  33. {('pylint", "C0321'), ("pycodestyle", "E702")},
  34. # bad indentation
  35. {("pylint", "W0311"), ("pycodestyle", "E111")},
  36. # wildcart import
  37. {("pylint", "W00401"), ("pyflakes", "W0401")},
  38. # module docstring
  39. {("pydocstyle", "D100"), ("pylint", "C0111")},
  40. )
  41. for key in values # type: ignore
  42. }
  43. class Error:
  44. """Store an error's information."""
  45. __slots__ = "source", "col", "lnum", "etype", "message", "filename", "number"
  46. def __init__(
  47. self,
  48. source="pylama",
  49. col=1,
  50. lnum=1,
  51. type=None, # pylint: disable=R0913
  52. text="unknown error",
  53. filename="",
  54. number="",
  55. **_,
  56. ):
  57. """Init error information with default values."""
  58. text = str(text).strip().replace("\n", " ")
  59. if number:
  60. self.number = number
  61. else:
  62. number = PATTERN_NUMBER.match(text)
  63. self.number = number.group(1).upper() if number else ""
  64. self.etype = type[:1] if type else (number[0] if number else "E")
  65. self.col = max(col, 1)
  66. self.filename = filename
  67. self.source = source
  68. self.lnum = int(lnum)
  69. self.message = text
  70. def __repr__(self):
  71. return f"<Error:{self.lnum}:{self.col}: {self.number} {self.message}>"
  72. def format(self, pattern: str) -> str:
  73. """Format the error with the given pattern."""
  74. return pattern.format(
  75. filename=self.filename,
  76. lnum=self.lnum,
  77. col=self.col,
  78. message=self.message,
  79. etype=self.etype,
  80. source=self.source,
  81. number=self.number,
  82. )
  83. def to_dict(self) -> Dict[str, Any]:
  84. """Return the error as a dict."""
  85. return {
  86. "source": self.source,
  87. "col": self.col,
  88. "lnum": self.lnum,
  89. "etype": self.etype,
  90. "message": self.message,
  91. "filename": self.filename,
  92. "number": self.number,
  93. }
  94. def remove_duplicates(errors: List[Error]) -> Generator[Error, None, None]:
  95. """Filter duplicates from given error's list."""
  96. passed: DefaultDict[int, Set] = defaultdict(set)
  97. for error in errors:
  98. key = error.source, error.number
  99. if key in DUPLICATES:
  100. if key in passed[error.lnum]:
  101. continue
  102. passed[error.lnum] = DUPLICATES[key]
  103. yield error
  104. def default_sorter(err: Error) -> Any:
  105. """Sort by line number."""
  106. return err.lnum
  107. # pylama:ignore=W0622,D,R0924