diff --git a/tdom/parser.py b/tdom/parser.py index 2eec36d..e73e3d4 100644 --- a/tdom/parser.py +++ b/tdom/parser.py @@ -189,11 +189,9 @@ def make_open_tag(self, tag: str, attrs: Sequence[HTMLAttribute]) -> OpenTag: # @NOTE: This must be called when the tag is handled since it is # populated based on the most recently finished start tag. Otherwise # the value will be out of sync. - starttag_text = self.get_starttag_text() - if starttag_text is None: - raise AssertionError( - f"Expected startag_text to be set when parsing component at {i_index}." - ) + starttag_text = self.always_get_starttag_text( + f"Expected startag_text to be set when parsing component at {i_index}." + ) tattrs = self.make_tattrs(attrs) @@ -371,12 +369,40 @@ def validate_end_tag(self, tag: str, open_tag: OpenTag) -> int | None: # any of this in the parser, instead relying on higher layers. return tag_ref.i_indexes[0] + def always_get_starttag_text( + self, msg: str = "Expecting starttag text to be set." + ) -> str: + """ + Wrap get_starttag_text and just raise if None is returned. + + Do this so we don't guard for `None` everywhere. + """ + starttag_text = self.get_starttag_text() + if starttag_text is None: + raise AssertionError(msg) + return starttag_text + # ------------------------------------------ # HTMLParser tag callbacks # ------------------------------------------ def handle_starttag(self, tag: str, attrs: Sequence[HTMLAttribute]) -> None: open_tag = self.make_open_tag(tag, attrs) + # An unquoted attribute value within a self-closing tag can consume + # the "/" as part of the attribute's value if not separated by whitespace. + # This is the CORRECT behavior but can can be especially confusing if + # preceded by an interpolation, such as `<{Comp} name={value}/>`. + # We explicitly dissallow this usage without preceding ascii whitespace. + # This applies to all tags (components or not). + if self.always_get_starttag_text().endswith("/>"): + if isinstance(open_tag, OpenTComponent): + error_tag = self.get_source().format_starttag(open_tag.start_i_index) + else: + error_tag = tag + raise ValueError( + f'Ambiguous self-closing tag, "<{error_tag}.../>", please precede "/>" with whitespace or apply quotes around the last attribute value.' + ) + if isinstance(open_tag, OpenTElement) and open_tag.tag in VOID_ELEMENTS: final_tag = self.finalize_tag(open_tag) self.append_child(final_tag) diff --git a/tdom/parser_test.py b/tdom/parser_test.py index d1650ae..8983e40 100644 --- a/tdom/parser_test.py +++ b/tdom/parser_test.py @@ -602,3 +602,77 @@ def test_extract_with_templated_attr_gt_char(self, Component): strings=("