no_self_use.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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. from typing import TYPE_CHECKING
  6. from astroid import nodes
  7. from pylint.checkers import BaseChecker
  8. from pylint.checkers.utils import (
  9. PYMETHODS,
  10. decorated_with_property,
  11. is_overload_stub,
  12. is_protocol_class,
  13. overrides_a_method,
  14. )
  15. from pylint.interfaces import INFERENCE
  16. if TYPE_CHECKING:
  17. from pylint.lint.pylinter import PyLinter
  18. class NoSelfUseChecker(BaseChecker):
  19. name = "no_self_use"
  20. msgs = {
  21. "R6301": (
  22. "Method could be a function",
  23. "no-self-use",
  24. "Used when a method doesn't use its bound instance, and so could "
  25. "be written as a function.",
  26. {"old_names": [("R0201", "old-no-self-use")]},
  27. ),
  28. }
  29. def __init__(self, linter: PyLinter) -> None:
  30. super().__init__(linter)
  31. self._first_attrs: list[str | None] = []
  32. self._meth_could_be_func: bool | None = None
  33. def visit_name(self, node: nodes.Name) -> None:
  34. """Check if the name handle an access to a class member
  35. if so, register it.
  36. """
  37. if self._first_attrs and (
  38. node.name == self._first_attrs[-1] or not self._first_attrs[-1]
  39. ):
  40. self._meth_could_be_func = False
  41. def visit_functiondef(self, node: nodes.FunctionDef) -> None:
  42. if not node.is_method():
  43. return
  44. self._meth_could_be_func = True
  45. self._check_first_arg_for_type(node)
  46. visit_asyncfunctiondef = visit_functiondef
  47. def _check_first_arg_for_type(self, node: nodes.FunctionDef) -> None:
  48. """Check the name of first argument."""
  49. # pylint: disable=duplicate-code
  50. if node.args.posonlyargs:
  51. first_arg = node.args.posonlyargs[0].name
  52. elif node.args.args:
  53. first_arg = node.argnames()[0]
  54. else:
  55. first_arg = None
  56. self._first_attrs.append(first_arg)
  57. # static method
  58. if node.type == "staticmethod":
  59. self._first_attrs[-1] = None
  60. def leave_functiondef(self, node: nodes.FunctionDef) -> None:
  61. """On method node, check if this method couldn't be a function.
  62. ignore class, static and abstract methods, initializer,
  63. methods overridden from a parent class.
  64. """
  65. if node.is_method():
  66. first = self._first_attrs.pop()
  67. if first is None:
  68. return
  69. class_node = node.parent.frame(future=True)
  70. if (
  71. self._meth_could_be_func
  72. and node.type == "method"
  73. and node.name not in PYMETHODS
  74. and not (
  75. node.is_abstract()
  76. or overrides_a_method(class_node, node.name)
  77. or decorated_with_property(node)
  78. or _has_bare_super_call(node)
  79. or is_protocol_class(class_node)
  80. or is_overload_stub(node)
  81. )
  82. ):
  83. self.add_message("no-self-use", node=node, confidence=INFERENCE)
  84. leave_asyncfunctiondef = leave_functiondef
  85. def _has_bare_super_call(fundef_node: nodes.FunctionDef) -> bool:
  86. for call in fundef_node.nodes_of_class(nodes.Call):
  87. func = call.func
  88. if isinstance(func, nodes.Name) and func.name == "super" and not call.args:
  89. return True
  90. return False
  91. def register(linter: PyLinter) -> None:
  92. linter.register_checker(NoSelfUseChecker(linter))