Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

html!: fix mixed self-closing and non-sc tags #523

Merged
merged 1 commit into from
Jul 26, 2019

Conversation

totorigolo
Copy link
Contributor

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

html! {
    <div>
        <div/> // <- considered as open tag
    </div>
}
error: this open tag has no corresponding close tag
   --> src/lib.rs:264:17
    |
... | <div>
    | ^^^^^

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 standard function returning (ident, is_self_closing).

Fixes: #522

@totorigolo
Copy link
Contributor Author

I checked if the problem is actually allowed by the HTML specification, and it happens that it's not: https://stackoverflow.com/a/3558200/2209243

@totorigolo totorigolo closed this Jul 9, 2019
@jstarry
Copy link
Member

jstarry commented Jul 21, 2019

Hey @totorigolo I think this is actually worth supporting. JSX supports self-closing tags so I think developers will be accustomed to this short-hand even if it's not valid HTML. See here: https://reactjs.org/docs/jsx-in-depth.html

@jstarry jstarry reopened this Jul 21, 2019
@totorigolo totorigolo force-pushed the fix-mixed-tags branch 2 times, most recently from 59e90a9 to 57ffe3e Compare July 23, 2019 16:51
Copy link
Member

@jstarry jstarry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks awesome! I think your approach looks good, could you add one more test and fix the merge conflicts? Thanks!


let a: VNode<Comp> = html! {
<div>
<div data-val={ 2 / 1 }/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add a test for an expression with a >? Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works:

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

But this doesn't:

let a: VNode<CompInt> = html! { <div> <div onblur=|_| 2 > 1/>  </div> };
let b: VNode<CompInt> = html! { <div> <div onblur=|_| false></div> </div> };
assert_eq!(a, b); // NB: assert_eq! doesn't (cannot) compare the closures

It wasn't working before this PR, is now breaking with a less nice message. This should however happen very rarely, and the easy workaround is to add brackets around 2 > 1. React's JSX doesn't have this problem AFAIK because it enforces putting brackets around expressions. I think that I'll take the pragmatic approach here and try to add a meaningful error message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aforementioned failing test on master:

diff --git a/tests/vtag_test.rs b/tests/vtag_test.rs
index 5ca5d6d..312888c 100644
--- a/tests/vtag_test.rs
+++ b/tests/vtag_test.rs
@@ -28,6 +28,27 @@ impl Renderable<Comp> for Comp {
     }
 }
 
+struct CompBool;
+
+impl Component for CompBool {
+    type Message = bool;
+    type Properties = ();
+
+    fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
+        CompBool
+    }
+
+    fn update(&mut self, _: Self::Message) -> ShouldRender {
+        unimplemented!();
+    }
+}
+
+impl Renderable<CompBool> for CompBool {
+    fn view(&self) -> Html<Self> {
+        unimplemented!();
+    }
+}
+
 #[test]
 fn it_compares_tags() {
     let a: VNode<Comp> = html! {
@@ -258,3 +279,11 @@ fn it_allows_aria_attributes() {
         panic!("vtag expected");
     }
 }
+
+#[test]
+fn this_fails() {
+    let a: VNode<CompBool> = html! { <div> <a onblur=|_| 2 > 1 />  </div> };
+    let b: VNode<CompBool> = html! { <div> <a onblur=|_| false></div> </div> };
+    assert_eq!(a, b);
+}
+

error: this open tag has no corresponding close tag
   --> tests/vtag_test.rs:285:44
    |
285 |     let a: VNode<CompBool> = html! { <div> <a onblur=|_| 2 > 1 />  </div> };
    |                                            ^^^^^^^^^^^^^^^^^
error: this open tag has no corresponding close tag
   --> tests/vtag_test.rs:286:44
    |
286 |     let b: VNode<CompBool> = html! { <div> <a onblur=|_| false></div> </div> };
    |                                            ^^^^^^^^^^^^^^^^^^^^
error: aborting due to 2 previous errors
error: Could not compile `yew`.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a feeling that was broken in the current macro implementation ;) thanks for adding tests and a better error message!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see three choices in order to provide better error messages:

  • wait for proc-macro diagnostics, which are currently nightly-only, and just use eprintln! in the meantime (this error should occur quite rarely).
  • mess around the actual code in order to return a result from verify_end.
  • use global variables.

I implemented the first one for now. What do you think?

@totorigolo
Copy link
Contributor Author

@jstarry How do you run failing tests like tests/macro/html-tag-fail.rs?

@jstarry
Copy link
Member

jstarry commented Jul 25, 2019

@totorigolo totorigolo force-pushed the fix-mixed-tags branch 2 times, most recently from 892856d to dd54b18 Compare July 25, 2019 15:40
Copy link
Member

@jstarry jstarry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, just a few small changes needed

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() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recently learned that Ident implements PartialEq so open.ident == next_close_ident should be fine

// only '<' or '{ ... }' can follow. (that should be
// enough for reasonable cases)
//
let next_punct = next_cursor.punct().is_some();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also check if punct.as_char() == '<'

crates/macro/src/html_tree/html_tag/mod.rs Show resolved Hide resolved
#### Problem
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>
    | ^^^^^
```

#### Solution
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).

#### Notes
I added a TODO for when proc-macro::Diagnostic gets stabilized, in
order to replace the clunky `eprintln!` with proper diagnostics. I
didn't try to return a Result to get a nice error right now because
this error should be fairly rare so we can just wait IMO.

Fixes: yewstack#522
@totorigolo
Copy link
Contributor Author

Fixed 🙂

@jstarry
Copy link
Member

jstarry commented Jul 26, 2019

bors r+

bors bot added a commit that referenced this pull request Jul 26, 2019
523: html!: fix mixed self-closing and non-sc tags r=jstarry a=totorigolo

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>
    | ^^^^^
```

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 standard function returning `(ident, is_self_closing)`.

Fixes: #522

Co-authored-by: Thomas Lacroix <[email protected]>
@jstarry
Copy link
Member

jstarry commented Jul 26, 2019

Thanks for fixing this!

@bors
Copy link
Contributor

bors bot commented Jul 26, 2019

Build succeeded

@bors bors bot merged commit 5908d33 into yewstack:master Jul 26, 2019
@totorigolo totorigolo deleted the fix-mixed-tags branch August 4, 2019 18:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

HTML tags are not finding closing tags for longer <div> blocks
2 participants