pyflakes.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. """Plugin built-in to Flake8 to treat pyflakes as a plugin."""
  2. import argparse
  3. import ast
  4. import os
  5. import tokenize
  6. from typing import Any
  7. from typing import Generator
  8. from typing import List
  9. from typing import Tuple
  10. from typing import Type
  11. import pyflakes.checker
  12. from flake8 import utils
  13. from flake8.options.manager import OptionManager
  14. FLAKE8_PYFLAKES_CODES = {
  15. "UnusedImport": "F401",
  16. "ImportShadowedByLoopVar": "F402",
  17. "ImportStarUsed": "F403",
  18. "LateFutureImport": "F404",
  19. "ImportStarUsage": "F405",
  20. "ImportStarNotPermitted": "F406",
  21. "FutureFeatureNotDefined": "F407",
  22. "PercentFormatInvalidFormat": "F501",
  23. "PercentFormatExpectedMapping": "F502",
  24. "PercentFormatExpectedSequence": "F503",
  25. "PercentFormatExtraNamedArguments": "F504",
  26. "PercentFormatMissingArgument": "F505",
  27. "PercentFormatMixedPositionalAndNamed": "F506",
  28. "PercentFormatPositionalCountMismatch": "F507",
  29. "PercentFormatStarRequiresSequence": "F508",
  30. "PercentFormatUnsupportedFormatCharacter": "F509",
  31. "StringDotFormatInvalidFormat": "F521",
  32. "StringDotFormatExtraNamedArguments": "F522",
  33. "StringDotFormatExtraPositionalArguments": "F523",
  34. "StringDotFormatMissingArgument": "F524",
  35. "StringDotFormatMixingAutomatic": "F525",
  36. "FStringMissingPlaceholders": "F541",
  37. "MultiValueRepeatedKeyLiteral": "F601",
  38. "MultiValueRepeatedKeyVariable": "F602",
  39. "TooManyExpressionsInStarredAssignment": "F621",
  40. "TwoStarredExpressions": "F622",
  41. "AssertTuple": "F631",
  42. "IsLiteral": "F632",
  43. "InvalidPrintSyntax": "F633",
  44. "IfTuple": "F634",
  45. "BreakOutsideLoop": "F701",
  46. "ContinueOutsideLoop": "F702",
  47. "ContinueInFinally": "F703",
  48. "YieldOutsideFunction": "F704",
  49. "ReturnOutsideFunction": "F706",
  50. "DefaultExceptNotLast": "F707",
  51. "DoctestSyntaxError": "F721",
  52. "ForwardAnnotationSyntaxError": "F722",
  53. "CommentAnnotationSyntaxError": "F723",
  54. "RedefinedWhileUnused": "F811",
  55. "UndefinedName": "F821",
  56. "UndefinedExport": "F822",
  57. "UndefinedLocal": "F823",
  58. "DuplicateArgument": "F831",
  59. "UnusedVariable": "F841",
  60. "RaiseNotImplemented": "F901",
  61. }
  62. class FlakesChecker(pyflakes.checker.Checker):
  63. """Subclass the Pyflakes checker to conform with the flake8 API."""
  64. with_doctest = False
  65. include_in_doctest: List[str] = []
  66. exclude_from_doctest: List[str] = []
  67. def __init__(
  68. self,
  69. tree: ast.AST,
  70. file_tokens: List[tokenize.TokenInfo],
  71. filename: str,
  72. ) -> None:
  73. """Initialize the PyFlakes plugin with an AST tree and filename."""
  74. filename = utils.normalize_path(filename)
  75. with_doctest = self.with_doctest
  76. included_by = [
  77. include
  78. for include in self.include_in_doctest
  79. if include != "" and filename.startswith(include)
  80. ]
  81. if included_by:
  82. with_doctest = True
  83. for exclude in self.exclude_from_doctest:
  84. if exclude != "" and filename.startswith(exclude):
  85. with_doctest = False
  86. overlaped_by = [
  87. include
  88. for include in included_by
  89. if include.startswith(exclude)
  90. ]
  91. if overlaped_by:
  92. with_doctest = True
  93. super().__init__(
  94. tree,
  95. filename=filename,
  96. withDoctest=with_doctest,
  97. file_tokens=file_tokens,
  98. )
  99. @classmethod
  100. def add_options(cls, parser: OptionManager) -> None:
  101. """Register options for PyFlakes on the Flake8 OptionManager."""
  102. parser.add_option(
  103. "--builtins",
  104. parse_from_config=True,
  105. comma_separated_list=True,
  106. help="define more built-ins, comma separated",
  107. )
  108. parser.add_option(
  109. "--doctests",
  110. default=False,
  111. action="store_true",
  112. parse_from_config=True,
  113. help="also check syntax of the doctests",
  114. )
  115. parser.add_option(
  116. "--include-in-doctest",
  117. default="",
  118. dest="include_in_doctest",
  119. parse_from_config=True,
  120. comma_separated_list=True,
  121. normalize_paths=True,
  122. help="Run doctests only on these files",
  123. )
  124. parser.add_option(
  125. "--exclude-from-doctest",
  126. default="",
  127. dest="exclude_from_doctest",
  128. parse_from_config=True,
  129. comma_separated_list=True,
  130. normalize_paths=True,
  131. help="Skip these files when running doctests",
  132. )
  133. @classmethod
  134. def parse_options(cls, options: argparse.Namespace) -> None:
  135. """Parse option values from Flake8's OptionManager."""
  136. if options.builtins:
  137. cls.builtIns = cls.builtIns.union(options.builtins)
  138. cls.with_doctest = options.doctests
  139. included_files = []
  140. for included_file in options.include_in_doctest:
  141. if included_file == "":
  142. continue
  143. if not included_file.startswith((os.sep, "./", "~/")):
  144. included_files.append(f"./{included_file}")
  145. else:
  146. included_files.append(included_file)
  147. cls.include_in_doctest = utils.normalize_paths(included_files)
  148. excluded_files = []
  149. for excluded_file in options.exclude_from_doctest:
  150. if excluded_file == "":
  151. continue
  152. if not excluded_file.startswith((os.sep, "./", "~/")):
  153. excluded_files.append(f"./{excluded_file}")
  154. else:
  155. excluded_files.append(excluded_file)
  156. cls.exclude_from_doctest = utils.normalize_paths(excluded_files)
  157. inc_exc = set(cls.include_in_doctest).intersection(
  158. cls.exclude_from_doctest
  159. )
  160. if inc_exc:
  161. raise ValueError(
  162. f"{inc_exc!r} was specified in both the "
  163. f"include-in-doctest and exclude-from-doctest "
  164. f"options. You are not allowed to specify it in "
  165. f"both for doctesting."
  166. )
  167. def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]:
  168. """Run the plugin."""
  169. for message in self.messages:
  170. col = getattr(message, "col", 0)
  171. yield (
  172. message.lineno,
  173. col,
  174. "{} {}".format(
  175. FLAKE8_PYFLAKES_CODES.get(type(message).__name__, "F999"),
  176. message.message % message.message_args,
  177. ),
  178. message.__class__,
  179. )