internal 0.1.4

Internal fields in Rust.
Documentation
#![doc = include_str!("../README.md")]
#![allow(clippy::tabs_in_doc_comments)]

extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote};
use syn::{Visibility, token::Pub, ImplItem, Item, Attribute, AttrStyle, parse_quote, ForeignItem};

fn internalize(public_item: Item, private_item: Item) -> TokenStream2 {
	quote! {
		#[cfg(not(feature = "internal"))]
		#private_item
		#[cfg(feature = "internal")]
		#public_item
	}
}

fn is_fully_public(vis: &Visibility) -> bool {
	match vis {
		Visibility::Public(_) => true,
		_ => false,
	}
}

fn process_item(item: Item) -> Item {
	let pub_vis = Visibility::Public(Pub { span: Span::call_site() });

	match item.clone() {
		Item::Const(mut public) => {
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::Const(public);
		}
		Item::Enum(mut public) => {
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::Enum(public);
		}
		Item::ExternCrate(mut public) => {
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::ExternCrate(public);
		}
		Item::Fn(mut public) => {
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::Fn(public);
		}
		Item::ForeignMod(mut public) => {
			for mut item in &mut public.items {
				match &mut item {
					ForeignItem::Fn(item) => {
						if !is_fully_public(&item.vis) {
							add_internal_doc_comment(&mut item.attrs);
						}
						item.vis = pub_vis.clone();
					}
					ForeignItem::Static(item) => {
						if !is_fully_public(&item.vis) {
							add_internal_doc_comment(&mut item.attrs);
						}
						item.vis = pub_vis.clone();
					}
					ForeignItem::Type(item) => {
						if !is_fully_public(&item.vis) {
							add_internal_doc_comment(&mut item.attrs);
						}
						item.vis = pub_vis.clone();
					}
					ForeignItem::Macro(_) => {}
					ForeignItem::Verbatim(_) => {}
					_ => {}
				}
			}
		}
		Item::Impl(mut public) => {
			for mut item in &mut public.items {
				match &mut item {
					ImplItem::Const(item) => {
						if !is_fully_public(&item.vis) {
							add_internal_doc_comment(&mut item.attrs);
						}
						item.vis = pub_vis.clone();
					}
					ImplItem::Fn(item) => {
						if !is_fully_public(&item.vis) {
							add_internal_doc_comment(&mut item.attrs);
						}
						item.vis = pub_vis.clone();
					}
					ImplItem::Type(item) => {
						if !is_fully_public(&item.vis) {
							add_internal_doc_comment(&mut item.attrs);
						}
						item.vis = pub_vis.clone();
					}
					ImplItem::Macro(_) => {}
					ImplItem::Verbatim(_) => {}
					_ => {}
				}
			}
			return Item::Impl(public);
		}
		Item::Macro(_) => {}
		Item::Mod(mut public) => {
			if let Some((_, ref mut items)) = public.content {
				for item in items {
					*item = process_item(item.clone())
				}
			}
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::Mod(public);
		}
		Item::Static(mut public) => {
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::Static(public);
		}
		Item::Struct(mut public) => {
			for field in &mut public.fields {
				if field.vis == Visibility::Inherited {
					add_internal_doc_comment(&mut field.attrs);
				}
				field.vis = pub_vis.clone();
			}
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::Struct(public);
		}
		Item::Trait(mut public) => {
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::Trait(public);
		}
		Item::TraitAlias(mut public) => {
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::TraitAlias(public);
		}
		Item::Type(mut public) => {
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::Type(public);
		}
		Item::Union(mut public) => {
			for field in &mut public.fields.named {
				if field.vis == Visibility::Inherited {
					add_internal_doc_comment(&mut field.attrs);
				}
				field.vis = pub_vis.clone();
			}
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::Union(public);
		}
		Item::Use(mut public) => {
			if !is_fully_public(&public.vis) {
				add_internal_doc_comment(&mut public.attrs);
			}
			public.vis = pub_vis.clone();
			return Item::Use(public);
		}
		Item::Verbatim(_) => {}
		_ => {}
	};

	item
}

static INTERNAL_DOCS_WARNING: [&str; 2] = [
	"This item is internal and could change or be removed without warning.",
	"Be careful when relying on it."
];

fn add_internal_doc_comment(attrs: &mut Vec<Attribute>) {
	let was_empty = attrs.is_empty();
	let mut end_index = 0;
	for line in INTERNAL_DOCS_WARNING.iter().rev() {
		end_index += 2;
		let line = format!(" {}", line);
		attrs.insert(0, Attribute {
			pound_token: Default::default(),
			style: AttrStyle::Outer,
			bracket_token: Default::default(),
			meta: parse_quote! {
				doc = #line
			},
		});
		attrs.insert(1, Attribute {
			pound_token: Default::default(),
			style: AttrStyle::Outer,
			bracket_token: Default::default(),
			meta: parse_quote! {
				doc = ""
			},
		});
	}
	if !was_empty {
		attrs.insert(end_index, Attribute {
			pound_token: Default::default(),
			style: AttrStyle::Outer,
			bracket_token: Default::default(),
			meta: parse_quote! {
				doc = " ---"
			},
		});
		attrs.insert(end_index + 1, Attribute {
			pound_token: Default::default(),
			style: AttrStyle::Outer,
			bracket_token: Default::default(),
			meta: parse_quote! {
				doc = ""
			},
		});
	} else {
		attrs.pop();
	}
}

#[proc_macro_attribute]
pub fn internal(_attr: TokenStream, input: TokenStream) -> TokenStream {
	let item = syn::parse2::<Item>(TokenStream2::from(input));

	if let Ok(item) = item {
		TokenStream::from(
			internalize(
				process_item(item.clone()),
				item
			)
		)
	} else {
		panic!("`#[internal]` wasn't called on an item");
	}
}