use convert_case::Casing;
use proc_macro2::Span;
use quote::quote;
use syn::{
parse::{Parse, ParseStream},
parse_quote, Attribute, Error, Ident, LitStr, Path, Result, Token,
};
use crate::{
attrs::{FlagOption, ValueOption},
class::{ClassConfig, ClassOption},
common::{crate_ident, kw, Case},
function::{FunctionConfig, FunctionOption},
};
#[derive(Debug, Default)]
pub(crate) struct ModuleConfig {
pub crate_: Option<String>,
pub prefix: Option<String>,
pub rename: Option<String>,
pub rename_vars: Option<Case>,
pub rename_types: Option<Case>,
}
impl ModuleConfig {
pub fn apply(&mut self, option: &ModuleOption) {
match option {
ModuleOption::Crate(ref x) => {
self.crate_ = Some(x.value.value());
}
ModuleOption::RenameVars(ref x) => {
self.rename_vars = Some(x.value);
}
ModuleOption::RenameTypes(ref x) => {
self.rename_types = Some(x.value);
}
ModuleOption::Rename(ref x) => {
self.rename = Some(x.value.value());
}
ModuleOption::Prefix(ref x) => {
self.prefix = Some(x.value.value());
}
}
}
pub fn crate_name(&self) -> Result<String> {
self.crate_.clone().map(Ok).unwrap_or_else(crate_ident)
}
pub fn carry_name(&self, name: &Ident) -> Ident {
Ident::new(
&format!("{}{}", self.prefix.as_deref().unwrap_or("js_"), name),
name.span(),
)
}
}
pub(crate) enum ModuleOption {
Prefix(ValueOption<kw::prefix, LitStr>),
Crate(ValueOption<Token![crate], LitStr>),
RenameVars(ValueOption<kw::rename_vars, Case>),
RenameTypes(ValueOption<kw::rename_types, Case>),
Rename(ValueOption<kw::rename, LitStr>),
}
impl Parse for ModuleOption {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(kw::prefix) {
input.parse().map(Self::Prefix)
} else if input.peek(Token![crate]) {
input.parse().map(Self::Crate)
} else if input.peek(kw::rename_vars) {
input.parse().map(Self::RenameVars)
} else if input.peek(kw::rename_types) {
input.parse().map(Self::RenameTypes)
} else if input.peek(kw::rename) {
input.parse().map(Self::Rename)
} else {
Err(syn::Error::new(input.span(), "invalid module attribute"))
}
}
}
#[derive(Default, Debug)]
pub(crate) struct ModuleFunctionConfig {
pub declare: bool,
pub evaluate: bool,
pub skip: bool,
pub function: FunctionConfig,
}
impl ModuleFunctionConfig {
pub fn apply(&mut self, option: &ModuleFunctionOption) {
match option {
ModuleFunctionOption::Declare(x) => {
self.declare = x.is_true();
}
ModuleFunctionOption::Evaluate(x) => {
self.evaluate = x.is_true();
}
ModuleFunctionOption::Function(x) => {
self.function.apply(x);
}
ModuleFunctionOption::Skip(x) => {
self.skip = x.is_true();
}
}
}
pub fn reexpand(&self) -> Option<Attribute> {
let mut attrs = Vec::new();
if let Some(x) = self.function.crate_.as_deref() {
attrs.push(quote!(crate = #x));
}
if let Some(x) = self.function.prefix.as_deref() {
attrs.push(quote!(prefix = #x));
}
if let Some(x) = self.function.rename.as_deref() {
attrs.push(quote!(rename = #x));
}
if attrs.is_empty() {
None
} else {
Some(parse_quote! {
#[qjs(#(#attrs),*)]
})
}
}
pub fn validate(&self, span: Span) -> Result<()> {
if self.skip && self.evaluate {
return Err(Error::new(span, "Can't skip the module evaluate function"));
}
if self.skip && self.declare {
return Err(Error::new(span, "Can't skip the module declare function"));
}
if self.declare && self.evaluate {
return Err(Error::new(
span,
"A function can be either declare or evaluate but not both.",
));
}
Ok(())
}
}
pub(crate) enum ModuleFunctionOption {
Declare(FlagOption<kw::declare>),
Evaluate(FlagOption<kw::evaluate>),
Skip(FlagOption<kw::skip>),
Function(FunctionOption),
}
impl Parse for ModuleFunctionOption {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(kw::declare) {
input.parse().map(Self::Declare)
} else if input.peek(kw::evaluate) {
input.parse().map(Self::Evaluate)
} else if input.peek(kw::skip) {
input.parse().map(Self::Skip)
} else {
input.parse().map(Self::Function)
}
}
}
#[derive(Default, Debug)]
pub(crate) struct ModuleTypeConfig {
pub skip: bool,
pub class: ClassConfig,
pub class_macro_name: Option<Path>,
}
impl ModuleTypeConfig {
pub fn apply(&mut self, option: &ModuleTypeOption) {
match option {
ModuleTypeOption::Skip(x) => {
self.skip = x.is_true();
}
ModuleTypeOption::Class(x) => self.class.apply(x),
}
}
pub fn reexpand(&self) -> Option<Attribute> {
let mut attrs = Vec::new();
if self.class.frozen {
attrs.push(quote!(frozen));
}
if let Some(x) = self.class.crate_.as_ref() {
attrs.push(quote!(crate = #x));
}
if let Some(x) = self.class.rename.as_ref() {
attrs.push(quote!(rename = #x));
}
if let Some(x) = self.class.rename_all {
attrs.push(quote!(rename = #x));
}
if attrs.is_empty() {
None
} else if let Some(x) = self.class_macro_name.as_ref() {
Some(parse_quote!(#![#x( #(#attrs,)* )]))
} else {
Some(parse_quote!(#![qjs( #(#attrs,)* )]))
}
}
}
pub(crate) enum ModuleTypeOption {
Skip(FlagOption<kw::skip>),
Class(ClassOption),
}
impl Parse for ModuleTypeOption {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(kw::skip) {
input.parse().map(Self::Skip)
} else {
input.parse().map(Self::Class)
}
}
}
#[derive(Default, Debug)]
pub(crate) struct ModuleItemConfig {
pub skip: bool,
pub rename: Option<String>,
}
impl ModuleItemConfig {
pub fn apply(&mut self, option: &ModuleItemOption) {
match option {
ModuleItemOption::Skip(x) => {
self.skip = x.is_true();
}
ModuleItemOption::Rename(x) => {
self.rename = Some(x.value.value());
}
}
}
pub fn js_name(&self, name: &Ident, case: Option<Case>) -> String {
if let Some(x) = self.rename.clone() {
return x;
}
let name = name.to_string();
if let Some(case) = case {
return name.to_case(case.to_convert_case());
}
name
}
}
pub(crate) enum ModuleItemOption {
Skip(FlagOption<kw::skip>),
Rename(ValueOption<kw::rename, LitStr>),
}
impl Parse for ModuleItemOption {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(kw::skip) {
input.parse().map(Self::Skip)
} else if input.peek(kw::rename) {
input.parse().map(Self::Rename)
} else {
Err(syn::Error::new(
input.span(),
"invalid module item attribute",
))
}
}
}