Improve prop docs (#1565)
* Do the stuff * More sanitizing + fix warnings and format
This commit is contained in:
1 parent
74952179db
commit
cea9563e25
4 files changed
+177
-34
No files matched your search
@@ -19,6 +19,7 @@ syn = { version = "2.0", features = ["full", "extra-traits"] }
|
|||||||
dioxus-rsx = { workspace = true }
|
dioxus-rsx = { workspace = true }
|
||||||
dioxus-core = { workspace = true }
|
dioxus-core = { workspace = true }
|
||||||
constcat = "0.3.0"
|
constcat = "0.3.0"
|
||||||
|
prettyplease = "0.2.15"
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -153,9 +153,20 @@ fn get_props_docs(fn_ident: &Ident, inputs: Vec<&FnArg>) -> Vec<Attribute> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Some((
|
Some((
|
||||||
pt.pat.to_token_stream().to_string(),
|
&pt.pat,
|
||||||
pt.ty.to_token_stream().to_string(),
|
&pt.ty,
|
||||||
pt.attrs.iter().filter(|a| !is_attr_doc(a)),
|
pt.attrs.iter().find_map(|attr| {
|
||||||
|
if attr.path() != &parse_quote!(deprecated) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = crate::utils::DeprecatedAttribute::from_meta(&attr.meta);
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Err(e) => panic!("{}", e.to_string()),
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
}
|
||||||
|
}),
|
||||||
arg_doc,
|
arg_doc,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -171,40 +182,37 @@ fn get_props_docs(fn_ident: &Ident, inputs: Vec<&FnArg>) -> Vec<Attribute> {
|
|||||||
#[doc = #header]
|
#[doc = #header]
|
||||||
});
|
});
|
||||||
|
|
||||||
for (arg_name, arg_type, attrs, input_arg_doc) in arg_docs {
|
for (arg_name, arg_type, deprecation, input_arg_doc) in arg_docs {
|
||||||
let input_arg_doc =
|
let arg_name = arg_name.into_token_stream().to_string();
|
||||||
keep_up_to_two_consecutive_chars(input_arg_doc.trim_matches('\n'), '\n')
|
let arg_type = crate::utils::format_type_string(arg_type);
|
||||||
.replace('\n', "<br>");
|
|
||||||
let prop_def_link = format!("{props_def_link}::{arg_name}");
|
|
||||||
let mut attribute_list = String::new();
|
|
||||||
let mut is_deprecated = false;
|
|
||||||
|
|
||||||
for attr in attrs {
|
let input_arg_doc =
|
||||||
if attr.path() == &parse_quote!(deprecated) {
|
keep_up_to_n_consecutive_chars(input_arg_doc.trim(), 2, '\n').replace('\n', "<br/>");
|
||||||
is_deprecated = true;
|
let prop_def_link = format!("{props_def_link}::{arg_name}");
|
||||||
continue;
|
let mut arg_doc = format!("- [`{arg_name}`]({prop_def_link}) : `{arg_type}`");
|
||||||
|
|
||||||
|
if let Some(deprecation) = deprecation {
|
||||||
|
arg_doc.push_str("<br/>👎 Deprecated");
|
||||||
|
|
||||||
|
if let Some(since) = deprecation.since {
|
||||||
|
arg_doc.push_str(&format!(" since {since}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
attribute_list.push('`');
|
if let Some(note) = deprecation.note {
|
||||||
attribute_list.push_str("e!(#attr).into_token_stream().to_string());
|
let note = keep_up_to_n_consecutive_chars(¬e, 1, '\n').replace('\n', " ");
|
||||||
attribute_list.push_str("`, ");
|
let note = keep_up_to_n_consecutive_chars(¬e, 1, '\t').replace('\t', " ");
|
||||||
|
|
||||||
|
arg_doc.push_str(&format!(": {note}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !input_arg_doc.is_empty() {
|
||||||
|
arg_doc.push_str("<hr/>");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
arg_doc.push_str("<br/>");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut arg_doc = format!("- [`{arg_name}`]({prop_def_link})");
|
arg_doc.push_str(&input_arg_doc);
|
||||||
|
|
||||||
if is_deprecated {
|
|
||||||
arg_doc.push_str(" 👎 Deprecated");
|
|
||||||
}
|
|
||||||
|
|
||||||
arg_doc.push_str(&format!("<br>Type: `{arg_type}`"));
|
|
||||||
|
|
||||||
if !attribute_list.is_empty() {
|
|
||||||
// Truncate the last `, `
|
|
||||||
attribute_list.truncate(attribute_list.len() - 2);
|
|
||||||
arg_doc.push_str(&format!("<br>Attributes: {attribute_list}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
arg_doc.push_str(&format!("<hr>{input_arg_doc}"));
|
|
||||||
|
|
||||||
props_docs.push(parse_quote! {
|
props_docs.push(parse_quote! {
|
||||||
#[doc = #arg_doc]
|
#[doc = #arg_doc]
|
||||||
@@ -302,7 +310,11 @@ fn is_attr_doc(attr: &Attribute) -> bool {
|
|||||||
attr.path() == &parse_quote!(doc)
|
attr.path() == &parse_quote!(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keep_up_to_two_consecutive_chars(input: &str, target_char: char) -> String {
|
fn keep_up_to_n_consecutive_chars(
|
||||||
|
input: &str,
|
||||||
|
n_of_consecutive_chars_allowed: usize,
|
||||||
|
target_char: char,
|
||||||
|
) -> String {
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
let mut prev_char: Option<char> = None;
|
let mut prev_char: Option<char> = None;
|
||||||
let mut consecutive_count = 0;
|
let mut consecutive_count = 0;
|
||||||
@@ -310,7 +322,7 @@ fn keep_up_to_two_consecutive_chars(input: &str, target_char: char) -> String {
|
|||||||
for c in input.chars() {
|
for c in input.chars() {
|
||||||
match prev_char {
|
match prev_char {
|
||||||
Some(prev) if c == target_char && prev == target_char => {
|
Some(prev) if c == target_char && prev == target_char => {
|
||||||
if consecutive_count < 2 {
|
if consecutive_count < n_of_consecutive_chars_allowed {
|
||||||
output.push(c);
|
output.push(c);
|
||||||
consecutive_count += 1;
|
consecutive_count += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use syn::{parse_macro_input, Path, Token};
|
|||||||
mod component_body;
|
mod component_body;
|
||||||
mod component_body_deserializers;
|
mod component_body_deserializers;
|
||||||
mod props;
|
mod props;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
// mod rsx;
|
// mod rsx;
|
||||||
use crate::component_body::ComponentBody;
|
use crate::component_body::ComponentBody;
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
use quote::ToTokens;
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{parse_quote, Expr, Lit, Meta, Token, Type};
|
||||||
|
|
||||||
|
const FORMATTED_TYPE_START: &str = "static TY_AFTER_HERE:";
|
||||||
|
const FORMATTED_TYPE_END: &str = "= todo!();";
|
||||||
|
|
||||||
|
/// Attempts to convert the given literal to a string.
|
||||||
|
/// Converts ints and floats to their base 10 counterparts.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the literal is [`Lit::Verbatim`] or if the literal is [`Lit::ByteStr`]
|
||||||
|
/// and the byte string could not be converted to UTF-8.
|
||||||
|
pub fn lit_to_string(lit: Lit) -> Option<String> {
|
||||||
|
match lit {
|
||||||
|
Lit::Str(l) => Some(l.value()),
|
||||||
|
Lit::ByteStr(l) => String::from_utf8(l.value()).ok(),
|
||||||
|
Lit::Byte(l) => Some(String::from(l.value() as char)),
|
||||||
|
Lit::Char(l) => Some(l.value().to_string()),
|
||||||
|
Lit::Int(l) => Some(l.base10_digits().to_string()),
|
||||||
|
Lit::Float(l) => Some(l.base10_digits().to_string()),
|
||||||
|
Lit::Bool(l) => Some(l.value().to_string()),
|
||||||
|
Lit::Verbatim(_) => None,
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_type_string(ty: &Type) -> String {
|
||||||
|
let ty_unformatted = ty.into_token_stream().to_string();
|
||||||
|
let ty_unformatted = ty_unformatted.trim();
|
||||||
|
|
||||||
|
// This should always be valid syntax.
|
||||||
|
// Not Rust code, but syntax, which is the only thing that `syn` cares about.
|
||||||
|
let Ok(file_unformatted) = syn::parse_file(&format!(
|
||||||
|
"{FORMATTED_TYPE_START}{ty_unformatted}{FORMATTED_TYPE_END}"
|
||||||
|
)) else {
|
||||||
|
return ty_unformatted.to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_formatted = prettyplease::unparse(&file_unformatted);
|
||||||
|
|
||||||
|
let file_trimmed = file_formatted.trim();
|
||||||
|
let start_removed = file_trimmed.trim_start_matches(FORMATTED_TYPE_START);
|
||||||
|
let end_removed = start_removed.trim_end_matches(FORMATTED_TYPE_END);
|
||||||
|
let ty_formatted = end_removed.trim();
|
||||||
|
|
||||||
|
ty_formatted.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the `#[deprecated]` attribute.
|
||||||
|
///
|
||||||
|
/// You can use the [`DeprecatedAttribute::from_meta`] function to try to parse an attribute to this struct.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DeprecatedAttribute {
|
||||||
|
pub since: Option<String>,
|
||||||
|
pub note: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeprecatedAttribute {
|
||||||
|
/// Returns `None` if the given attribute was not a valid form of the `#[deprecated]` attribute.
|
||||||
|
pub fn from_meta(meta: &Meta) -> syn::Result<Self> {
|
||||||
|
if meta.path() != &parse_quote!(deprecated) {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
meta.span(),
|
||||||
|
"attribute path is not `deprecated`",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
match &meta {
|
||||||
|
Meta::Path(_) => Ok(Self::default()),
|
||||||
|
Meta::NameValue(name_value) => {
|
||||||
|
let Expr::Lit(expr_lit) = &name_value.value else {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
name_value.span(),
|
||||||
|
"literal in `deprecated` value must be a string",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
since: None,
|
||||||
|
note: lit_to_string(expr_lit.lit.clone()).map(|s| s.trim().to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Meta::List(list) => {
|
||||||
|
let parsed = list.parse_args::<DeprecatedAttributeArgsParser>()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
since: parsed.since.map(|s| s.trim().to_string()),
|
||||||
|
note: parsed.note.map(|s| s.trim().to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod kw {
|
||||||
|
use syn::custom_keyword;
|
||||||
|
custom_keyword!(since);
|
||||||
|
custom_keyword!(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeprecatedAttributeArgsParser {
|
||||||
|
since: Option<String>,
|
||||||
|
note: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for DeprecatedAttributeArgsParser {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let mut since: Option<String> = None;
|
||||||
|
let mut note: Option<String> = None;
|
||||||
|
|
||||||
|
if input.peek(kw::since) {
|
||||||
|
input.parse::<kw::since>()?;
|
||||||
|
input.parse::<Token![=]>()?;
|
||||||
|
|
||||||
|
since = lit_to_string(input.parse()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.peek(Token![,]) && input.peek2(kw::note) {
|
||||||
|
input.parse::<Token![,]>()?;
|
||||||
|
input.parse::<kw::note>()?;
|
||||||
|
input.parse::<Token![=]>()?;
|
||||||
|
|
||||||
|
note = lit_to_string(input.parse()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { since, note })
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user