parse.rs

20.5 kB · rust · 699 lines

1use crate::model::{Result, SelfKind};2use quote::ToTokens;3use std::collections::BTreeMap;4use std::path::Path;5use syn::parse::{Parse, ParseStream};6use syn::punctuated::Punctuated;7use syn::spanned::Spanned;8use syn::{braced, Attribute, Ident, LitStr, Token};910pub type Files = BTreeMap<String, String>;1112pub struct Tree {13    pub modules: BTreeMap<String, Module>,14    pub macro_body_fns: usize,15}1617pub struct Module {18    pub file: String,19    pub public: bool,20    pub docs: Vec<String>,21    pub children: Vec<String>,22    pub items: Vec<Item>,23    pub uses: Vec<Use>,24}2526pub struct Use {27    pub public: bool,28    pub path: Vec<String>,29    pub name: String,30    pub glob: bool,31}3233pub enum Item {34    Fn(Fn),35    Struct(Struct),36    Enum(Enum),37    Alias(Alias),38    Const(Const),39    Impl(Impl),40    Trait(Trait),41}4243pub struct Trait {44    pub name: String,45    pub fns: Vec<Fn>,46}4748pub struct Fn {49    pub name: String,50    pub line: usize,51    pub docs: Vec<String>,52    pub constant: bool,53    pub generated: bool,54    pub type_params: Vec<(String, String)>,55    pub const_params: Vec<String>,56    pub where_text: String,57    pub self_kind: Option<SelfKind>,58    pub params: Vec<(String, syn::Type)>,59    pub ret: Option<syn::Type>,60}6162pub struct Struct {63    pub name: String,64    pub line: usize,65    pub docs: Vec<String>,66    pub derives: Vec<String>,67    pub serde: Vec<String>,68    pub const_generic: bool,69    pub fields: Vec<Field>,70}7172pub struct Field {73    pub name: String,74    pub public: bool,75    pub docs: Vec<String>,76    pub serde: Vec<String>,77    pub serde_skip: bool,78    pub ty: syn::Type,79}8081pub struct Enum {82    pub name: String,83    pub line: usize,84    pub docs: Vec<String>,85    pub derives: Vec<String>,86    pub serde: Vec<String>,87    pub named: bool,88    pub variants: Vec<Variant>,89}9091pub struct Variant {92    pub name: String,93    pub word: Option<String>,94    pub docs: Vec<String>,95    pub serde: Vec<String>,96    pub fields: Vec<Field>,97}9899pub struct Alias {100    pub name: String,101    pub line: usize,102    pub docs: Vec<String>,103    pub generic: bool,104    pub ty: syn::Type,105}106107pub struct Const {108    pub name: String,109    pub line: usize,110    pub docs: Vec<String>,111    pub ty: syn::Type,112}113114pub struct Impl {115    pub line: usize,116    pub trait_path: Option<Vec<String>>,117    pub self_ty: syn::Type,118    pub const_params: Vec<String>,119    pub type_params: Vec<String>,120    pub fns: Vec<Fn>,121}122123pub fn is_pub_fn_line(line: &str) -> bool {124    line.trim_start().starts_with("pub fn ")125}126127pub fn load(src: &Path) -> Result<Files> {128    let mut files = Files::new();129    read_dir(src, src, &mut files)?;130    Ok(files)131}132133fn read_dir(src: &Path, dir: &Path, files: &mut Files) -> Result<()> {134    let mut entries: Vec<_> = std::fs::read_dir(dir)135        .map_err(|e| format!("{}: {e}", dir.display()))?136        .filter_map(|e| e.ok())137        .map(|e| e.path())138        .collect();139    entries.sort();140    for path in entries {141        let rel = path.strip_prefix(src).map_err(|e| e.to_string())?;142        if path.is_dir() {143            if rel == Path::new("bin") {144                continue;145            }146            read_dir(src, &path, files)?;147        } else if path.extension().is_some_and(|x| x == "rs") {148            let text =149                std::fs::read_to_string(&path).map_err(|e| format!("{}: {e}", path.display()))?;150            files.insert(rel.to_string_lossy().replace('\\', "/"), text);151        }152    }153    Ok(())154}155156pub fn parse(files: &Files) -> Result<Tree> {157    let mut walker = Walker {158        files,159        tree: Tree {160            modules: BTreeMap::new(),161            macro_body_fns: 0,162        },163    };164    walker.file(At {165        path: vec![],166        file: "lib.rs".into(),167        dir: String::new(),168        public: true,169        docs: vec![],170    })?;171    Ok(walker.tree)172}173174struct Walker<'a> {175    files: &'a Files,176    tree: Tree,177}178179struct At {180    path: Vec<String>,181    file: String,182    dir: String,183    public: bool,184    docs: Vec<String>,185}186187impl Walker<'_> {188    fn file(&mut self, mut at: At) -> Result<()> {189        let file = at.file.clone();190        let text = self191            .files192            .get(&file)193            .ok_or_else(|| format!("missing file {file}"))?;194        let parsed =195            syn::parse_file(text).map_err(|e| format!("{file}:{}: {e}", e.span().start().line))?;196        at.docs.extend(docs_of(&parsed.attrs));197        self.module(at, text, &parsed.items)198    }199200    fn module(&mut self, at: At, text: &str, items: &[syn::Item]) -> Result<()> {201        let At {202            path,203            file,204            dir,205            public,206            docs,207        } = at;208        let key = path.join("::");209        let mut module = Module {210            file: file.clone(),211            public,212            docs,213            children: vec![],214            items: vec![],215            uses: vec![],216        };217        let mut pending = vec![];218        for item in items {219            match item {220                syn::Item::Mod(m) if !is_test(&m.attrs) => {221                    let name = m.ident.to_string();222                    module.children.push(name.clone());223                    pending.push(m);224                }225                syn::Item::Use(u) if !is_test(&u.attrs) => {226                    let public = is_public(&u.vis);227                    flatten_use(&u.tree, vec![], public, &mut module.uses);228                }229                syn::Item::Fn(f) if !is_test(&f.attrs) && is_public(&f.vis) => {230                    module231                        .items232                        .push(Item::Fn(function(&f.sig, docs_of(&f.attrs), false)));233                }234                syn::Item::Struct(s) if !is_test(&s.attrs) && is_public(&s.vis) => {235                    module.items.push(Item::Struct(structure(s)));236                }237                syn::Item::Enum(e) if !is_test(&e.attrs) && is_public(&e.vis) => {238                    module.items.push(Item::Enum(enumeration(e)));239                }240                syn::Item::Type(t) if !is_test(&t.attrs) && is_public(&t.vis) => {241                    module.items.push(Item::Alias(Alias {242                        name: t.ident.to_string(),243                        line: t.ident.span().start().line,244                        docs: docs_of(&t.attrs),245                        generic: !t.generics.params.is_empty(),246                        ty: (*t.ty).clone(),247                    }));248                }249                syn::Item::Const(c) if !is_test(&c.attrs) && is_public(&c.vis) => {250                    module.items.push(Item::Const(Const {251                        name: c.ident.to_string(),252                        line: c.ident.span().start().line,253                        docs: docs_of(&c.attrs),254                        ty: (*c.ty).clone(),255                    }));256                }257                syn::Item::Impl(i) if !is_test(&i.attrs) => {258                    module.items.push(Item::Impl(implementation(i)));259                }260                syn::Item::Trait(t) if !is_test(&t.attrs) && is_public(&t.vis) => {261                    let fns = t262                        .items263                        .iter()264                        .filter_map(|item| match item {265                            syn::TraitItem::Fn(f) => {266                                Some(function(&f.sig, docs_of(&f.attrs), false))267                            }268                            _ => None,269                        })270                        .collect();271                    module.items.push(Item::Trait(Trait {272                        name: t.ident.to_string(),273                        fns,274                    }));275                }276                syn::Item::Macro(m) if m.mac.path.is_ident("named_enum") => {277                    let line = m.mac.path.span().start().line;278                    let named: NamedEnum = syn::parse2(m.mac.tokens.clone())279                        .map_err(|e| format!("{file}:{line}: named_enum! {e}"))?;280                    let (e, i) = named.items(line);281                    module.items.push(Item::Enum(e));282                    module.items.push(Item::Impl(i));283                }284                syn::Item::Macro(m) if m.mac.path.is_ident("macro_rules") => {285                    let start = m.span().start().line;286                    let end = m.span().end().line;287                    self.tree.macro_body_fns += text288                        .lines()289                        .skip(start - 1)290                        .take(end + 1 - start)291                        .filter(|l| is_pub_fn_line(l))292                        .count();293                }294                _ => {}295            }296        }297        self.tree.modules.insert(key, module);298        for m in pending {299            let name = m.ident.to_string();300            let mut child = path.clone();301            child.push(name.clone());302            let dir = if dir.is_empty() {303                name.clone()304            } else {305                format!("{dir}/{name}")306            };307            let public = is_public(&m.vis);308            let docs = docs_of(&m.attrs);309            match &m.content {310                Some((_, items)) => {311                    let at = At {312                        path: child,313                        file: file.clone(),314                        dir,315                        public,316                        docs,317                    };318                    self.module(at, text, items)?;319                }320                None => {321                    let flat = format!("{dir}.rs");322                    let nested = format!("{dir}/mod.rs");323                    let file = if self.files.contains_key(&flat) {324                        flat325                    } else {326                        nested327                    };328                    self.file(At {329                        path: child,330                        file,331                        dir,332                        public,333                        docs,334                    })?;335                }336            }337        }338        Ok(())339    }340}341342fn flatten_use(tree: &syn::UseTree, prefix: Vec<String>, public: bool, out: &mut Vec<Use>) {343    match tree {344        syn::UseTree::Path(p) => {345            let mut next = prefix;346            next.push(p.ident.to_string());347            flatten_use(&p.tree, next, public, out);348        }349        syn::UseTree::Name(n) => {350            let mut path = prefix;351            let name = n.ident.to_string();352            if name == "self" {353                let name = path.last().cloned().unwrap_or_default();354                out.push(Use {355                    public,356                    path,357                    name,358                    glob: false,359                });360            } else {361                path.push(name.clone());362                out.push(Use {363                    public,364                    path,365                    name,366                    glob: false,367                });368            }369        }370        syn::UseTree::Rename(r) => {371            let mut path = prefix;372            path.push(r.ident.to_string());373            out.push(Use {374                public,375                path,376                name: r.rename.to_string(),377                glob: false,378            });379        }380        syn::UseTree::Glob(_) => out.push(Use {381            public,382            path: prefix,383            name: "*".into(),384            glob: true,385        }),386        syn::UseTree::Group(g) => {387            for item in &g.items {388                flatten_use(item, prefix.clone(), public, out);389            }390        }391    }392}393394fn function(sig: &syn::Signature, docs: Vec<String>, generated: bool) -> Fn {395    let mut type_params = vec![];396    let mut const_params = vec![];397    for param in &sig.generics.params {398        match param {399            syn::GenericParam::Type(t) => {400                type_params.push((t.ident.to_string(), t.bounds.to_token_stream().to_string()))401            }402            syn::GenericParam::Const(c) => const_params.push(c.ident.to_string()),403            syn::GenericParam::Lifetime(_) => {}404        }405    }406    let mut self_kind = None;407    let mut params = vec![];408    for (i, arg) in sig.inputs.iter().enumerate() {409        match arg {410            syn::FnArg::Receiver(r) => {411                self_kind = Some(match (&r.reference, &r.mutability) {412                    (None, _) => SelfKind::Value,413                    (Some(_), None) => SelfKind::Ref,414                    (Some(_), Some(_)) => SelfKind::Mut,415                });416            }417            syn::FnArg::Typed(p) => {418                let name = match &*p.pat {419                    syn::Pat::Ident(id) => id.ident.to_string(),420                    _ => format!("arg{i}"),421                };422                params.push((name, (*p.ty).clone()));423            }424        }425    }426    Fn {427        name: sig.ident.to_string(),428        line: sig.ident.span().start().line,429        docs,430        constant: sig.constness.is_some(),431        generated,432        type_params,433        const_params,434        where_text: sig435            .generics436            .where_clause437            .as_ref()438            .map(|w| w.to_token_stream().to_string())439            .unwrap_or_default(),440        self_kind,441        params,442        ret: match &sig.output {443            syn::ReturnType::Default => None,444            syn::ReturnType::Type(_, t) => Some((**t).clone()),445        },446    }447}448449fn fields_of(fields: &syn::Fields) -> Vec<Field> {450    fields451        .iter()452        .enumerate()453        .map(|(i, f)| Field {454            name: f455                .ident456                .as_ref()457                .map_or_else(|| i.to_string(), |id| id.to_string()),458            public: is_public(&f.vis),459            docs: docs_of(&f.attrs),460            serde: serde_of(&f.attrs),461            serde_skip: serde_skip(&f.attrs),462            ty: f.ty.clone(),463        })464        .collect()465}466467fn structure(s: &syn::ItemStruct) -> Struct {468    Struct {469        name: s.ident.to_string(),470        line: s.ident.span().start().line,471        docs: docs_of(&s.attrs),472        derives: derives_of(&s.attrs),473        serde: serde_of(&s.attrs),474        const_generic: s475            .generics476            .params477            .iter()478            .any(|p| matches!(p, syn::GenericParam::Const(_))),479        fields: fields_of(&s.fields),480    }481}482483fn enumeration(e: &syn::ItemEnum) -> Enum {484    Enum {485        name: e.ident.to_string(),486        line: e.ident.span().start().line,487        docs: docs_of(&e.attrs),488        derives: derives_of(&e.attrs),489        serde: serde_of(&e.attrs),490        named: false,491        variants: e492            .variants493            .iter()494            .map(|v| Variant {495                name: v.ident.to_string(),496                word: None,497                docs: docs_of(&v.attrs),498                serde: serde_of(&v.attrs),499                fields: fields_of(&v.fields),500            })501            .collect(),502    }503}504505fn implementation(i: &syn::ItemImpl) -> Impl {506    let mut const_params = vec![];507    let mut type_params = vec![];508    for param in &i.generics.params {509        match param {510            syn::GenericParam::Const(c) => const_params.push(c.ident.to_string()),511            syn::GenericParam::Type(t) => type_params.push(t.ident.to_string()),512            syn::GenericParam::Lifetime(_) => {}513        }514    }515    let fns = i516        .items517        .iter()518        .filter_map(|item| match item {519            syn::ImplItem::Fn(f) if is_public(&f.vis) && !is_test(&f.attrs) => {520                Some(function(&f.sig, docs_of(&f.attrs), false))521            }522            _ => None,523        })524        .collect();525    let trait_path = i526        .trait_527        .as_ref()528        .map(|(_, path, _)| path.segments.iter().map(|s| s.ident.to_string()).collect());529    Impl {530        line: i.impl_token.span.start().line,531        trait_path,532        self_ty: (*i.self_ty).clone(),533        const_params,534        type_params,535        fns,536    }537}538539struct NamedEnum {540    attrs: Vec<Attribute>,541    name: Ident,542    variants: Vec<(Vec<Attribute>, Ident, String)>,543}544545impl Parse for NamedEnum {546    fn parse(input: ParseStream) -> syn::Result<Self> {547        let attrs = input.call(Attribute::parse_outer)?;548        input.parse::<Token![pub]>()?;549        input.parse::<Token![enum]>()?;550        let name: Ident = input.parse()?;551        let content;552        braced!(content in input);553        let mut variants = vec![];554        while !content.is_empty() {555            let vattrs = content.call(Attribute::parse_outer)?;556            let variant: Ident = content.parse()?;557            content.parse::<Token![=>]>()?;558            let word: LitStr = content.parse()?;559            variants.push((vattrs, variant, word.value()));560            if content.is_empty() {561                break;562            }563            content.parse::<Token![,]>()?;564        }565        Ok(NamedEnum {566            attrs,567            name,568            variants,569        })570    }571}572573impl NamedEnum {574    fn items(&self, line: usize) -> (Enum, Impl) {575        let name = self.name.to_string();576        let count = self.variants.len();577        let enumeration = Enum {578            name: name.clone(),579            line,580            docs: docs_of(&self.attrs),581            derives: derives_of(&self.attrs),582            serde: serde_of(&self.attrs),583            named: true,584            variants: self585                .variants586                .iter()587                .map(|(attrs, ident, word)| Variant {588                    name: ident.to_string(),589                    word: Some(word.clone()),590                    docs: docs_of(attrs),591                    serde: serde_of(attrs),592                    fields: vec![],593                })594                .collect(),595        };596        let all: syn::Signature = syn::parse_str(&format!("const fn all() -> [{name}; {count}]"))597            .expect("the all signature parses");598        let word: syn::Signature =599            syn::parse_str("fn name(&self) -> &'static str").expect("the name signature parses");600        let mut all = function(601            &all,602            vec![format!("Returns every {name} in canonical order.")],603            true,604        );605        let mut word = function(606            &word,607            vec![format!("Returns the {name}'s display name.")],608            true,609        );610        all.line = line;611        word.line = line;612        let self_ty: syn::Type = syn::parse_str(&name).expect("the enum name parses");613        (614            enumeration,615            Impl {616                line,617                trait_path: None,618                self_ty,619                const_params: vec![],620                type_params: vec![],621                fns: vec![all, word],622            },623        )624    }625}626627fn is_public(vis: &syn::Visibility) -> bool {628    matches!(vis, syn::Visibility::Public(_))629}630631fn is_test(attrs: &[Attribute]) -> bool {632    attrs633        .iter()634        .any(|a| a.path().is_ident("cfg") && a.meta.to_token_stream().to_string().contains("test"))635}636637pub fn docs_of(attrs: &[Attribute]) -> Vec<String> {638    attrs639        .iter()640        .filter(|a| a.path().is_ident("doc"))641        .filter_map(|a| match &a.meta {642            syn::Meta::NameValue(nv) => match &nv.value {643                syn::Expr::Lit(syn::ExprLit {644                    lit: syn::Lit::Str(s),645                    ..646                }) => Some(s.value()),647                _ => None,648            },649            _ => None,650        })651        .map(|s| s.strip_prefix(' ').unwrap_or(&s).to_string())652        .collect()653}654655fn derives_of(attrs: &[Attribute]) -> Vec<String> {656    let mut out = vec![];657    for attr in attrs.iter().filter(|a| a.path().is_ident("derive")) {658        if let Ok(paths) =659            attr.parse_args_with(Punctuated::<syn::Path, Token![,]>::parse_terminated)660        {661            out.extend(662                paths663                    .iter()664                    .filter_map(|p| p.segments.last().map(|s| s.ident.to_string())),665            );666        }667    }668    out669}670671fn serde_skip(attrs: &[Attribute]) -> bool {672    let mut skip = false;673    for list in attrs674        .iter()675        .filter(|a| a.path().is_ident("serde"))676        .filter_map(|a| a.meta.require_list().ok())677    {678        let mut key = true;679        for token in list.tokens.clone() {680            match token {681                proc_macro2::TokenTree::Punct(p) if p.as_char() == ',' => key = true,682                proc_macro2::TokenTree::Ident(id) if key => {683                    skip |= ["skip", "skip_serializing", "skip_deserializing"].contains(&id.to_string().as_str());684                    key = false;685                }686                _ => key = false,687            }688        }689    }690    skip691}692693fn serde_of(attrs: &[Attribute]) -> Vec<String> {694    attrs695        .iter()696        .filter(|a| a.path().is_ident("serde"))697        .filter_map(|a| a.meta.require_list().ok().map(|l| l.tokens.to_string()))698        .collect()699}