Skip to content

Commit

Permalink
html!: allow mixing self-closing and non-sc tags
Browse files Browse the repository at this point in the history
Self-closing tags are more convenient when there are no children. For
instance, React's JSX allows it.

Before, self-closing tags where considered as open tags in `verify_end`,
causing codes like this to not compile:

```rust
html! {
    <div>
        <div/> // <- considered as open tag
    </div>
}
```

```
error: this open tag has no corresponding close tag
   --> src/lib.rs:264:17
    |
... | <div>
    | ^^^^^
```

Add a new `Peek`-able `HtmlSelfClosingTag`, used in `verify_end`.

However, this fix isn't ideal because it peeks the buffer twice for
non self-closing tags. I did it that way in order to keep the peek
thing. An alternative would be to turn HtmlSelfClosingTag::peek into a
function returning (ident, is_self_closing).

Fixes: #522
  • Loading branch information
totorigolo committed Jul 23, 2019
1 parent a33a89f commit 59e90a9
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 3 deletions.
37 changes: 34 additions & 3 deletions crates/macro-impl/src/html_tree/html_tag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl Parse for HtmlTag {
}

let open = input.parse::<HtmlTagOpen>()?;
if open.div.is_some() {
if open.div.is_some() { // self-closing tag
return Ok(HtmlTag {
ident: open.ident,
attributes: open.attributes,
Expand All @@ -58,9 +58,10 @@ impl Parse for HtmlTag {
}

let mut children: Vec<HtmlTree> = vec![];
let open_ident_str = open.ident.to_string();
loop {
if let Some(next_close_ident) = HtmlTagClose::peek(input.cursor()) {
if open.ident.to_string() == next_close_ident.to_string() {
if open_ident_str == next_close_ident.to_string() {
break;
}
}
Expand Down Expand Up @@ -162,7 +163,9 @@ impl HtmlTag {
fn verify_end(mut cursor: Cursor, open_ident: &Ident) -> bool {
let mut tag_stack_count = 1;
loop {
if let Some(next_open_ident) = HtmlTagOpen::peek(cursor) {
if HtmlSelfClosingTag::peek(cursor).is_some() {
// Do nothing
} else if let Some(next_open_ident) = HtmlTagOpen::peek(cursor) {
if open_ident.to_string() == next_open_ident.to_string() {
tag_stack_count += 1;
}
Expand All @@ -185,6 +188,34 @@ impl HtmlTag {
}
}

struct HtmlSelfClosingTag;

impl Peek<Ident> for HtmlSelfClosingTag {
fn peek(cursor: Cursor) -> Option<Ident> {
let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '<').as_option()?;

let (ident, cursor) = cursor.ident()?;
(ident.to_string().to_lowercase() == ident.to_string()).as_option()?;

let mut cursor = cursor;
loop {
if let Some((punct, next_cursor)) = cursor.punct() {
match punct.as_char() {
'/' => return Some(ident),
'>' => return None,
_ => {},
}
cursor = next_cursor;
} else if let Some((_, next)) = cursor.token_tree() {
cursor = next;
} else {
return None;
}
}
}
}

struct HtmlTagOpen {
lt: Token![<],
ident: Ident,
Expand Down
27 changes: 27 additions & 0 deletions crates/shared/tests/vtag_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,30 @@ fn it_allows_aria_attributes() {
panic!("vtag expected");
}
}

#[test]
fn it_checks_mixed_closing_tags() {
let a: VNode<Comp> = html! {
<div>
<div/>
</div>
};
let b: VNode<Comp> = html! {
<div>
<div></div>
</div>
};
assert_eq!(a, b);

let a: VNode<Comp> = html! {
<div>
<div data-val={ 2 / 1 }/>
</div>
};
let b: VNode<Comp> = html! {
<div>
<div data-val={ 2 }></div>
</div>
};
assert_eq!(a, b);
}

0 comments on commit 59e90a9

Please sign in to comment.