unsupported_version.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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. """Checker for features used that are not supported by all python versions
  5. indicated by the py-version setting.
  6. """
  7. from __future__ import annotations
  8. from typing import TYPE_CHECKING
  9. from astroid import nodes
  10. from pylint.checkers import BaseChecker
  11. from pylint.checkers.utils import (
  12. only_required_for_messages,
  13. safe_infer,
  14. uninferable_final_decorators,
  15. )
  16. from pylint.interfaces import HIGH
  17. if TYPE_CHECKING:
  18. from pylint.lint import PyLinter
  19. class UnsupportedVersionChecker(BaseChecker):
  20. """Checker for features that are not supported by all python versions
  21. indicated by the py-version setting.
  22. """
  23. name = "unsupported_version"
  24. msgs = {
  25. "W2601": (
  26. "F-strings are not supported by all versions included in the py-version setting",
  27. "using-f-string-in-unsupported-version",
  28. "Used when the py-version set by the user is lower than 3.6 and pylint encounters "
  29. "an f-string.",
  30. ),
  31. "W2602": (
  32. "typing.final is not supported by all versions included in the py-version setting",
  33. "using-final-decorator-in-unsupported-version",
  34. "Used when the py-version set by the user is lower than 3.8 and pylint encounters "
  35. "a ``typing.final`` decorator.",
  36. ),
  37. "W2603": (
  38. "Exception groups are not supported by all versions included in the py-version setting",
  39. "using-exception-groups-in-unsupported-version",
  40. "Used when the py-version set by the user is lower than 3.11 and pylint encounters "
  41. "``except*`` or `ExceptionGroup``.",
  42. ),
  43. "W2604": (
  44. "Generic type syntax (PEP 695) is not supported by all versions included in the py-version setting",
  45. "using-generic-type-syntax-in-unsupported-version",
  46. "Used when the py-version set by the user is lower than 3.12 and pylint encounters "
  47. "generic type syntax.",
  48. ),
  49. "W2605": (
  50. "Assignment expression is not supported by all versions included in the py-version setting",
  51. "using-assignment-expression-in-unsupported-version",
  52. "Used when the py-version set by the user is lower than 3.8 and pylint encounters "
  53. "an assignment expression (walrus) operator.",
  54. ),
  55. "W2606": (
  56. "Positional-only arguments are not supported by all versions included in the py-version setting",
  57. "using-positional-only-args-in-unsupported-version",
  58. "Used when the py-version set by the user is lower than 3.8 and pylint encounters "
  59. "positional-only arguments.",
  60. ),
  61. }
  62. def open(self) -> None:
  63. """Initialize visit variables and statistics."""
  64. py_version = self.linter.config.py_version
  65. self._py36_plus = py_version >= (3, 6)
  66. self._py38_plus = py_version >= (3, 8)
  67. self._py311_plus = py_version >= (3, 11)
  68. self._py312_plus = py_version >= (3, 12)
  69. @only_required_for_messages("using-f-string-in-unsupported-version")
  70. def visit_joinedstr(self, node: nodes.JoinedStr) -> None:
  71. """Check f-strings."""
  72. if not self._py36_plus:
  73. self.add_message(
  74. "using-f-string-in-unsupported-version", node=node, confidence=HIGH
  75. )
  76. @only_required_for_messages("using-assignment-expression-in-unsupported-version")
  77. def visit_namedexpr(self, node: nodes.JoinedStr) -> None:
  78. if not self._py38_plus:
  79. self.add_message(
  80. "using-assignment-expression-in-unsupported-version",
  81. node=node,
  82. confidence=HIGH,
  83. )
  84. @only_required_for_messages("using-positional-only-args-in-unsupported-version")
  85. def visit_arguments(self, node: nodes.Arguments) -> None:
  86. if not self._py38_plus and node.posonlyargs:
  87. self.add_message(
  88. "using-positional-only-args-in-unsupported-version",
  89. node=node,
  90. confidence=HIGH,
  91. )
  92. @only_required_for_messages("using-final-decorator-in-unsupported-version")
  93. def visit_decorators(self, node: nodes.Decorators) -> None:
  94. """Check decorators."""
  95. self._check_typing_final(node)
  96. def _check_typing_final(self, node: nodes.Decorators) -> None:
  97. """Add a message when the `typing.final` decorator is used and the
  98. py-version is lower than 3.8.
  99. """
  100. if self._py38_plus:
  101. return
  102. decorators = []
  103. for decorator in node.get_children():
  104. inferred = safe_infer(decorator)
  105. if inferred and inferred.qname() == "typing.final":
  106. decorators.append(decorator)
  107. for decorator in decorators or uninferable_final_decorators(node):
  108. self.add_message(
  109. "using-final-decorator-in-unsupported-version",
  110. node=decorator,
  111. confidence=HIGH,
  112. )
  113. @only_required_for_messages("using-exception-groups-in-unsupported-version")
  114. def visit_trystar(self, node: nodes.TryStar) -> None:
  115. if not self._py311_plus:
  116. self.add_message(
  117. "using-exception-groups-in-unsupported-version",
  118. node=node,
  119. confidence=HIGH,
  120. )
  121. @only_required_for_messages("using-exception-groups-in-unsupported-version")
  122. def visit_excepthandler(self, node: nodes.ExceptHandler) -> None:
  123. if (
  124. not self._py311_plus
  125. and isinstance(node.type, nodes.Name)
  126. and node.type.name == "ExceptionGroup"
  127. ):
  128. self.add_message(
  129. "using-exception-groups-in-unsupported-version",
  130. node=node,
  131. confidence=HIGH,
  132. )
  133. @only_required_for_messages("using-exception-groups-in-unsupported-version")
  134. def visit_raise(self, node: nodes.Raise) -> None:
  135. if (
  136. not self._py311_plus
  137. and isinstance(node.exc, nodes.Call)
  138. and isinstance(node.exc.func, nodes.Name)
  139. and node.exc.func.name == "ExceptionGroup"
  140. ):
  141. self.add_message(
  142. "using-exception-groups-in-unsupported-version",
  143. node=node,
  144. confidence=HIGH,
  145. )
  146. @only_required_for_messages("using-generic-type-syntax-in-unsupported-version")
  147. def visit_typealias(self, node: nodes.TypeAlias) -> None:
  148. if not self._py312_plus:
  149. self.add_message(
  150. "using-generic-type-syntax-in-unsupported-version",
  151. node=node,
  152. confidence=HIGH,
  153. )
  154. @only_required_for_messages("using-generic-type-syntax-in-unsupported-version")
  155. def visit_typevar(self, node: nodes.TypeVar) -> None:
  156. if not self._py312_plus:
  157. self.add_message(
  158. "using-generic-type-syntax-in-unsupported-version",
  159. node=node,
  160. confidence=HIGH,
  161. )
  162. @only_required_for_messages("using-generic-type-syntax-in-unsupported-version")
  163. def visit_typevartuple(self, node: nodes.TypeVarTuple) -> None:
  164. if not self._py312_plus:
  165. self.add_message(
  166. "using-generic-type-syntax-in-unsupported-version",
  167. node=node,
  168. confidence=HIGH,
  169. )
  170. def register(linter: PyLinter) -> None:
  171. linter.register_checker(UnsupportedVersionChecker(linter))