Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/diplomat_core/src/ast/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 14 kB image not shown  

Quelle  docs.rs   Sprache: unbekannt

 
use super::Path;
use core::fmt;
use quote::ToTokens;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use syn::parse::{self, Parse, ParseStream};
use syn::{Attribute, Ident, Meta, Token};

#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug, Default)]
pub struct Docs(String, Vec<RustLink>);

/// The type of markdown generated by [`Docs::to_markdown()`]
///
/// Note that this only controls markdown generated by this code. Existing markdown
/// in the Rust documentation will not be sanitized in any way.
#[derive(PartialEq, Eq, Clone, Debug)]
#[non_exhaustive]
pub enum MarkdownStyle {
    /// Regular markdown with no specific extensions, compatible with most common flavors
    Normal,
    /// Markdown that can be losslessly converted to ReStructuredText
    RstCompat,
}

impl Docs {
    pub fn from_attrs(attrs: &[Attribute]) -> Self {
        Self(Self::get_doc_lines(attrs), Self::get_rust_link(attrs))
    }

    fn get_doc_lines(attrs: &[Attribute]) -> String {
        let mut lines: String = String::new();

        attrs.iter().for_each(|attr| {
            if let Meta::NameValue(ref nv) = attr.meta {
                if nv.path.is_ident("doc") {
                    let node: syn::LitStr = syn::parse2(nv.value.to_token_stream()).unwrap();
                    let line = node.value().trim().to_string();

                    if !lines.is_empty() {
                        lines.push('\n');
                    }

                    lines.push_str(&line);
                }
            }
        });

        lines
    }

    fn get_rust_link(attrs: &[Attribute]) -> Vec<RustLink> {
        attrs
            .iter()
            .filter(|i| i.path().to_token_stream().to_string() == "diplomat :: rust_link")
            .map(|i| i.parse_args().expect("Malformed attribute"))
            .collect()
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty() && self.1.is_empty()
    }

    /// Convert to markdown
    pub fn to_markdown(&self, docs_url_gen: &DocsUrlGenerator, style: MarkdownStyle) -> String {
        use std::fmt::Write;
        let mut lines = self.0.clone();
        let mut has_compact = false;
        let backtick = if style == MarkdownStyle::RstCompat {
            ""
        } else {
            "`"
        };
        for rust_link in &self.1 {
            if rust_link.display == RustLinkDisplay::Compact {
                has_compact = true;
            } else if rust_link.display == RustLinkDisplay::Normal {
                if !lines.is_empty() {
                    write!(lines, "\n\n").unwrap();
                }
                write!(
                    lines,
                    "See the [Rust documentation for {backtick}{name}{backtick}]({link}) for more information.",
                    name = rust_link.path.elements.last().unwrap(),
                    link = docs_url_gen.gen_for_rust_link(rust_link)
                )
                .unwrap();
            }
        }
        if has_compact {
            if !lines.is_empty() {
                write!(lines, "\n\n").unwrap();
            }
            write!(lines, "Additional information: ").unwrap();
            for (i, rust_link) in self
                .1
                .iter()
                .filter(|r| r.display == RustLinkDisplay::Compact)
                .enumerate()
            {
                if i != 0 {
                    write!(lines, ", ").unwrap();
                }
                write!(
                    lines,
                    "[{}]({})",
                    i + 1,
                    docs_url_gen.gen_for_rust_link(rust_link)
                )
                .unwrap();
            }
        }
        lines
    }

