Coverage for tdom/tnodes.py: 100%

67 statements  

« 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 

3 

4from .template_utils import PartPosition, TemplateRef, TemplateSpan 

5 

6 

7@dataclass(slots=True, frozen=True) 

8class TLiteralAttribute: 

9 name: str 

10 value: str | None 

11 

12 

13@dataclass(slots=True, frozen=True) 

14class TInterpolatedAttribute: 

15 name: str 

16 value_i_index: int 

17 

18 

19@dataclass(slots=True, frozen=True) 

20class TTemplatedAttribute: 

21 name: str 

22 value_ref: TemplateRef 

23 

24 

25@dataclass(slots=True, frozen=True) 

26class TSpreadAttribute: 

27 i_index: int 

28 

29 

30type TAttribute = ( 

31 TLiteralAttribute | TTemplatedAttribute | TInterpolatedAttribute | TSpreadAttribute 

32) 

33 

34 

35@dataclass(slots=True, frozen=True) 

36class TNode: 

37 def __html__(self) -> str: 

38 raise NotImplementedError("Cannot render TNode to HTML directly.") 

39 

40 def __str__(self) -> str: 

41 raise NotImplementedError("Cannot render TNode to string directly.") 

42 

43 

44@dataclass(slots=True, frozen=True) 

45class TText(TNode): 

46 ref: TemplateRef 

47 

48 source_pos: PartPosition | None = field(default=None, compare=False) 

49 

50 @classmethod 

51 def empty(cls) -> t.Self: 

52 return cls(TemplateRef.empty()) 

53 

54 @classmethod 

55 def literal(cls, text: str) -> t.Self: 

56 return cls(TemplateRef.literal(text)) 

57 

58 

59@dataclass(slots=True, frozen=True) 

60class TComment(TNode): 

61 ref: TemplateRef 

62 

63 source_pos: PartPosition | None = field(default=None, compare=False) 

64 

65 @classmethod 

66 def literal(cls, text: str) -> t.Self: 

67 return cls(TemplateRef.literal(text)) 

68 

69 

70@dataclass(slots=True, frozen=True) 

71class TDocumentType(TNode): 

72 text: str 

73 

74 source_pos: PartPosition | None = field(default=None, compare=False) 

75 

76 

77@dataclass(slots=True, frozen=True) 

78class TFragment(TNode): 

79 children: tuple[TNode, ...] = field(default_factory=tuple) 

80 

81 

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) 

87 

88 source_pos: PartPosition | None = field(default=None, compare=False) 

89 

90 

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.""" 

95 

96 end_i_index: int | None = None 

97 """The interpolation index for the component's ending tag name, if any.""" 

98 

99 children_span: TemplateSpan | None = None 

100 """The source span for the component's children, if it has an end tag.""" 

101 

102 attrs: tuple[TAttribute, ...] = field(default_factory=tuple) 

103 

104 source_pos: PartPosition | None = field(default=None, compare=False) 

105 

106 

107@dataclass(frozen=True, slots=True) 

108class TagSourceInfo: 

109 """ 

110 Retained tag information from the parsed source meant for error reporting. 

111 

112 @NOTE: This must be cacheable so it should not directly reference a 

113 template instance. 

114 """ 

115 

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>.""" 

122 

123 @property 

124 def starttag_pos(self) -> PartPosition: 

125 """Template part position where the start tag begins.""" 

126 return self.starttag_span.start 

127 

128 

129@dataclass(slots=True, frozen=True) 

130class TTree: 

131 root: TNode 

132 

133 sinfos: tuple[TagSourceInfo, ...] = () 

134 

135 def unpack_sinfo_table(self) -> dict[PartPosition, TagSourceInfo]: 

136 return {sinfo.starttag_pos: sinfo for sinfo in self.sinfos}