px-wsdom-ts-parse 0.0.6

for the wsdom crate, and potentially other uses
Documentation
use winnow::{
    combinator::{alt, delimited, opt, separated_pair},
    PResult, Parser,
};

use super::{
    ts_type::TsType,
    util::{quote_backslash_escape, token, token_word, word1, Parsable},
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field<'a> {
    pub name: FieldName<'a>,
    pub readonly: bool,
    pub optional: bool,
    pub ty: TsType<'a>,
}
impl<'a> Parsable<'a> for Field<'a> {
    fn parse(input: &mut &'a str) -> PResult<Self> {
        separated_pair(
            (
                opt(token_word("readonly")),
                FieldName::parse,
                opt(token('?')),
            ),
            token(':'),
            TsType::parse,
        )
        .parse_next(input)
        .map(|((readonly, name, optional), ty)| Self {
            readonly: readonly.is_some(),
            name,
            optional: optional.is_some(),
            ty,
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FieldName<'a> {
    Name(&'a str),
    Wildcard { name: &'a str, ty: TsType<'a> },
}
impl<'a> Parsable<'a> for FieldName<'a> {
    fn parse(input: &mut &'a str) -> PResult<Self> {
        alt((
            word1.map(Self::Name),
            quote_backslash_escape('"').map(Self::Name),
            delimited(
                token('['),
                separated_pair(word1, token(':'), TsType::parse),
                token(']'),
            )
            .map(|(name, ty)| Self::Wildcard { name, ty }),
        ))
        .parse_next(input)
    }
}