option.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
  4. from __future__ import annotations
  5. import copy
  6. import optparse # pylint: disable=deprecated-module
  7. import pathlib
  8. import re
  9. import warnings
  10. from collections.abc import Callable, Sequence
  11. from re import Pattern
  12. from typing import Any
  13. from pylint import utils
  14. # pylint: disable=unused-argument
  15. def _csv_validator(
  16. _: Any, name: str, value: str | list[str] | tuple[str]
  17. ) -> Sequence[str]:
  18. return utils._check_csv(value)
  19. # pylint: disable=unused-argument
  20. def _regexp_validator(
  21. _: Any, name: str, value: str | re.Pattern[str]
  22. ) -> re.Pattern[str]:
  23. if hasattr(value, "pattern"):
  24. return value # type: ignore[return-value]
  25. return re.compile(value)
  26. # pylint: disable=unused-argument
  27. def _regexp_csv_validator(
  28. _: Any, name: str, value: str | list[str]
  29. ) -> list[re.Pattern[str]]:
  30. return [_regexp_validator(_, name, val) for val in _csv_validator(_, name, value)]
  31. def _regexp_paths_csv_validator(
  32. _: Any, name: str, value: str | list[Pattern[str]]
  33. ) -> list[Pattern[str]]:
  34. if isinstance(value, list):
  35. return value
  36. patterns = []
  37. for val in _csv_validator(_, name, value):
  38. patterns.append(
  39. re.compile(
  40. str(pathlib.PureWindowsPath(val)).replace("\\", "\\\\")
  41. + "|"
  42. + pathlib.PureWindowsPath(val).as_posix()
  43. )
  44. )
  45. return patterns
  46. def _choice_validator(choices: list[Any], name: str, value: Any) -> Any:
  47. if value not in choices:
  48. msg = "option %s: invalid value: %r, should be in %s"
  49. raise optparse.OptionValueError(msg % (name, value, choices))
  50. return value
  51. def _yn_validator(opt: str, _: str, value: Any) -> bool:
  52. if isinstance(value, int):
  53. return bool(value)
  54. if isinstance(value, str):
  55. value = value.lower()
  56. if value in {"y", "yes", "true"}:
  57. return True
  58. if value in {"n", "no", "false"}:
  59. return False
  60. msg = "option %s: invalid yn value %r, should be in (y, yes, true, n, no, false)"
  61. raise optparse.OptionValueError(msg % (opt, value))
  62. def _multiple_choice_validator(choices: list[Any], name: str, value: Any) -> Any:
  63. values = utils._check_csv(value)
  64. for csv_value in values:
  65. if csv_value not in choices:
  66. msg = "option %s: invalid value: %r, should be in %s"
  67. raise optparse.OptionValueError(msg % (name, csv_value, choices))
  68. return values
  69. def _non_empty_string_validator( # pragma: no cover # Unused
  70. opt: Any, _: str, value: str
  71. ) -> str:
  72. if not value:
  73. msg = "indent string can't be empty."
  74. raise optparse.OptionValueError(msg)
  75. return utils._unquote(value)
  76. def _multiple_choices_validating_option( # pragma: no cover # Unused
  77. opt: optparse.Option, name: str, value: Any
  78. ) -> Any:
  79. return _multiple_choice_validator(
  80. opt.choices, name, value # type: ignore[attr-defined]
  81. )
  82. def _py_version_validator(_: Any, name: str, value: Any) -> tuple[int, int, int]:
  83. if not isinstance(value, tuple):
  84. try:
  85. value = tuple(int(val) for val in value.split("."))
  86. except (ValueError, AttributeError):
  87. raise optparse.OptionValueError(
  88. f"Invalid format for {name}, should be version string. E.g., '3.8'"
  89. ) from None
  90. return value # type: ignore[no-any-return]
  91. VALIDATORS: dict[str, Callable[[Any, str, Any], Any] | Callable[[Any], Any]] = {
  92. "string": utils._unquote,
  93. "int": int,
  94. "float": float,
  95. "glob_paths_csv": _csv_validator,
  96. "regexp": lambda pattern: re.compile(pattern or ""),
  97. "regexp_csv": _regexp_csv_validator,
  98. "regexp_paths_csv": _regexp_paths_csv_validator,
  99. "csv": _csv_validator,
  100. "yn": _yn_validator,
  101. "choice": lambda opt, name, value: _choice_validator(opt["choices"], name, value),
  102. "confidence": lambda opt, name, value: _multiple_choice_validator(
  103. opt["choices"], name, value
  104. ),
  105. "multiple_choice": lambda opt, name, value: _multiple_choice_validator(
  106. opt["choices"], name, value
  107. ),
  108. "non_empty_string": _non_empty_string_validator,
  109. "py_version": _py_version_validator,
  110. }
  111. def _call_validator(opttype: str, optdict: Any, option: str, value: Any) -> Any:
  112. if opttype not in VALIDATORS:
  113. raise TypeError(f'Unsupported type "{opttype}"')
  114. try:
  115. return VALIDATORS[opttype](optdict, option, value) # type: ignore[call-arg]
  116. except TypeError:
  117. try:
  118. return VALIDATORS[opttype](value) # type: ignore[call-arg]
  119. except Exception as e:
  120. raise optparse.OptionValueError(
  121. f"{option} value ({value!r}) should be of type {opttype}"
  122. ) from e
  123. def _validate(value: Any, optdict: Any, name: str = "") -> Any:
  124. """Return a validated value for an option according to its type.
  125. optional argument name is only used for error message formatting
  126. """
  127. try:
  128. _type = optdict["type"]
  129. except KeyError:
  130. return value
  131. return _call_validator(_type, optdict, name, value)
  132. # pylint: disable=no-member
  133. class Option(optparse.Option):
  134. TYPES = optparse.Option.TYPES + (
  135. "glob_paths_csv",
  136. "regexp",
  137. "regexp_csv",
  138. "regexp_paths_csv",
  139. "csv",
  140. "yn",
  141. "confidence",
  142. "multiple_choice",
  143. "non_empty_string",
  144. "py_version",
  145. )
  146. ATTRS = optparse.Option.ATTRS + ["hide", "level"]
  147. TYPE_CHECKER = copy.copy(optparse.Option.TYPE_CHECKER)
  148. TYPE_CHECKER["glob_paths_csv"] = _csv_validator
  149. TYPE_CHECKER["regexp"] = _regexp_validator
  150. TYPE_CHECKER["regexp_csv"] = _regexp_csv_validator
  151. TYPE_CHECKER["regexp_paths_csv"] = _regexp_paths_csv_validator
  152. TYPE_CHECKER["csv"] = _csv_validator
  153. TYPE_CHECKER["yn"] = _yn_validator
  154. TYPE_CHECKER["confidence"] = _multiple_choices_validating_option
  155. TYPE_CHECKER["multiple_choice"] = _multiple_choices_validating_option
  156. TYPE_CHECKER["non_empty_string"] = _non_empty_string_validator
  157. TYPE_CHECKER["py_version"] = _py_version_validator
  158. def __init__(self, *opts: Any, **attrs: Any) -> None:
  159. # TODO: 3.0: Remove deprecated class
  160. warnings.warn(
  161. "Option has been deprecated and will be removed in pylint 3.0",
  162. DeprecationWarning,
  163. stacklevel=2,
  164. )
  165. super().__init__(*opts, **attrs)
  166. if hasattr(self, "hide") and self.hide:
  167. self.help = optparse.SUPPRESS_HELP
  168. def _check_choice(self) -> None:
  169. if self.type in {"choice", "multiple_choice", "confidence"}:
  170. if self.choices is None: # type: ignore[attr-defined]
  171. raise optparse.OptionError(
  172. "must supply a list of choices for type 'choice'", self
  173. )
  174. if not isinstance(self.choices, (tuple, list)): # type: ignore[attr-defined]
  175. raise optparse.OptionError(
  176. # pylint: disable-next=consider-using-f-string
  177. "choices must be a list of strings ('%s' supplied)"
  178. % str(type(self.choices)).split("'")[1], # type: ignore[attr-defined]
  179. self,
  180. )
  181. elif self.choices is not None: # type: ignore[attr-defined]
  182. raise optparse.OptionError(
  183. f"must not supply choices for type {self.type!r}", self
  184. )
  185. optparse.Option.CHECK_METHODS[2] = _check_choice # type: ignore[index]
  186. def process( # pragma: no cover # Argparse
  187. self, opt: Any, value: Any, values: Any, parser: Any
  188. ) -> int:
  189. assert isinstance(self.dest, str)
  190. if self.callback and self.callback.__module__ == "pylint.lint.run":
  191. return 1
  192. # First, convert the value(s) to the right type. Howl if any
  193. # value(s) are bogus.
  194. value = self.convert_value(opt, value)
  195. if self.type == "named":
  196. existent = getattr(values, self.dest)
  197. if existent:
  198. existent.update(value)
  199. value = existent
  200. # And then take whatever action is expected of us.
  201. # This is a separate method to make life easier for
  202. # subclasses to add new actions.
  203. return self.take_action(self.action, self.dest, opt, value, values, parser)