mixins.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. # Copyright (c) 2010-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
  2. # Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
  3. # Copyright (c) 2014 Google, Inc.
  4. # Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
  5. # Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
  6. # Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
  7. # Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
  8. # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
  9. # Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
  10. # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
  11. # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
  12. # Copyright (c) 2021 pre-commit-ci[bot] <bot@noreply.github.com>
  13. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  14. # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
  15. """This module contains some mixins for the different nodes.
  16. """
  17. import itertools
  18. from typing import TYPE_CHECKING, Optional
  19. from astroid import decorators
  20. from astroid.exceptions import AttributeInferenceError
  21. if TYPE_CHECKING:
  22. from astroid import nodes
  23. class BlockRangeMixIn:
  24. """override block range"""
  25. @decorators.cachedproperty
  26. def blockstart_tolineno(self):
  27. return self.lineno
  28. def _elsed_block_range(self, lineno, orelse, last=None):
  29. """handle block line numbers range for try/finally, for, if and while
  30. statements
  31. """
  32. if lineno == self.fromlineno:
  33. return lineno, lineno
  34. if orelse:
  35. if lineno >= orelse[0].fromlineno:
  36. return lineno, orelse[-1].tolineno
  37. return lineno, orelse[0].fromlineno - 1
  38. return lineno, last or self.tolineno
  39. class FilterStmtsMixin:
  40. """Mixin for statement filtering and assignment type"""
  41. def _get_filtered_stmts(self, _, node, _stmts, mystmt: Optional["nodes.Statement"]):
  42. """method used in _filter_stmts to get statements and trigger break"""
  43. if self.statement(future=True) is mystmt:
  44. # original node's statement is the assignment, only keep
  45. # current node (gen exp, list comp)
  46. return [node], True
  47. return _stmts, False
  48. def assign_type(self):
  49. return self
  50. class AssignTypeMixin:
  51. def assign_type(self):
  52. return self
  53. def _get_filtered_stmts(
  54. self, lookup_node, node, _stmts, mystmt: Optional["nodes.Statement"]
  55. ):
  56. """method used in filter_stmts"""
  57. if self is mystmt:
  58. return _stmts, True
  59. if self.statement(future=True) is mystmt:
  60. # original node's statement is the assignment, only keep
  61. # current node (gen exp, list comp)
  62. return [node], True
  63. return _stmts, False
  64. class ParentAssignTypeMixin(AssignTypeMixin):
  65. def assign_type(self):
  66. return self.parent.assign_type()
  67. class ImportFromMixin(FilterStmtsMixin):
  68. """MixIn for From and Import Nodes"""
  69. def _infer_name(self, frame, name):
  70. return name
  71. def do_import_module(self, modname=None):
  72. """return the ast for a module whose name is <modname> imported by <self>"""
  73. # handle special case where we are on a package node importing a module
  74. # using the same name as the package, which may end in an infinite loop
  75. # on relative imports
  76. # XXX: no more needed ?
  77. mymodule = self.root()
  78. level = getattr(self, "level", None) # Import as no level
  79. if modname is None:
  80. modname = self.modname
  81. # XXX we should investigate deeper if we really want to check
  82. # importing itself: modname and mymodule.name be relative or absolute
  83. if mymodule.relative_to_absolute_name(modname, level) == mymodule.name:
  84. # FIXME: we used to raise InferenceError here, but why ?
  85. return mymodule
  86. return mymodule.import_module(
  87. modname, level=level, relative_only=level and level >= 1
  88. )
  89. def real_name(self, asname):
  90. """get name from 'as' name"""
  91. for name, _asname in self.names:
  92. if name == "*":
  93. return asname
  94. if not _asname:
  95. name = name.split(".", 1)[0]
  96. _asname = name
  97. if asname == _asname:
  98. return name
  99. raise AttributeInferenceError(
  100. "Could not find original name for {attribute} in {target!r}",
  101. target=self,
  102. attribute=asname,
  103. )
  104. class MultiLineBlockMixin:
  105. """Mixin for nodes with multi-line blocks, e.g. For and FunctionDef.
  106. Note that this does not apply to every node with a `body` field.
  107. For instance, an If node has a multi-line body, but the body of an
  108. IfExpr is not multi-line, and hence cannot contain Return nodes,
  109. Assign nodes, etc.
  110. """
  111. @decorators.cachedproperty
  112. def _multi_line_blocks(self):
  113. return tuple(getattr(self, field) for field in self._multi_line_block_fields)
  114. def _get_return_nodes_skip_functions(self):
  115. for block in self._multi_line_blocks:
  116. for child_node in block:
  117. if child_node.is_function:
  118. continue
  119. yield from child_node._get_return_nodes_skip_functions()
  120. def _get_yield_nodes_skip_lambdas(self):
  121. for block in self._multi_line_blocks:
  122. for child_node in block:
  123. if child_node.is_lambda:
  124. continue
  125. yield from child_node._get_yield_nodes_skip_lambdas()
  126. @decorators.cached
  127. def _get_assign_nodes(self):
  128. children_assign_nodes = (
  129. child_node._get_assign_nodes()
  130. for block in self._multi_line_blocks
  131. for child_node in block
  132. )
  133. return list(itertools.chain.from_iterable(children_assign_nodes))
  134. class NoChildrenMixin:
  135. """Mixin for nodes with no children, e.g. Pass."""
  136. def get_children(self):
  137. yield from ()