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}