    pub fn rust_links(&self) -> &[RustLink] {
        &self.1
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[non_exhaustive]
pub enum RustLinkDisplay {
    /// A nice expanded representation that includes the type name
    ///
    /// e.g. "See the \[link to Rust documentation\] for more details"
    Normal,
    /// A compact representation that will fit multiple rust_link entries in one line
    ///
    /// E.g. "For further information, see: 1, 2, 3, 4" (all links)
    Compact,
    /// Hidden. Useful for programmatically annotating an API as related without showing a link to the user
    Hidden,
}

#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug, PartialOrd, Ord)]
#[non_exhaustive]
pub struct RustLink {
    pub path: Path,
    pub typ: DocType,
    pub display: RustLinkDisplay,
}

impl Parse for RustLink {
    fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
        let path = input.parse()?;
        let path = Path::from_syn(&path);
        let _comma: Token![,] = input.parse()?;
        let ty_ident: Ident = input.parse()?;
        let typ = match &*ty_ident.to_string() {
            "Struct" => DocType::Struct,
            "StructField" => DocType::StructField,
            "Enum" => DocType::Enum,
            "EnumVariant" => DocType::EnumVariant,
            "EnumVariantField" => DocType::EnumVariantField,
            "Trait" => DocType::Trait,
            "FnInStruct" => DocType::FnInStruct,
            "FnInEnum" => DocType::FnInEnum,
            "FnInTrait" => DocType::FnInTrait,
            "DefaultFnInTrait" => DocType::DefaultFnInTrait,
            "Fn" => DocType::Fn,
            "Mod" => DocType::Mod,
            "Constant" => DocType::Constant,
            "AssociatedConstantInEnum" => DocType::AssociatedConstantInEnum,
            "AssociatedConstantInTrait" => DocType::AssociatedConstantInTrait,
            "AssociatedConstantInStruct" => DocType::AssociatedConstantInStruct,
            "Macro" => DocType::Macro,
            "AssociatedTypeInEnum" => DocType::AssociatedTypeInEnum,
            "AssociatedTypeInTrait" => DocType::AssociatedTypeInTrait,
            "AssociatedTypeInStruct" => DocType::AssociatedTypeInStruct,
            "Typedef" => DocType::Typedef,
            _ => {
                return Err(parse::Error::new(
                    ty_ident.span(),
                    "Unknown rust_link doc type",
                ))
            }
        };
        let lookahead = input.lookahead1();
        let display = if lookahead.peek(Token![,]) {
            let _comma: Token![,] = input.parse()?;
            let display_ident: Ident = input.parse()?;
            match &*display_ident.to_string() {
                "normal" => RustLinkDisplay::Normal,
                "compact" => RustLinkDisplay::Compact,
                "hidden" => RustLinkDisplay::Hidden,
                _ => return Err(parse::Error::new(display_ident.span(), "Unknown rust_link display style: Must be must be `normal`, `compact`, or `hidden`.")),
            }
        } else {
            RustLinkDisplay::Normal
        };
        Ok(RustLink { path, typ, display })
    }
}
impl fmt::Display for RustLink {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}#{:?}", self.path, self.typ)
    }
}

#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug, PartialOrd, Ord)]
#[non_exhaustive]
pub enum DocType {
    Struct,
    StructField,
    Enum,
    EnumVariant,
    EnumVariantField,
    Trait,
    FnInStruct,
    FnInEnum,
    FnInTrait,
    DefaultFnInTrait,
    Fn,
    Mod,
    Constant,
    AssociatedConstantInEnum,
    AssociatedConstantInTrait,
    AssociatedConstantInStruct,
    Macro,
    AssociatedTypeInEnum,
    AssociatedTypeInTrait,
    AssociatedTypeInStruct,
    Typedef,
}

#[derive(Default)]
pub struct DocsUrlGenerator {
    default_url: Option<String>,
    base_urls: HashMap<String, String>,
}

impl DocsUrlGenerator {
    pub fn with_base_urls(default_url: Option<String>, base_urls: HashMap<String, String>) -> Self {
        Self {
            default_url,
            base_urls,
        }
    }

