This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew_macro/html_tree/lint/
mod.rs

1//! Lints to catch possible misuse of the `html!` macro use. At the moment these are mostly focused
2//! on accessibility.
3
4use proc_macro_error::emit_warning;
5use syn::spanned::Spanned;
6
7use super::html_element::{HtmlElement, TagName};
8use super::HtmlTree;
9use crate::props::{ElementProps, Prop};
10
11/// Lints HTML elements to check if they are well formed. If the element is not well-formed, then
12/// use `proc-macro-error` (and the `emit_warning!` macro) to produce a warning. At present, these
13/// are only emitted on nightly.
14pub trait Lint {
15    #[cfg_attr(not(yew_lints), allow(dead_code))]
16    fn lint(element: &HtmlElement);
17}
18
19/// Applies all the lints to the HTML tree.
20pub fn lint_all(tree: &HtmlTree) {
21    lint::<AHrefLint>(tree);
22    lint::<ImgAltLint>(tree);
23}
24
25/// Applies a specific lint to the HTML tree.
26pub fn lint<L>(tree: &HtmlTree)
27where
28    L: Lint,
29{
30    let _ = L::lint;
31    #[cfg(not(yew_lints))]
32    let _ = tree;
33    #[cfg(yew_lints)]
34    match tree {
35        HtmlTree::List(list) => {
36            for child in &list.children.0 {
37                lint::<L>(child)
38            }
39        }
40        HtmlTree::Element(el) => L::lint(el),
41        _ => {}
42    }
43}
44
45/// Retrieves an attribute from an element and returns a reference valid for the lifetime of the
46/// element (if that attribute can be found on the prop).
47///
48/// Attribute names are lowercased before being compared (so pass "href" for `name` and not "HREF").
49fn get_attribute<'a>(props: &'a ElementProps, name: &str) -> Option<&'a Prop> {
50    props
51        .attributes
52        .iter()
53        .find(|item| item.label.eq_ignore_ascii_case(name))
54}
55
56/// Lints to check if anchor (`<a>`) tags have valid `href` attributes defined.
57pub struct AHrefLint;
58
59impl Lint for AHrefLint {
60    fn lint(element: &HtmlElement) {
61        if let TagName::Lit(ref tag_name) = element.name {
62            if !tag_name.eq_ignore_ascii_case("a") {
63                return;
64            };
65            if let Some(prop) = get_attribute(&element.props, "href") {
66                if let syn::Expr::Lit(lit) = &prop.value {
67                    if let syn::Lit::Str(href) = &lit.lit {
68                        let href_value = href.value();
69                        match href_value.as_ref() {
70                            "#" | "javascript:void(0)" => emit_warning!(
71                                lit.span(),
72                                format!("'{href_value}' is not a suitable value for the `href` attribute. \
73                                        Without a meaningful attribute assistive technologies \
74                                        will struggle to understand your webpage. \
75                                        https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML#onclick_events"
76                            )),
77                            _ => {}
78
79                        }
80                    }
81                };
82            } else {
83                emit_warning!(
84                    quote::quote! {#tag_name}.span(),
85                    "All `<a>` elements should have a `href` attribute. This makes it possible \
86                        for assistive technologies to correctly interpret what your links point to. \
87                        https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML#more_on_links"
88                )
89            }
90        }
91    }
92}
93
94/// Checks to make sure that images have `alt` attributes defined.
95pub struct ImgAltLint;
96
97impl Lint for ImgAltLint {
98    fn lint(element: &HtmlElement) {
99        if let super::html_element::TagName::Lit(ref tag_name) = element.name {
100            if !tag_name.eq_ignore_ascii_case("img") {
101                return;
102            };
103            if get_attribute(&element.props, "alt").is_none() {
104                emit_warning!(
105                    quote::quote! {#tag_name}.span(),
106                    "All `<img>` tags should have an `alt` attribute which provides a \
107                     human-readable description "
108                )
109            }
110        }
111    }
112}