Skip to content

Commit de09ccf

Browse files
authored
👌 IMPROVE: Make SyntaxTreeNode type annotations work when subclassing (#131)
This commit makes `SyntaxTreeNode.children` and `SyntaxTreeNode.parent` `@property` s, so that they can be type annotated in a way where the getter return type is automatically bound to type of `self`.
1 parent 0cd5dc3 commit de09ccf

File tree

1 file changed

+60
-22
lines changed

1 file changed

+60
-22
lines changed

‎markdown_it/tree.py‎

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,32 @@
33
This module is not part of upstream JavaScript markdown-it.
44
"""
55
import textwrap
6-
from typing import NamedTuple, Sequence, Tuple, Dict, List, Optional, Any
6+
from typing import (
7+
NamedTuple,
8+
Sequence,
9+
Tuple,
10+
Dict,
11+
List,
12+
Optional,
13+
Any,
14+
TypeVar,
15+
Type,
16+
overload,
17+
Union,
18+
)
719

820
from .token import Token
921
from .utils import _removesuffix
1022

1123

24+
class _NesterTokens(NamedTuple):
25+
opening: Token
26+
closing: Token
27+
28+
29+
_NodeType = TypeVar("_NodeType", bound="SyntaxTreeNode")
30+
31+
1232
class SyntaxTreeNode:
1333
"""A Markdown syntax tree node.
1434
@@ -23,10 +43,6 @@ class SyntaxTreeNode:
2343
between
2444
"""
2545

26-
class _NesterTokens(NamedTuple):
27-
opening: Token
28-
closing: Token
29-
3046
def __init__(self) -> None:
3147
"""Initialize a root node with no children.
3248
@@ -36,23 +52,33 @@ def __init__(self) -> None:
3652
self.token: Optional[Token] = None
3753

3854
# Only containers have nester tokens
39-
self.nester_tokens: Optional[SyntaxTreeNode._NesterTokens] = None
55+
self.nester_tokens: Optional[_NesterTokens] = None
4056

4157
# Root node does not have self.parent
42-
self.parent: Optional["SyntaxTreeNode"] = None
58+
self._parent: Any = None
4359

4460
# Empty list unless a non-empty container, or unnested token that has
4561
# children (i.e. inline or img)
46-
self.children: List["SyntaxTreeNode"] = []
62+
self._children: list = []
4763

4864
def __repr__(self) -> str:
4965
return f"{type(self).__name__}({self.type})"
5066

51-
def __getitem__(self, item: int) -> "SyntaxTreeNode":
67+
@overload
68+
def __getitem__(self: _NodeType, item: int) -> _NodeType:
69+
...
70+
71+
@overload
72+
def __getitem__(self: _NodeType, item: slice) -> List[_NodeType]:
73+
...
74+
75+
def __getitem__(
76+
self: _NodeType, item: Union[int, slice]
77+
) -> Union[_NodeType, List[_NodeType]]:
5278
return self.children[item]
5379

5480
@classmethod
55-
def from_tokens(cls, tokens: Sequence[Token]) -> "SyntaxTreeNode":
81+
def from_tokens(cls: Type[_NodeType], tokens: Sequence[Token]) -> _NodeType:
5682
"""Instantiate a `SyntaxTreeNode` from a token stream.
5783
5884
This is the standard method for instantiating `SyntaxTreeNode`.
@@ -61,12 +87,10 @@ def from_tokens(cls, tokens: Sequence[Token]) -> "SyntaxTreeNode":
6187
root._set_children_from_tokens(tokens)
6288
return root
6389

64-
def to_tokens(self) -> List[Token]:
90+
def to_tokens(self: _NodeType) -> List[Token]:
6591
"""Recover the linear token stream."""
6692

67-
def recursive_collect_tokens(
68-
node: "SyntaxTreeNode", token_list: List[Token]
69-
) -> None:
93+
def recursive_collect_tokens(node: _NodeType, token_list: List[Token]) -> None:
7094
if node.type == "root":
7195
for child in node.children:
7296
recursive_collect_tokens(child, token_list)
@@ -83,6 +107,22 @@ def recursive_collect_tokens(
83107
recursive_collect_tokens(self, tokens)
84108
return tokens
85109

110+
@property
111+
def children(self: _NodeType) -> List[_NodeType]:
112+
return self._children
113+
114+
@children.setter
115+
def children(self: _NodeType, value: List[_NodeType]) -> None:
116+
self._children = value
117+
118+
@property
119+
def parent(self: _NodeType) -> Optional[_NodeType]:
120+
return self._parent
121+
122+
@parent.setter
123+
def parent(self: _NodeType, value: Optional[_NodeType]) -> None:
124+
self._parent = value
125+
86126
@property
87127
def is_root(self) -> bool:
88128
"""Is the node a special root node?"""
@@ -99,7 +139,7 @@ def is_nested(self) -> bool:
99139
return bool(self.nester_tokens)
100140

101141
@property
102-
def siblings(self) -> Sequence["SyntaxTreeNode"]:
142+
def siblings(self: _NodeType) -> Sequence[_NodeType]:
103143
"""Get siblings of the node.
104144
105145
Gets the whole group of siblings, including self.
@@ -125,7 +165,7 @@ def type(self) -> str:
125165
return _removesuffix(self.nester_tokens.opening.type, "_open")
126166

127167
@property
128-
def next_sibling(self) -> Optional["SyntaxTreeNode"]:
168+
def next_sibling(self: _NodeType) -> Optional[_NodeType]:
129169
"""Get the next node in the sequence of siblings.
130170
131171
Returns `None` if this is the last sibling.
@@ -136,7 +176,7 @@ def next_sibling(self) -> Optional["SyntaxTreeNode"]:
136176
return None
137177

138178
@property
139-
def previous_sibling(self) -> Optional["SyntaxTreeNode"]:
179+
def previous_sibling(self: _NodeType) -> Optional[_NodeType]:
140180
"""Get the previous node in the sequence of siblings.
141181
142182
Returns `None` if this is the first sibling.
@@ -147,11 +187,11 @@ def previous_sibling(self) -> Optional["SyntaxTreeNode"]:
147187
return None
148188

149189
def _make_child(
150-
self,
190+
self: _NodeType,
151191
*,
152192
token: Optional[Token] = None,
153193
nester_tokens: Optional[_NesterTokens] = None,
154-
) -> "SyntaxTreeNode":
194+
) -> _NodeType:
155195
"""Make and return a child node for `self`."""
156196
if token and nester_tokens or not token and not nester_tokens:
157197
raise ValueError("must specify either `token` or `nester_tokens`")
@@ -189,9 +229,7 @@ def _set_children_from_tokens(self, tokens: Sequence[Token]) -> None:
189229
raise ValueError(f"unclosed tokens starting {nested_tokens[0]}")
190230

191231
child = self._make_child(
192-
nester_tokens=SyntaxTreeNode._NesterTokens(
193-
nested_tokens[0], nested_tokens[-1]
194-
)
232+
nester_tokens=_NesterTokens(nested_tokens[0], nested_tokens[-1])
195233
)
196234
child._set_children_from_tokens(nested_tokens[1:-1])
197235

0 commit comments

Comments
 (0)