Coverage for tdom/tnodes.py: 100%
67 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-19 21:37 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-19 21:37 +0000
1import typing as t
2from dataclasses import dataclass, field
4from .template_utils import PartPosition, TemplateRef, TemplateSpan
7@dataclass(slots=True, frozen=True)
8class TLiteralAttribute:
9 name: str
10 value: str | None
13@dataclass(slots=True, frozen=True)
14class TInterpolatedAttribute:
15 name: str
16 value_i_index: int
19@dataclass(slots=True, frozen=True)
20class TTemplatedAttribute:
21 name: str
22 value_ref: TemplateRef
25@dataclass(slots=True, frozen=True)
26class TSpreadAttribute:
27 i_index: int
30type TAttribute = (
31 TLiteralAttribute | TTemplatedAttribute | TInterpolatedAttribute | TSpreadAttribute
32)
35@dataclass(slots=True, frozen=True)
36class TNode:
37 def __html__(self) -> str:
38 raise NotImplementedError("Cannot render TNode to HTML directly.")
40 def __str__(self) -> str:
41 raise NotImplementedError("Cannot render TNode to string directly.")
44@dataclass(slots=True, frozen=True)
45class TText(TNode):
46 ref: TemplateRef
48 source_pos: PartPosition | None = field(default=None, compare=False)
50 @classmethod
51 def empty(cls) -> t.Self:
52 return cls(TemplateRef.empty())
54 @classmethod
55 def literal(cls, text: str) -> t.Self:
56 return cls(TemplateRef.literal(text))
59@dataclass(slots=True, frozen=True)
60class TComment(TNode):
61 ref: TemplateRef
63 source_pos: PartPosition | None = field(default=None, compare=False)
65 @classmethod
66 def literal(cls, text: str) -> t.Self:
67 return cls(TemplateRef.literal(text))
70@dataclass(slots=True, frozen=True)
71class TDocumentType(TNode):
72 text: str
74 source_pos: PartPosition | None = field(default=None, compare=False)
77@dataclass(slots=True, frozen=True)
78class TFragment(TNode):
79 children: tuple[TNode, ...] = field(default_factory=tuple)
82@dataclass(slots=True, frozen=True)
83class TElement(TNode):
84 tag: str
85 attrs: tuple[TAttribute, ...] = field(default_factory=tuple)
86 children: tuple[TNode, ...] = field(default_factory=tuple)
88 source_pos: PartPosition | None = field(default=None, compare=False)
91@dataclass(slots=True, frozen=True)
92class TComponent(TNode):
93 start_i_index: int
94 """The interpolation index for the component's starting tag name."""
96 end_i_index: int | None = None
97 """The interpolation index for the component's ending tag name, if any."""
99 children_span: TemplateSpan | None = None
100 """The source span for the component's children, if it has an end tag."""
102 attrs: tuple[TAttribute, ...] = field(default_factory=tuple)
104 source_pos: PartPosition | None = field(default=None, compare=False)
107@dataclass(frozen=True, slots=True)
108class TagSourceInfo:
109 """
110 Retained tag information from the parsed source meant for error reporting.
112 @NOTE: This must be cacheable so it should not directly reference a
113 template instance.
114 """
116 starttag_span: TemplateSpan
117 """Source span occupied by the start tag."""
118 startend: bool
119 """Was parsed as startend tag, ie. <tag />."""
120 endtag_pos: PartPosition | None = None
121 """Template part position of the endtag, ie. </tag>."""
123 @property
124 def starttag_pos(self) -> PartPosition:
125 """Template part position where the start tag begins."""
126 return self.starttag_span.start
129@dataclass(slots=True, frozen=True)
130class TTree:
131 root: TNode
133 sinfos: tuple[TagSourceInfo, ...] = ()
135 def unpack_sinfo_table(self) -> dict[PartPosition, TagSourceInfo]:
136 return {sinfo.starttag_pos: sinfo for sinfo in self.sinfos}