diff --git a/core/src/parser.rs b/core/src/parser.rs index b785efa0..272a027a 100644 --- a/core/src/parser.rs +++ b/core/src/parser.rs @@ -416,340 +416,3 @@ fn parse_type_alias(t: &ItemType) -> Result { generic_types, }) } - -// Helpers - -/// Parses any comment out of the given slice of attributes -fn parse_comment_attrs(attrs: &[Attribute]) -> Vec { - const DOC_ATTR: &str = "doc"; - attrs - .iter() - .map(Attribute::parse_meta) - .filter_map(Result::ok) - .filter_map(|attr| match attr { - Meta::NameValue(name_value) => { - if let Some(ident) = name_value.path.get_ident() { - if *ident == DOC_ATTR { - Some(name_value.lit) - } else { - None - } - } else { - None - } - } - _ => None, - }) - .filter_map(literal_as_string) - .map(|string| string.trim().into()) - .collect() -} - -/// Checks the given attrs for `#[typeshare]` -fn has_typeshare_annotation(attrs: &[syn::Attribute]) -> bool { - let typeshare_ident = Ident::new("typeshare", Span::call_site()); - for a in attrs { - if let Some(segment) = a.path.segments.iter().next() { - if segment.ident == typeshare_ident { - return true; - } - } - } - - false -} - -fn get_ident( - ident: Option<&proc_macro2::Ident>, - attrs: &[syn::Attribute], - rename_all: &Option, -) -> Id { - let original = ident.map_or("???".to_string(), |id| id.to_string().replace("r#", "")); - - let mut renamed = rename_all_to_case(original.clone(), rename_all); - - if let Some(s) = serde_rename(attrs) { - renamed = s; - } - - Id { original, renamed } -} - -fn rename_all_to_case(original: String, case: &Option) -> String { - match case { - None => original, - Some(value) => match value.as_str() { - "lowercase" => original.to_lowercase(), - "UPPERCASE" => original.to_uppercase(), - "PascalCase" => original.to_pascal_case(), - "camelCase" => original.to_camel_case(), - "snake_case" => original.to_snake_case(), - "SCREAMING_SNAKE_CASE" => original.to_screaming_snake_case(), - "kebab-case" => original.to_kebab_case(), - "SCREAMING-KEBAB-CASE" => original.to_screaming_kebab_case(), - _ => original, - }, - } -} - -fn literal_as_string(lit: syn::Lit) -> Option { - match lit { - syn::Lit::Str(str) => Some(str.value()), - _ => None, - } -} - -fn get_typeshare_name_value_meta_items<'a>( - attrs: &'a [syn::Attribute], - name: &'a str, -) -> impl Iterator + 'a { - attrs.iter().flat_map(move |attr| { - get_typeshare_meta_items(attr) - .iter() - .filter_map(|arg| match arg { - NestedMeta::Meta(Meta::NameValue(name_value)) => { - if let Some(ident) = name_value.path.get_ident() { - if *ident == name { - Some(name_value.lit.clone()) - } else { - None - } - } else { - None - } - } - _ => None, - }) - .collect::>() - }) -} - -fn get_serde_name_value_meta_items<'a>( - attrs: &'a [syn::Attribute], - name: &'a str, -) -> impl Iterator + 'a { - attrs.iter().flat_map(move |attr| { - get_serde_meta_items(attr) - .iter() - .filter_map(|arg| match arg { - NestedMeta::Meta(Meta::NameValue(name_value)) => { - if let Some(ident) = name_value.path.get_ident() { - if *ident == name { - Some(name_value.lit.clone()) - } else { - None - } - } else { - None - } - } - _ => None, - }) - .collect::>() - }) -} - -fn get_serialized_as_type(attrs: &[syn::Attribute]) -> Option { - get_typeshare_name_value_meta_items(attrs, "serialized_as") - .next() - .and_then(literal_as_string) -} - -fn get_field_type_override(attrs: &[syn::Attribute]) -> Option { - get_typeshare_name_value_meta_items(attrs, "serialized_as") - .next() - .and_then(literal_as_string) -} - -/// Checks the struct or enum for decorators like `#[typeshare(typescript(readonly)]` -/// Takes a slice of `syn::Attribute`, returns a `HashMap>`, where `language` is `SupportedLanguage` -/// and `decorator` is `FieldDecorator`. Field decorators are ordered in a `BTreeSet` for consistent code generation. -fn get_field_decorators( - attrs: &[Attribute], -) -> HashMap> { - let languages: HashSet = SupportedLanguage::all_languages().collect(); - - attrs - .iter() - .flat_map(get_typeshare_meta_items) - .flat_map(|meta| { - if let NestedMeta::Meta(Meta::List(list)) = meta { - Some(list) - } else { - None - } - }) - .flat_map(|list| match list.path.get_ident() { - Some(ident) if languages.contains(&ident.try_into().unwrap()) => { - Some((ident.try_into().unwrap(), list.nested)) - } - _ => None, - }) - .map(|(language, list)| { - ( - language, - list.into_iter().filter_map(|nested| match nested { - NestedMeta::Meta(Meta::Path(path)) if path.segments.len() == 1 => { - Some(FieldDecorator::Word(path.get_ident()?.to_string())) - } - NestedMeta::Meta(Meta::NameValue(name_value)) => { - Some(FieldDecorator::NameValue( - name_value.path.get_ident()?.to_string(), - literal_as_string(name_value.lit)?, - )) - } - // TODO: this should throw a visible error since it suggests a malformed - // attribute. - _ => None, - }), - ) - }) - .fold(HashMap::new(), |mut acc, (language, decorators)| { - acc.entry(language).or_default().extend(decorators); - acc - }) -} - -/// Checks the struct or enum for decorators like `#[typeshare(swift = "Codable, Equatable")]` -/// Takes a slice of `syn::Attribute`, returns a `HashMap>`, where `language` is `SupportedLanguage` and `decoration_words` is `String` -fn get_decorators(attrs: &[syn::Attribute]) -> HashMap> { - // The resulting HashMap, Key is the language, and the value is a vector of decorators words that will be put onto structures - let mut out: HashMap> = HashMap::new(); - - for value in get_typeshare_name_value_meta_items(attrs, "swift").filter_map(literal_as_string) { - let decorators: Vec = value.split(',').map(|s| s.trim().to_string()).collect(); - - // lastly, get the entry in the hashmap output and extend the value, or insert what we have already found - let decs = out.entry(SupportedLanguage::Swift).or_default(); - decs.extend(decorators); - // Sorting so all the added decorators will be after the normal ([`String`], `Codable`) in alphabetical order - decs.sort_unstable(); - decs.dedup(); //removing any duplicates just in case - } - - //return our hashmap mapping of language -> Vec - out -} - -fn get_tag_key(attrs: &[syn::Attribute]) -> Option { - get_serde_name_value_meta_items(attrs, "tag") - .next() - .and_then(literal_as_string) -} - -fn get_content_key(attrs: &[syn::Attribute]) -> Option { - get_serde_name_value_meta_items(attrs, "content") - .next() - .and_then(literal_as_string) -} - -fn serde_rename(attrs: &[syn::Attribute]) -> Option { - get_serde_name_value_meta_items(attrs, "rename") - .next() - .and_then(literal_as_string) -} - -fn serde_rename_all(attrs: &[syn::Attribute]) -> Option { - get_serde_name_value_meta_items(attrs, "rename_all") - .next() - .and_then(literal_as_string) -} - -fn serde_attr(attrs: &[syn::Attribute], ident: &Ident) -> bool { - attrs.iter().any(|attr| { - get_serde_meta_items(attr).iter().any(|arg| match arg { - NestedMeta::Meta(Meta::Path(path)) => { - if let Some(this_ident) = path.get_ident() { - *this_ident == *ident - } else { - false - } - } - _ => false, - }) - }) -} - -fn serde_default(attrs: &[syn::Attribute]) -> bool { - serde_attr(attrs, &Ident::new("default", Span::call_site())) -} - -fn serde_flatten(attrs: &[syn::Attribute]) -> bool { - serde_attr(attrs, &Ident::new("flatten", Span::call_site())) -} - -// TODO: for now, this is a workaround until we can integrate serde_derive_internal -// into our parser. -/// Returns all arguments passed into `#[serde(...)]` attributes -pub fn get_serde_meta_items(attr: &syn::Attribute) -> Vec { - if attr.path.get_ident().is_none() || *attr.path.get_ident().unwrap() != SERDE { - return Vec::default(); - } - - match attr.parse_meta() { - Ok(Meta::List(meta)) => meta.nested.into_iter().collect(), - _ => Vec::new(), - } -} - -/// Returns all arguments passed into `#[typeshare(...)]` attributes -pub fn get_typeshare_meta_items(attr: &syn::Attribute) -> Vec { - if attr.path.get_ident().is_none() || *attr.path.get_ident().unwrap() != TYPESHARE { - return Vec::default(); - } - - match attr.parse_meta() { - Ok(Meta::List(meta)) => meta.nested.into_iter().collect(), - _ => Vec::new(), - } -} - -// `#[typeshare(skip)]` or `#[serde(skip)]` -fn is_skipped(attrs: &[syn::Attribute]) -> bool { - let skip = Ident::new("skip", Span::call_site()); - attrs.iter().any(|attr| { - get_serde_meta_items(attr) - .into_iter() - .chain(get_typeshare_meta_items(attr)) - .any(|arg| match arg { - NestedMeta::Meta(Meta::Path(path)) => { - if let Some(ident) = path.get_ident() { - *ident == skip - } else { - false - } - } - _ => false, - }) - }) -} - -#[test] -fn test_rename_all_to_case() { - let test_word = "test_case"; - - let tests = [ - ("lowercase", "test_case"), - ("UPPERCASE", "TEST_CASE"), - ("PascalCase", "TestCase"), - ("camelCase", "testCase"), - ("snake_case", "test_case"), - ("SCREAMING_SNAKE_CASE", "TEST_CASE"), - ("kebab-case", "test-case"), - ("SCREAMING-KEBAB-CASE", "TEST-CASE"), - ("invalid case", "test_case"), - ]; - - for test in tests { - assert_eq!( - rename_all_to_case(test_word.to_string(), &Some(test.0.to_string())), - test.1 - ); - } -} - -/// Removes `-` characters from identifiers -pub(crate) fn remove_dash_from_identifier(name: &str) -> String { - // Dashes are not valid in identifiers, so we map them to underscores - name.replace('-', "_") -}