use convert_case::Case as ConvertCase;
use proc_macro2::Span;
use proc_macro_crate::FoundCrate;
use quote::{ToTokens, TokenStreamExt};
use syn::{
fold::Fold,
parse::{Parse, ParseStream},
Error, Generics, Lifetime, LifetimeParam, LitStr, Result, Type,
};
pub const GET_PREFIX: &str = "__impl_get_";
pub const SET_PREFIX: &str = "__impl_set_";
pub const BASE_PREFIX: &str = "js_";
pub const IMPL_PREFIX: &str = "__impl_";
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum Case {
Lower,
Upper,
Camel,
Pascal,
Snake,
ScreamingSnake,
}
impl Parse for Case {
fn parse(input: ParseStream) -> syn::Result<Self> {
let str_lit: LitStr = input.parse()?;
let value = str_lit.value();
match value.as_str() {
"lowercase" => Ok(Case::Lower),
"UPPERCASE" => Ok(Case::Upper),
"camelCase" => Ok(Case::Camel),
"PascalCase" => Ok(Case::Pascal),
"snake_case" => Ok(Case::Snake),
"SCREAMING_SNAKE" => Ok(Case::ScreamingSnake),
_ => Err(syn::Error::new(str_lit.span(), "Invalid casing, expected one of 'lowercase', 'UPPERCASE', 'camelCase','PascalCase','snake_case','SCREAMING_SNAKE'"))
}
}
}
impl ToTokens for Case {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
Case::Lower => tokens.append_all(["lowercase"]),
Case::Upper => tokens.append_all(["UPPERCASE"]),
Case::Camel => tokens.append_all(["camelCase"]),
Case::Pascal => tokens.append_all(["PascalCase"]),
Case::Snake => tokens.append_all(["snake_case"]),
Case::ScreamingSnake => tokens.append_all(["SCREAMING_SNAKE"]),
}
}
}
impl Case {
pub fn to_convert_case(self) -> ConvertCase<'static> {
match self {
Case::Lower => ConvertCase::Lower,
Case::Upper => ConvertCase::Upper,
Case::Camel => ConvertCase::Camel,
Case::Pascal => ConvertCase::Pascal,
Case::Snake => ConvertCase::Snake,
Case::ScreamingSnake => ConvertCase::Constant,
}
}
}
const JIT_PACKAGE: &str = "quickjs-jit";
const UPSTREAM_PACKAGE: &str = "rquickjs";
const JIT_DEFAULT_CRATE_IDENT: &str = "quickjs_jit";
const COMPATIBLE_LIB_IDENT: &str = "rquickjs";
fn top_level_package_candidates() -> [&'static str; 2] {
[JIT_PACKAGE, UPSTREAM_PACKAGE]
}
fn normalize_crate_name(name: &str) -> &str {
match name {
JIT_DEFAULT_CRATE_IDENT => COMPATIBLE_LIB_IDENT,
name => name,
}
}
pub(crate) fn crate_ident() -> Result<String> {
let mut last_error = None;
for package in top_level_package_candidates() {
match proc_macro_crate::crate_name(package) {
Ok(FoundCrate::Itself) => return Ok(COMPATIBLE_LIB_IDENT.to_owned()),
Ok(FoundCrate::Name(name)) => return Ok(normalize_crate_name(&name).to_owned()),
Err(error) => last_error = Some(error),
}
}
Err(Error::new(
Span::call_site(),
format_args!(
"could not find quickjs-jit or rquickjs package: {}",
last_error.expect("package candidates are non-empty")
),
))
}
pub fn add_js_lifetime(generics: &Generics) -> Generics {
let mut generics = generics.clone();
let has_js_lifetime = generics.lifetimes().any(|lt| lt.lifetime.ident == "js");
if !has_js_lifetime {
generics.params.insert(
0,
syn::GenericParam::Lifetime(LifetimeParam::new(Lifetime::new(
"'js",
Span::call_site(),
))),
);
}
generics
}
pub struct SelfReplacer<'a> {
pub ty: &'a Type,
}
impl<'a> SelfReplacer<'a> {
pub fn with(ty: &'a Type) -> Self {
Self { ty }
}
}
impl<'a> Fold for SelfReplacer<'a> {
fn fold_type(&mut self, i: Type) -> Type {
let Type::Path(x) = i else { return i };
if x.path.segments.len() != 1 {
return Type::Path(self.fold_type_path(x));
}
if x.path.segments.first().unwrap().ident == "Self" {
self.ty.clone()
} else {
Type::Path(self.fold_type_path(x))
}
}
}
pub(crate) mod kw {
syn::custom_keyword!(frozen);
syn::custom_keyword!(skip_trace);
syn::custom_keyword!(rename);
syn::custom_keyword!(rename_all);
syn::custom_keyword!(rename_vars);
syn::custom_keyword!(rename_types);
syn::custom_keyword!(get);
syn::custom_keyword!(set);
syn::custom_keyword!(constructor);
syn::custom_keyword!(skip);
syn::custom_keyword!(configurable);
syn::custom_keyword!(enumerable);
syn::custom_keyword!(writable);
syn::custom_keyword!(prop);
syn::custom_keyword!(prefix);
syn::custom_keyword!(declare);
syn::custom_keyword!(evaluate);
syn::custom_keyword!(exotic);
syn::custom_keyword!(delete);
syn::custom_keyword!(has);
syn::custom_keyword!(get_own_property);
syn::custom_keyword!(get_own_property_names);
}
#[cfg(test)]
mod tests {
#[test]
fn top_level_package_candidates_prefer_jit_distribution() {
assert_eq!(
super::top_level_package_candidates(),
["quickjs-jit", "rquickjs"]
);
}
#[test]
fn default_jit_package_name_maps_to_compatible_library_name() {
assert_eq!(super::normalize_crate_name("quickjs_jit"), "rquickjs");
assert_eq!(super::normalize_crate_name("js"), "js");
}
}