use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, TokenStreamExt};
use std::fmt;
use syn::{
braced,
parse::{Parse, ParseStream},
spanned::Spanned,
token::Brace,
Attribute, FnArg, ForeignItemFn, Ident, ItemUse, Path, PathArguments, PathSegment, Token,
Visibility,
};
use crate::{
documentation::{generate_extern_crate_fn_docs, generate_module_docs},
helpers::{visit_matching_attrs_parsed_mut, AttributeAction, CRATE_NAME},
pre_attr::PreAttr,
};
pub(crate) use impl_block::{impl_block_stub_name, ImplBlock};
mod impl_block;
pub(crate) struct ExternCrateAttr {
path: Path,
}
impl fmt::Display for ExternCrateAttr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "#[extern_crate(")?;
if self.path.leading_colon.is_some() {
write!(f, "::")?;
}
for segment in &self.path.segments {
write!(f, "{}", segment.ident)?;
}
write!(f, ")]")
}
}
impl Parse for ExternCrateAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(ExternCrateAttr {
path: input.call(Path::parse_mod_style)?,
})
}
}
pub(crate) struct Module {
attrs: Vec<Attribute>,
visibility: Visibility,
mod_token: Token![mod],
ident: Ident,
braces: Brace,
impl_blocks: Vec<ImplBlock>,
imports: Vec<ItemUse>,
functions: Vec<ForeignItemFn>,
modules: Vec<Module>,
}
impl fmt::Display for Module {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.original_token_stream())
}
}
impl Spanned for Module {
fn span(&self) -> Span {
self.visibility
.span()
.join(self.braces.span)
.unwrap_or(self.braces.span)
}
}
impl Parse for Module {
fn parse(input: ParseStream) -> syn::Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let visibility = input.parse()?;
let mod_token = input.parse()?;
let ident = input.parse()?;
let content;
let braces = braced!(content in input);
let mut impl_blocks = Vec::new();
let mut imports = Vec::new();
let mut functions = Vec::new();
let mut modules = Vec::new();
while !content.is_empty() {
if content.peek(Token![impl]) {
impl_blocks.push(content.parse()?);
} else if <ItemUse as Parse>::parse(&content.fork()).is_ok() {
imports.push(content.parse()?);
} else if <ForeignItemFn as Parse>::parse(&content.fork()).is_ok() {
functions.push(content.parse()?);
} else {
modules.push(content.parse().map_err(|err| {
syn::Error::new(
err.span(),
"expected a module, a function signature, an impl block or a use statement",
)
})?);
}
}
Ok(Module {
attrs,
visibility,
mod_token,
ident,
braces,
impl_blocks,
imports,
functions,
modules,
})
}
}
impl Module {
pub(crate) fn render(&self, attr: ExternCrateAttr) -> TokenStream {
let mut tokens = TokenStream::new();
self.render_inner(attr.path, &mut tokens, None, &self.ident);
tokens
}
fn render_inner(
&self,
mut path: Path,
tokens: &mut TokenStream,
visibility: Option<&TokenStream>,
top_level_module: &Ident,
) {
if visibility.is_some() {
path.segments.push(PathSegment {
ident: self.ident.clone(),
arguments: PathArguments::None,
});
}
let mut attrs = self.attrs.clone();
let mut render_docs = true;
visit_matching_attrs_parsed_mut(&mut attrs, "pre", |attr| match attr.content() {
PreAttr::NoDoc(_) => {
render_docs = false;
AttributeAction::Remove
}
_ => AttributeAction::Keep,
});
if render_docs {
let docs = generate_module_docs(self, &path);
tokens.append_all(quote! { #docs });
}
tokens.append_all(attrs);
let visibility = if let Some(visibility) = visibility {
tokens.append_all(quote! { #visibility });
visibility.clone()
} else {
let local_vis = &self.visibility;
tokens.append_all(quote! { #local_vis });
if let Visibility::Public(pub_keyword) = local_vis {
quote! { #pub_keyword }
} else {
let span = match local_vis {
Visibility::Inherited => self.mod_token.span(),
_ => local_vis.span(),
};
quote_spanned! { span=> pub(crate) }
}
};
let mod_token = self.mod_token;
tokens.append_all(quote! { #mod_token });
tokens.append(self.ident.clone());
let mut brace_content = TokenStream::new();
let crate_name = Ident::new(&CRATE_NAME, Span::call_site());
brace_content.append_all(quote! {
#[allow(unused_imports)]
#[doc(no_inline)]
#visibility use #path::*;
#[allow(unused_imports)]
use #crate_name::pre;
});
for impl_block in &self.impl_blocks {
impl_block.render(&mut brace_content, &path, &visibility, top_level_module);
}
for import in &self.imports {
brace_content.append_all(quote! { #import });
}
for function in &self.functions {
render_function(function, &mut brace_content, &path, &visibility);
}
for module in &self.modules {
module.render_inner(
path.clone(),
&mut brace_content,
Some(&visibility),
top_level_module,
);
}
tokens.append_all(quote_spanned! { self.braces.span=> { #brace_content } });
}
fn original_token_stream(&self) -> TokenStream {
let mut stream = TokenStream::new();
stream.append_all(&self.attrs);
let vis = &self.visibility;
stream.append_all(quote! { #vis });
stream.append_all(quote! { mod });
stream.append(self.ident.clone());
let mut content = TokenStream::new();
content.append_all(
self.impl_blocks
.iter()
.map(|impl_block| impl_block.original_token_stream()),
);
content.append_all(&self.imports);
content.append_all(&self.functions);
content.append_all(self.modules.iter().map(|m| m.original_token_stream()));
stream.append_all(quote! { { #content } });
stream
}
}
fn render_function(
function: &ForeignItemFn,
tokens: &mut TokenStream,
path: &Path,
visibility: &TokenStream,
) {
tokens.append_all(&function.attrs);
let doc_header = generate_extern_crate_fn_docs(path, &function.sig, function.span());
tokens.append_all(quote! { #doc_header });
tokens.append_all(quote_spanned! { function.span()=> #[inline(always)] });
tokens.append_all(visibility.clone().into_iter().map(|mut token| {
token.set_span(function.span());
token
}));
let signature = &function.sig;
tokens.append_all(quote! { #signature });
let mut path = path.clone();
path.segments.push(PathSegment {
ident: function.sig.ident.clone(),
arguments: PathArguments::None,
});
for punct in path
.segments
.pairs_mut()
.map(|p| p.into_tuple().1)
.flatten()
{
punct.spans = [function.span(); 2];
}
let mut args_list = TokenStream::new();
args_list.append_separated(
function.sig.inputs.iter().map(|arg| match arg {
FnArg::Receiver(_) => unreachable!("receiver is not valid in a function argument list"),
FnArg::Typed(pat) => &pat.pat,
}),
quote_spanned! { function.span()=> , },
);
tokens.append_all(quote_spanned! { function.span()=> { #path(#args_list) } });
}