1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use std::path::PathBuf;

use super::{Attribute, AttributeType};
use crate::Result;
use convert_case::{Case, Casing};
use serde::{Deserialize, Serialize};
use weedle::interface::InterfaceMember;

/// The parsed WebIDL definitions converted from the raw spec.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParsedInterface {
    pub name: String,
    pub inherits_from: Option<String>,
    pub attributes: Vec<Attribute>,
}

pub fn parse_webidls(
    iter: impl Iterator<Item = Result<(String, PathBuf)>>,
) -> Result<Vec<ParsedInterface>> {
    let mut outputs = vec![];
    for res in iter {
        let (string, path) = res?;
        let filename = path.file_name().unwrap().to_str().unwrap();
        if !filename.starts_with("HTML") {
            continue;
        }
        let string = string.trim();
        let definitions = weedle::parse(&string).map_err(|err| err.to_string())?;
        let definitions = definitions.into_iter();
        for def in definitions {
            if let weedle::Definition::Interface(interface) = def {
                outputs.push(ParsedInterface {
                    name: parse_interface_name(&interface),
                    inherits_from: parse_inheritance(&interface),
                    attributes: interface
                        .members
                        .body
                        .iter()
                        .filter_map(parse_attributes)
                        .collect::<Vec<_>>(),
                });
            }
        }
    }
    Ok(outputs)
}

fn parse_interface_name(interface: &weedle::InterfaceDefinition) -> String {
    interface.identifier.0.to_owned()
}

fn parse_inheritance(interface: &weedle::InterfaceDefinition) -> Option<String> {
    interface
        .inheritance
        .map(|parent| parent.identifier.0.to_string())
}

fn parse_attributes(member: &InterfaceMember) -> Option<Attribute> {
    if let InterfaceMember::Attribute(attr) = member {
        // NOTE: we're skipping over all DOM-only methods for now.
        if attr.readonly.is_some() {
            return None;
        }

        let ty = match &attr.type_.type_ {
            weedle::types::Type::Single(ty) => match ty {
                weedle::types::SingleType::NonAny(ty) => match ty {
                    weedle::types::NonAnyType::Integer(_) => AttributeType::Integer,
                    weedle::types::NonAnyType::FloatingPoint(_) => AttributeType::Float,
                    weedle::types::NonAnyType::Boolean(_) => AttributeType::Bool,
                    weedle::types::NonAnyType::Object(_) => {
                        // `js-sys` doesn't handle this either, so we just skip right past it.
                        return None;
                    }
                    weedle::types::NonAnyType::USVString(_)
                    | weedle::types::NonAnyType::DOMString(_) => AttributeType::String,
                    weedle::types::NonAnyType::Identifier(id) => {
                        AttributeType::Identifier(id.type_.0.to_owned())
                    }
                    ty => unreachable!("{ty:?} is not a recognized type"),
                },
                weedle::types::SingleType::Any(_) => return None,
            },
            _ => return None,
        };
        let field_name = attr.identifier.0.to_string().to_case(Case::Snake);
        let field_name = super::normalize_field_name(&field_name);
        Some(Attribute {
            name: attr.identifier.0.to_string(),
            field_name,
            description: String::new(),
            ty,
        })
    } else {
        None
    }
}