    fn gen_for_rust_link(&self, rust_link: &RustLink) -> String {
        use DocType::*;

        let mut r = String::new();

        let base = self
            .base_urls
            .get(rust_link.path.elements[0].as_str())
            .map(String::as_str)
            .or(self.default_url.as_deref())
            .unwrap_or("https://docs.rs/");

        r.push_str(base);
        if !base.ends_with('/') {
            r.push('/');
        }
        if r == "https://docs.rs/" {
            r.push_str(rust_link.path.elements[0].as_str());
            r.push_str("/latest/");
        }

        let mut elements = rust_link.path.elements.iter().peekable();

        let module_depth = rust_link.path.elements.len()
            - match rust_link.typ {
                Mod => 0,
                Struct | Enum | Trait | Fn | Macro | Constant | Typedef => 1,
                FnInEnum
                | FnInStruct
                | FnInTrait
                | DefaultFnInTrait
                | EnumVariant
                | StructField
                | AssociatedTypeInEnum
                | AssociatedTypeInStruct
                | AssociatedTypeInTrait
                | AssociatedConstantInEnum
                | AssociatedConstantInStruct
                | AssociatedConstantInTrait => 2,
                EnumVariantField => 3,
            };

        for _ in 0..module_depth {
            r.push_str(elements.next().unwrap().as_str());
            r.push('/');
        }

        if elements.peek().is_none() {
            r.push_str("index.html");
            return r;
        }

        r.push_str(match rust_link.typ {
            Typedef => "type.",
            Struct
            | StructField
            | FnInStruct
            | AssociatedTypeInStruct
            | AssociatedConstantInStruct => "struct.",
            Enum
            | EnumVariant
            | EnumVariantField
            | FnInEnum
            | AssociatedTypeInEnum
            | AssociatedConstantInEnum => "enum.",
            Trait
            | FnInTrait
            | DefaultFnInTrait
            | AssociatedTypeInTrait
            | AssociatedConstantInTrait => "trait.",
            Fn => "fn.",
            Constant => "constant.",
            Macro => "macro.",
            Mod => unreachable!(),
        });

        r.push_str(elements.next().unwrap().as_str());

        r.push_str(".html");

        match rust_link.typ {
            FnInStruct | FnInEnum | DefaultFnInTrait => {
                r.push_str("#method.");
                r.push_str(elements.next().unwrap().as_str());
            }
            AssociatedTypeInStruct | AssociatedTypeInEnum | AssociatedTypeInTrait => {
                r.push_str("#associatedtype.");
                r.push_str(elements.next().unwrap().as_str());
            }
            AssociatedConstantInStruct | AssociatedConstantInEnum | AssociatedConstantInTrait => {
                r.push_str("#associatedconstant.");
                r.push_str(elements.next().unwrap().as_str());
            }
            FnInTrait => {
                r.push_str("#tymethod.");
                r.push_str(elements.next().unwrap().as_str());
            }
            EnumVariant => {
                r.push_str("#variant.");
                r.push_str(elements.next().unwrap().as_str());
            }
            StructField => {
                r.push_str("#structfield.");
                r.push_str(elements.next().unwrap().as_str());
            }
            EnumVariantField => {
                r.push_str("#variant.");
                r.push_str(elements.next().unwrap().as_str());
                r.push_str(".field.");
                r.push_str(elements.next().unwrap().as_str());
            }
            _ => {}
        }
        r
    }
}

#[test]
fn test_docs_url_generator() {
    let test_cases = [
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Struct)] },
            "https://docs.rs/std/latest/std/foo/bar/struct.batz.html",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, StructField)] },
            "https://docs.rs/std/latest/std/foo/struct.bar.html#structfield.batz",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Enum)] },
            "https://docs.rs/std/latest/std/foo/bar/enum.batz.html",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, EnumVariant)] },
            "https://docs.rs/std/latest/std/foo/enum.bar.html#variant.batz",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, EnumVariantField)] },
            "https://docs.rs/std/latest/std/enum.foo.html#variant.bar.field.batz",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Trait)] },
            "https://docs.rs/std/latest/std/foo/bar/trait.batz.html",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, FnInStruct)] },
            "https://docs.rs/std/latest/std/foo/struct.bar.html#method.batz",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, FnInEnum)] },
            "https://docs.rs/std/latest/std/foo/enum.bar.html#method.batz",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, FnInTrait)] },
            "https://docs.rs/std/latest/std/foo/trait.bar.html#tymethod.batz",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, DefaultFnInTrait)] },
            "https://docs.rs/std/latest/std/foo/trait.bar.html#method.batz",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Fn)] },
            "https://docs.rs/std/latest/std/foo/bar/fn.batz.html",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Mod)] },
            "https://docs.rs/std/latest/std/foo/bar/batz/index.html",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Constant)] },
            "https://docs.rs/std/latest/std/foo/bar/constant.batz.html",
        ),
        (
            syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Macro)] },
            "https://docs.rs/std/latest/std/foo/bar/macro.batz.html",
        ),
    ];

    for (attr, expected) in test_cases.clone() {
        assert_eq!(
            DocsUrlGenerator::default().gen_for_rust_link(&Docs::from_attrs(&[attr]).1[0]),
            expected
        );
    }

    assert_eq!(
        DocsUrlGenerator::with_base_urls(
            None,
            [("std".to_string(), "http://std-docs.biz/".to_string())]
                .into_iter()
                .collect()
        )
        .gen_for_rust_link(&Docs::from_attrs(&[test_cases[0].0.clone()]).1[0]),
        "http://std-docs.biz/std/foo/bar/struct.batz.html"
    );

    assert_eq!(
        DocsUrlGenerator::with_base_urls(Some("http://std-docs.biz/".to_string()), HashMap::new())
            .gen_for_rust_link(&Docs::from_attrs(&[test_cases[0].0.clone()]).1[0]),
        "http://std-docs.biz/std/foo/bar/struct.batz.html"
    );
}

[ Dauer der Verarbeitung: 0.28 Sekunden  (vorverarbeitet)  ]