1use ignore::Walk;
3use itertools::Itertools;
4use proc_macro2::{Delimiter, Group};
5use rayon::iter::{IntoParallelIterator, ParallelIterator};
6use std::{
7 collections::{hash_map::Entry, HashMap, HashSet},
8 path::PathBuf,
9};
10use syn::{
11 ext::IdentExt,
12 parse::{Parse, Parser},
13 punctuated::Punctuated,
14 visit::Visit,
15 Attribute, Expr, ExprGroup, ExprLit, ExprParen, Fields, GenericParam, Ident, ItemConst,
16 ItemEnum, ItemStruct, ItemType, Lit, Meta, Token,
17};
18
19use typeshare_model::{
20 decorator::{self, DecoratorSet},
21 prelude::*,
22};
23
24use crate::{
25 rename::RenameExt,
26 target_os,
27 type_parser::{parse_rust_type, parse_rust_type_from_string, type_name},
28 visitors::TypeShareVisitor,
29 FileParseErrors, ParseError, ParseErrorKind, ParseErrorSet,
30};
31
32const SERDE: &str = "serde";
33const TYPESHARE: &str = "typeshare";
34
35#[non_exhaustive]
38#[derive(Debug, Clone)]
39pub enum RustItem {
40 Struct(RustStruct),
42 Enum(RustEnum),
44 Alias(RustTypeAlias),
46 Const(RustConst),
48}
49
50#[derive(Default, Debug)]
52pub struct ParsedData {
53 pub structs: Vec<RustStruct>,
55 pub enums: Vec<RustEnum>,
57 pub aliases: Vec<RustTypeAlias>,
59 pub consts: Vec<RustConst>,
61 pub import_types: HashSet<ImportedType>,
66}
67
68impl ParsedData {
69 pub fn merge(&mut self, other: Self) {
70 self.structs.extend(other.structs);
71 self.enums.extend(other.enums);
72 self.aliases.extend(other.aliases);
73 self.consts.extend(other.consts);
74 self.import_types.extend(other.import_types);
75 }
76
77 pub fn add(&mut self, item: RustItem) {
78 match item {
79 RustItem::Struct(rust_struct) => self.structs.push(rust_struct),
80 RustItem::Enum(rust_enum) => self.enums.push(rust_enum),
81 RustItem::Alias(rust_type_alias) => self.aliases.push(rust_type_alias),
82 RustItem::Const(rust_const) => self.consts.push(rust_const),
83 }
84 }
85
86 pub fn all_type_names(&self) -> impl Iterator<Item = &'_ TypeName> + use<'_> {
87 let s = self.structs.iter().map(|s| &s.id.renamed);
88 let e = self.enums.iter().map(|e| &e.shared().id.renamed);
89 let a = self.aliases.iter().map(|a| &a.id.renamed);
90 s.chain(e).chain(a)
94 }
95
96 pub fn sort_contents(&mut self) {
97 self.structs
98 .sort_unstable_by(|lhs, rhs| Ord::cmp(&lhs.id.original, &rhs.id.original));
99
100 self.enums.sort_unstable_by(|lhs, rhs| {
101 Ord::cmp(&lhs.shared().id.original, &rhs.shared().id.original)
102 });
103
104 self.aliases
105 .sort_unstable_by(|lhs, rhs| Ord::cmp(&lhs.id.original, &rhs.id.original));
106
107 self.consts
108 .sort_unstable_by(|lhs, rhs| Ord::cmp(&lhs.id.original, &rhs.id.original));
109 }
110}
111
112#[derive(Debug)]
114pub struct ParserInput {
115 file_path: PathBuf,
117 crate_name: Option<CrateName>,
119}
120
121pub fn parser_inputs(walker_builder: Walk) -> Vec<ParserInput> {
123 walker_builder
124 .filter_map(Result::ok)
125 .filter(|dir_entry| !dir_entry.path().is_dir())
126 .map(|dir_entry| {
127 let path = dir_entry.path();
128 let crate_name = CrateName::find_crate_name(path);
129 let file_path = path.to_path_buf();
130
131 ParserInput {
132 file_path,
133 crate_name,
134 }
135 })
136 .collect()
137}
138
139fn add_parsed_data(
163 container: &mut HashMap<Option<CrateName>, ParsedData>,
164 crate_name: Option<CrateName>,
165 parsed_data: ParsedData,
166) {
167 match container.entry(crate_name) {
168 Entry::Vacant(entry) => {
169 entry.insert(parsed_data);
170 }
171 Entry::Occupied(entry) => {
172 entry.into_mut().merge(parsed_data);
173 }
174 }
175}
176
177pub fn parse_input(
179 inputs: Vec<ParserInput>,
180 ignored_types: &[&str],
181 mode: FilesMode<()>,
182 target_os: Option<&[&str]>,
183) -> Result<HashMap<Option<CrateName>, ParsedData>, Vec<FileParseErrors>> {
184 inputs
185 .into_par_iter()
186 .map(|parser_input| {
187 let content = std::fs::read_to_string(&parser_input.file_path).map_err(|err| {
190 FileParseErrors::new(
191 parser_input.file_path.clone(),
192 parser_input.crate_name.clone(),
193 crate::FileErrorKind::ReadError(err),
194 )
195 })?;
196
197 let parsed_data = parse(
198 &content,
199 ignored_types,
200 match mode {
201 FilesMode::Single => FilesMode::Single,
202 FilesMode::Multi(()) => match parser_input.crate_name {
203 None => {
204 return Err(FileParseErrors::new(
205 parser_input.file_path.clone(),
206 parser_input.crate_name,
207 crate::FileErrorKind::UnknownCrate,
208 ))
209 }
210 Some(ref crate_name) => FilesMode::Multi(crate_name),
211 },
212 _ => panic!("unsupported mode {mode:?}; this is probably a typeshare bug"),
213 },
214 target_os,
215 )
216 .map_err(|err| {
217 FileParseErrors::new(
218 parser_input.file_path.clone(),
219 parser_input.crate_name.clone(),
220 crate::FileErrorKind::ParseErrors(err),
221 )
222 })?;
223
224 let parsed_data = parsed_data.and_then(|parsed_data| {
225 if is_parsed_data_empty(&parsed_data) {
226 None
227 } else {
228 Some(parsed_data)
229 }
230 });
231
232 Ok(parsed_data.map(|parsed_data| (parser_input.crate_name, parsed_data)))
233 })
234 .filter_map(|data| data.transpose())
235 .fold(
236 || Ok(HashMap::new()),
237 |mut accum, result| {
238 match (&mut accum, result) {
239 (Ok(accum), Ok((crate_name, parsed_data))) => {
240 add_parsed_data(accum, crate_name, parsed_data)
241 }
242 (Ok(_), Err(error)) => {
243 accum = Err(Vec::from([error]));
244 }
245 (Err(accum), Err(error)) => accum.push(error),
246 (Err(_), Ok(_)) => {}
247 }
248
249 accum
250 },
251 )
252 .reduce(
253 || Ok(HashMap::new()),
254 |old, new| match (old, new) {
255 (Ok(mut old), Ok(new)) => {
256 new.into_iter().for_each(|(crate_name, parsed_data)| {
257 add_parsed_data(&mut old, crate_name, parsed_data)
258 });
259 Ok(old)
260 }
261 (Err(errors), Ok(_)) | (Ok(_), Err(errors)) => Err(errors),
262 (Err(mut err1), Err(err2)) => {
263 err1.extend(err2);
264 Err(err1)
265 }
266 },
267 )
268}
269
270fn is_parsed_data_empty(parsed_data: &ParsedData) -> bool {
272 parsed_data.enums.is_empty()
273 && parsed_data.aliases.is_empty()
274 && parsed_data.structs.is_empty()
275 && parsed_data.consts.is_empty()
276}
277
278pub fn parse(
280 source_code: &str,
281 ignored_types: &[&str],
282 file_mode: FilesMode<&CrateName>,
283 target_os: Option<&[&str]>,
284) -> Result<Option<ParsedData>, ParseErrorSet> {
285 if !source_code.contains("#[typeshare") {
288 return Ok(None);
289 }
290
291 let mut import_visitor = TypeShareVisitor::new(ignored_types, file_mode, target_os);
294 let file_contents = syn::parse_file(source_code)
295 .map_err(|err| ParseError::new(&err.span(), ParseErrorKind::SynError(err)))?;
296
297 import_visitor.visit_file(&file_contents);
298
299 import_visitor.parsed_data().map(Some)
300}
301
302pub(crate) fn parse_struct(
308 s: &ItemStruct,
309 valid_os: Option<&[&str]>,
310) -> Result<RustItem, ParseError> {
311 let serde_rename_all = serde_rename_all(&s.attrs);
312
313 let generic_types = s
314 .generics
315 .params
316 .iter()
317 .filter_map(|param| match param {
318 GenericParam::Type(type_param) => Some(type_name(&type_param.ident)),
319 _ => None,
320 })
321 .collect();
322
323 let decorators = get_decorators(&s.attrs);
324
325 if let Some(ty) = get_serialized_as_type(&decorators) {
329 return Ok(RustItem::Alias(RustTypeAlias {
330 id: get_ident(Some(&s.ident), &s.attrs, None),
331 ty: parse_rust_type_from_string(&ty)?,
332 comments: parse_comment_attrs(&s.attrs),
333 generic_types,
334 decorators,
335 }));
336 }
337
338 Ok(match &s.fields {
339 Fields::Named(f) => {
341 let fields = f
342 .named
343 .iter()
344 .filter(|field| !is_skipped(&field.attrs))
345 .filter(|field| match valid_os {
346 Some(valid) => check_target_os(&field.attrs, valid),
347 None => true,
348 })
349 .map(|f| {
350 let decorators = get_decorators(&f.attrs);
351
352 let ty = match get_serialized_as_type(&decorators) {
353 Some(ty) => parse_rust_type_from_string(&ty)?,
354 None => parse_rust_type(&f.ty)?,
355 };
356
357 if serde_flatten(&f.attrs) {
358 return Err(ParseError::new(&f, ParseErrorKind::SerdeFlattenNotAllowed));
359 }
360
361 let has_default = serde_default(&f.attrs);
362
363 Ok(RustField {
364 id: get_ident(f.ident.as_ref(), &f.attrs, serde_rename_all.as_deref()),
365 ty,
366 comments: parse_comment_attrs(&f.attrs),
367 has_default,
368 decorators,
369 })
370 })
371 .collect::<Result<_, ParseError>>()?;
372
373 RustItem::Struct(RustStruct {
374 id: get_ident(Some(&s.ident), &s.attrs, None),
375 generic_types,
376 fields,
377 comments: parse_comment_attrs(&s.attrs),
378 decorators,
379 })
380 }
381 Fields::Unnamed(fields) => {
383 let Some(field) = fields.unnamed.iter().exactly_one().ok() else {
384 return Err(ParseError::new(fields, ParseErrorKind::ComplexTupleStruct));
385 };
386
387 let field_decorators = get_decorators(&field.attrs);
388
389 let ty = match get_serialized_as_type(&field_decorators) {
390 Some(ty) => parse_rust_type_from_string(&ty)?,
391 None => parse_rust_type(&field.ty)?,
392 };
393
394 RustItem::Alias(RustTypeAlias {
395 id: get_ident(Some(&s.ident), &s.attrs, None),
396 ty: ty,
397 comments: parse_comment_attrs(&s.attrs),
398 generic_types,
399 decorators,
400 })
401 }
402 Fields::Unit => RustItem::Struct(RustStruct {
404 id: get_ident(Some(&s.ident), &s.attrs, None),
405 generic_types,
406 fields: vec![],
407 comments: parse_comment_attrs(&s.attrs),
408 decorators,
409 }),
410 })
411}
412
413pub(crate) fn parse_enum(e: &ItemEnum, valid_os: Option<&[&str]>) -> Result<RustItem, ParseError> {
419 let generic_types = e
420 .generics
421 .params
422 .iter()
423 .filter_map(|param| match param {
424 GenericParam::Type(type_param) => Some(type_name(&type_param.ident)),
425 _ => None,
426 })
427 .collect();
428
429 let serde_rename_all = serde_rename_all(&e.attrs);
430 let decorators = get_decorators(&e.attrs);
431
432 if let Some(ty) = get_serialized_as_type(&decorators) {
435 return Ok(RustItem::Alias(RustTypeAlias {
436 id: get_ident(Some(&e.ident), &e.attrs, None),
437 ty: parse_rust_type_from_string(&ty)?,
438 comments: parse_comment_attrs(&e.attrs),
439 generic_types,
440 decorators,
441 }));
442 }
443
444 let original_enum_ident = type_name(&e.ident);
445
446 let maybe_tag_key = get_tag_key(&e.attrs);
448 let maybe_content_key = get_content_key(&e.attrs);
449
450 let variants = e
452 .variants
453 .iter()
454 .filter(|v| !is_skipped(&v.attrs))
456 .filter(|field| match valid_os {
457 Some(valid) => check_target_os(&field.attrs, valid),
458 None => true,
459 })
460 .map(|v| parse_enum_variant(v, serde_rename_all.as_deref(), valid_os))
461 .collect::<Result<Vec<_>, _>>()?;
462
463 let is_recursive = variants.iter().any(|v| match v {
465 RustEnumVariant::Unit(_) => false,
466 RustEnumVariant::Tuple { ty, .. } => ty.contains_type(&original_enum_ident),
467 RustEnumVariant::AnonymousStruct { fields, .. } => fields
468 .iter()
469 .any(|f| f.ty.contains_type(&original_enum_ident)),
470 _ => panic!("unrecgonized enum type"),
471 });
472
473 let shared = RustEnumShared {
474 id: get_ident(Some(&e.ident), &e.attrs, None),
475 comments: parse_comment_attrs(&e.attrs),
476 decorators,
477 generic_types,
478 is_recursive,
479 };
480
481 if variants
483 .iter()
484 .all(|v| matches!(v, RustEnumVariant::Unit(_)))
485 {
486 if maybe_tag_key.is_some() {
488 return Err(ParseError::new(
489 &e,
490 ParseErrorKind::SerdeTagNotAllowed {
491 enum_ident: original_enum_ident,
492 },
493 ));
494 }
495 if maybe_content_key.is_some() {
496 return Err(ParseError::new(
497 &e,
498 ParseErrorKind::SerdeContentNotAllowed {
499 enum_ident: original_enum_ident,
500 },
501 ));
502 }
503
504 Ok(RustItem::Enum(RustEnum::Unit {
505 shared,
506 unit_variants: variants
507 .into_iter()
508 .map(|variant| match variant {
509 RustEnumVariant::Unit(unit) => unit,
510 _ => unreachable!("non-unit variant; this was checked earlier"),
511 })
512 .collect(),
513 }))
514 } else {
515 Ok(RustItem::Enum(RustEnum::Algebraic {
517 tag_key: maybe_tag_key.ok_or_else(|| {
518 ParseError::new(
519 &e,
520 ParseErrorKind::SerdeTagRequired {
521 enum_ident: original_enum_ident.clone(),
522 },
523 )
524 })?,
525 content_key: maybe_content_key.ok_or_else(|| {
526 ParseError::new(
527 &e,
528 ParseErrorKind::SerdeContentRequired {
529 enum_ident: original_enum_ident.clone(),
530 },
531 )
532 })?,
533 shared,
534 variants,
535 }))
536 }
537}
538
539fn parse_enum_variant(
541 v: &syn::Variant,
542 enum_serde_rename_all: Option<&str>,
543 valid_os: Option<&[&str]>,
544) -> Result<RustEnumVariant, ParseError> {
545 let shared = RustEnumVariantShared {
546 id: get_ident(Some(&v.ident), &v.attrs, enum_serde_rename_all),
547 comments: parse_comment_attrs(&v.attrs),
548 };
549
550 let variant_serde_rename_all = serde_rename_all(&v.attrs);
556
557 match &v.fields {
558 syn::Fields::Unit => Ok(RustEnumVariant::Unit(shared)),
559 syn::Fields::Unnamed(associated_type) => {
560 let Some(field) = associated_type.unnamed.iter().exactly_one().ok() else {
561 return Err(ParseError::new(
562 associated_type,
563 ParseErrorKind::MultipleUnnamedAssociatedTypes,
564 ));
565 };
566 let decorators = get_decorators(&field.attrs);
567
568 let ty = match get_serialized_as_type(&decorators) {
569 Some(ty) => parse_rust_type_from_string(&ty)?,
570 None => parse_rust_type(&field.ty)?,
571 };
572
573 Ok(RustEnumVariant::Tuple { ty, shared })
574 }
575 syn::Fields::Named(fields_named) => Ok(RustEnumVariant::AnonymousStruct {
576 fields: fields_named
577 .named
578 .iter()
579 .filter(|f| !is_skipped(&f.attrs))
580 .filter(|field| match valid_os {
581 Some(valid) => check_target_os(&field.attrs, valid),
582 None => true,
583 })
584 .map(|f| {
585 let decorators = get_decorators(&f.attrs);
586
587 let field_type = match get_serialized_as_type(&decorators) {
588 Some(ty) => parse_rust_type_from_string(&ty)?,
589 None => parse_rust_type(&f.ty)?,
590 };
591
592 let has_default = serde_default(&f.attrs);
593
594 Ok(RustField {
595 id: get_ident(
596 f.ident.as_ref(),
597 &f.attrs,
598 variant_serde_rename_all.as_deref(),
599 ),
600 ty: field_type,
601 comments: parse_comment_attrs(&f.attrs),
602 has_default,
603 decorators,
604 })
605 })
606 .try_collect()?,
607 shared,
608 }),
609 }
610}
611
612pub(crate) fn parse_type_alias(t: &ItemType) -> Result<RustItem, ParseError> {
615 let decorators = get_decorators(&t.attrs);
616
617 let ty = match get_serialized_as_type(&decorators) {
618 Some(ty) => parse_rust_type_from_string(&ty)?,
619 None => parse_rust_type(&t.ty)?,
620 };
621
622 let generic_types = t
623 .generics
624 .params
625 .iter()
626 .filter_map(|param| match param {
627 GenericParam::Type(type_param) => Some(type_name(&type_param.ident)),
628 _ => None,
629 })
630 .collect();
631
632 Ok(RustItem::Alias(RustTypeAlias {
633 id: get_ident(Some(&t.ident), &t.attrs, None),
634 ty,
635 comments: parse_comment_attrs(&t.attrs),
636 generic_types,
637 decorators,
638 }))
639}
640
641pub(crate) fn parse_const(c: &ItemConst) -> Result<RustItem, ParseError> {
643 let expr = parse_const_expr(&c.expr)?;
644 let decorators = get_decorators(&c.attrs);
645
646 let ty = match get_serialized_as_type(&decorators) {
649 Some(ty) => parse_rust_type_from_string(ty)?,
650 None => parse_rust_type(&c.ty)?,
651 };
652
653 match &ty {
654 RustType::Special(SpecialRustType::HashMap(_, _))
655 | RustType::Special(SpecialRustType::Vec(_))
656 | RustType::Special(SpecialRustType::Option(_)) => {
657 return Err(ParseError::new(&c.ty, ParseErrorKind::RustConstTypeInvalid));
658 }
659 RustType::Special(_) => (),
660 RustType::Simple { .. } => (),
661 _ => return Err(ParseError::new(&c.ty, ParseErrorKind::RustConstTypeInvalid)),
662 };
663
664 Ok(RustItem::Const(RustConst {
665 id: get_ident(Some(&c.ident), &c.attrs, None),
666 ty,
667 expr,
668 }))
669}
670
671fn parse_const_expr(e: &Expr) -> Result<RustConstExpr, ParseError> {
672 let value = match e {
673 Expr::Lit(ExprLit {
674 lit: Lit::Int(lit), ..
675 }) => lit
676 .base10_parse()
677 .map_err(|_| ParseError::new(&lit, ParseErrorKind::RustConstExprInvalid))?,
678
679 Expr::Group(ExprGroup { expr, .. }) | Expr::Paren(ExprParen { expr, .. }) => {
680 return parse_const_expr(expr)
681 }
682 _ => return Err(ParseError::new(e, ParseErrorKind::RustConstExprInvalid)),
683 };
684
685 Ok(RustConstExpr::Int(value))
686}
687
688pub(crate) fn has_typeshare_annotation(attrs: &[syn::Attribute]) -> bool {
692 attrs
693 .iter()
694 .flat_map(|attr| attr.path().segments.clone())
695 .any(|segment| segment.ident == TYPESHARE)
696}
697
698pub(crate) fn serde_rename_all(attrs: &[syn::Attribute]) -> Option<String> {
699 get_name_value_meta_items(attrs, "rename_all", SERDE).next()
700}
701
702pub(crate) fn get_serialized_as_type(decorators: &DecoratorSet) -> Option<&str> {
703 match decorators.get("serialized_as")? {
705 decorator::Value::String(s) => Some(s),
706 _ => None,
707 }
708}
709
710pub(crate) fn get_name_value_meta_items<'a>(
711 attrs: &'a [syn::Attribute],
712 name: &'a str,
713 ident: &'static str,
714) -> impl Iterator<Item = String> + 'a {
715 attrs.iter().flat_map(move |attr| {
716 get_meta_items(attr, ident)
717 .iter()
718 .filter_map(|arg| match arg {
719 Meta::NameValue(name_value) if name_value.path.is_ident(name) => {
720 expr_to_string(&name_value.value)
721 }
722 _ => None,
723 })
724 .collect::<Vec<_>>()
725 })
726}
727
728fn get_meta_items(attr: &syn::Attribute, ident: &str) -> Vec<Meta> {
730 if attr.path().is_ident(ident) {
731 attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
732 .iter()
733 .flat_map(|meta| meta.iter())
734 .cloned()
735 .collect()
736 } else {
737 Vec::default()
738 }
739}
740
741fn get_ident(ident: Option<&Ident>, attrs: &[syn::Attribute], rename_all: Option<&str>) -> Id {
742 let original = ident.map_or("???".to_string(), |id| id.to_string().replace("r#", ""));
743
744 let mut renamed = rename_all_to_case(original.clone(), rename_all);
745
746 if let Some(s) = serde_rename(attrs) {
747 renamed = s;
748 }
749
750 Id {
751 original: TypeName::new_string(original),
752 renamed: TypeName::new_string(renamed),
753 }
754}
755
756fn rename_all_to_case(original: String, case: Option<&str>) -> String {
757 match case {
760 None => original,
761 Some(value) => match value {
762 "lowercase" => original.to_lowercase(),
763 "UPPERCASE" => original.to_uppercase(),
764 "PascalCase" => original.to_pascal_case(),
765 "camelCase" => original.to_camel_case(),
766 "snake_case" => original.to_snake_case(),
767 "SCREAMING_SNAKE_CASE" => original.to_screaming_snake_case(),
768 "kebab-case" => original.to_kebab_case(),
769 "SCREAMING-KEBAB-CASE" => original.to_screaming_kebab_case(),
770 _ => original,
771 },
772 }
773}
774
775fn serde_rename(attrs: &[syn::Attribute]) -> Option<String> {
776 get_name_value_meta_items(attrs, "rename", SERDE).next()
777}
778
779fn parse_comment_attrs(attrs: &[Attribute]) -> Vec<String> {
781 attrs
782 .iter()
783 .map(|attr| attr.meta.clone())
784 .filter_map(|meta| match meta {
785 Meta::NameValue(name_value) if name_value.path.is_ident("doc") => {
786 expr_to_string(&name_value.value)
787 }
788 _ => None,
789 })
790 .collect()
791}
792
793fn is_skipped(attrs: &[syn::Attribute]) -> bool {
795 attrs.iter().any(|attr| {
796 get_meta_items(attr, SERDE)
797 .into_iter()
798 .chain(get_meta_items(attr, TYPESHARE))
799 .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident("skip")))
800 })
801}
802
803fn serde_attr(attrs: &[syn::Attribute], ident: &str) -> bool {
804 attrs.iter().any(|attr| {
805 get_meta_items(attr, SERDE)
806 .iter()
807 .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident(ident)))
808 })
809}
810
811fn serde_default(attrs: &[syn::Attribute]) -> bool {
812 serde_attr(attrs, "default")
813}
814
815fn serde_flatten(attrs: &[syn::Attribute]) -> bool {
816 serde_attr(attrs, "flatten")
817}
818
819fn get_decorators(attrs: &[Attribute]) -> DecoratorSet {
823 attrs
824 .iter()
825 .flat_map(|attr| match attr.meta {
826 Meta::List(ref meta) => Some(meta),
827 Meta::Path(_) | Meta::NameValue(_) => None,
828 })
829 .filter(|meta| meta.path.is_ident(TYPESHARE))
830 .filter_map(|meta| meta.parse_args_with(KeyValueSeq::parse_terminated).ok())
831 .flatten()
832 .map(|pair| (pair.key, pair.value))
833 .collect()
834}
835
836pub fn check_target_os(attrs: &[Attribute], valid: &[&str]) -> bool {
840 attrs
841 .iter()
842 .filter_map(|attr| match attr.meta {
843 Meta::List(ref list) if list.path.is_ident("cfg") => Some(&list.tokens),
844 _ => None,
845 })
846 .filter_map(|cfg_tokens| target_os::Cfg::parse.parse2(cfg_tokens.clone()).ok())
847 .all(|cfg| target_os::target_os_good(&cfg, valid))
848}
849
850type KeyValueSeq = Punctuated<KeyMaybeValue, Token![,]>;
851
852fn expr_to_string(expr: &Expr) -> Option<String> {
853 match expr {
854 Expr::Lit(expr_lit) => literal_to_string(&expr_lit.lit),
855 _ => None,
856 }
857}
858
859fn literal_to_string(lit: &syn::Lit) -> Option<String> {
860 match lit {
861 syn::Lit::Str(str) => Some(str.value().trim().to_string()),
862 _ => None,
863 }
864}
865
866fn get_tag_key(attrs: &[syn::Attribute]) -> Option<String> {
867 get_name_value_meta_items(attrs, "tag", SERDE).next()
868}
869
870fn get_content_key(attrs: &[syn::Attribute]) -> Option<String> {
871 get_name_value_meta_items(attrs, "content", SERDE).next()
872}
873
874struct KeyMaybeValue {
877 key: String,
878 value: decorator::Value,
879}
880
881impl syn::parse::Parse for KeyMaybeValue {
882 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
883 let key = input.call(Ident::parse_any)?;
885
886 let value = if let Some(syn::token::Eq { .. }) = input.parse()? {
888 match input.parse()? {
889 syn::Lit::Str(lit) => decorator::Value::String(lit.value()),
890 syn::Lit::Int(lit) => decorator::Value::Int(lit.base10_parse()?),
891 syn::Lit::Bool(lit) => decorator::Value::Bool(lit.value),
892 lit => {
893 return Err(syn::Error::new(
894 lit.span(),
895 "unsupported decorator type (need string, int, or bool)",
896 ))
897 }
898 }
899 }
900 else if let Some(group @ Group { .. }) = input.parse()? {
902 let Delimiter::Parenthesis = group.delimiter() else {
903 return Err(syn::Error::new(
904 group.span(),
905 "expected a parenthesized group",
906 ));
907 };
908
909 let pairs = KeyValueSeq::parse_terminated.parse2(group.stream())?;
910
911 decorator::Value::Nested(
912 pairs
913 .into_iter()
914 .map(|pair| (pair.key, pair.value))
915 .collect(),
916 )
917 }
918 else {
920 decorator::Value::None
921 };
922
923 Ok(KeyMaybeValue {
924 key: key.to_string(),
925 value,
926 })
927 }
928}
929
930#[test]
931fn test_rename_all_to_case() {
932 let test_word = "test_case";
933
934 let tests = [
935 ("lowercase", "test_case"),
936 ("UPPERCASE", "TEST_CASE"),
937 ("PascalCase", "TestCase"),
938 ("camelCase", "testCase"),
939 ("snake_case", "test_case"),
940 ("SCREAMING_SNAKE_CASE", "TEST_CASE"),
941 ("kebab-case", "test-case"),
942 ("SCREAMING-KEBAB-CASE", "TEST-CASE"),
943 ("invalid case", "test_case"),
944 ];
945
946 for test in tests {
947 assert_eq!(
948 rename_all_to_case(test_word.to_string(), Some(test.0)),
949 test.1
950 );
951 }
952}
953
954#[cfg(test)]
955mod test_get_decorators {
956 use std::str::FromStr;
957
958 use cool_asserts::assert_matches;
959 use proc_macro2::TokenStream;
960 use syn::parse::Parser;
961 use typeshare_model::decorator::Value;
962
963 use super::*;
964
965 fn parse_attr(input: &str) -> Vec<Attribute> {
966 let tokens = TokenStream::from_str(input).expect("failed to create token stream");
967 let attr =
968 Parser::parse2(Attribute::parse_outer, tokens).expect("failed to parse attribute");
969
970 attr
971 }
972
973 #[test]
974 fn basic() {
975 let attr = parse_attr("#[typeshare(foo)]");
976 let decorators = get_decorators(&attr);
977
978 assert_eq!(decorators.get_all("foo"), &[Value::None]);
979 assert_eq!(decorators.get_all("baz"), &[])
980 }
981
982 #[test]
983 fn several() {
984 let attr = parse_attr("#[typeshare(foo, int=10, string=\"foo\")]");
985 let decorators = get_decorators(&attr);
986
987 assert_eq!(decorators.get_all("foo"), &[Value::None]);
988 assert_eq!(decorators.get_all("int"), &[Value::Int(10)]);
989 assert_eq!(
990 decorators.get_all("string"),
991 &[Value::String(String::from("foo"))]
992 );
993 assert_eq!(decorators.get_all("baz"), &[])
994 }
995
996 #[test]
997 fn multi_key() {
998 let attr = parse_attr("#[typeshare(thing=10, foo, thing=\"hello\")]");
999 let decorators = get_decorators(&attr);
1000
1001 assert_eq!(decorators.get_all("foo"), &[Value::None]);
1002 assert_eq!(
1003 decorators.get_all("thing"),
1004 &[Value::Int(10), Value::String(String::from("hello"))]
1005 )
1006 }
1007
1008 #[test]
1009 fn multiple_attributes() {
1010 let attr = parse_attr(
1011 "#[typeshare(foo, bar = \"baz\")]
1012 #[typeshare(baz = 42, qux)]",
1013 );
1014 let decorators = get_decorators(&attr);
1015
1016 assert_eq!(decorators.get_all("foo"), &[Value::None]);
1017 assert_eq!(
1018 decorators.get_all("bar"),
1019 &[Value::String(String::from("baz"))]
1020 );
1021 assert_eq!(decorators.get_all("baz"), &[Value::Int(42)]);
1022 assert_eq!(decorators.get_all("qux"), &[Value::None]);
1023 }
1024
1025 #[test]
1026 fn duplicate_keys_in_multiple_attributes() {
1027 let attr = parse_attr(
1028 "#[typeshare(foo = \"bar\", foo = 42)]
1029 #[typeshare(foo)]",
1030 );
1031 let decorators = get_decorators(&attr);
1032
1033 assert_eq!(
1034 decorators.get_all("foo"),
1035 &[
1036 Value::String(String::from("bar")),
1037 Value::Int(42),
1038 Value::None
1039 ]
1040 );
1041 }
1042
1043 #[test]
1045 fn jvm_inline() {
1046 let attr = parse_attr(
1047 "#[typeshare(kotlin =\"JvmInline\", redacted)]
1048 #[derive(Serialize, Debug, Clone, PartialEq, Eq, Hash)]
1049 #[serde(rename_all = \"camelCase\")]",
1050 );
1051
1052 let decorators = get_decorators(&attr);
1053
1054 assert_eq!(decorators.get_all("redacted"), &[Value::None]);
1055 assert_eq!(
1056 decorators.get_all("kotlin"),
1057 &[Value::String(String::from("JvmInline"))]
1058 )
1059 }
1060
1061 #[test]
1062 fn nested() {
1063 let attr = parse_attr("#[typeshare(a, b(c=1, d=2, d=3))]");
1064
1065 let decorators = get_decorators(&attr);
1066
1067 assert_eq!(decorators.get_all("a"), &[Value::None]);
1068
1069 let (inner,) = assert_matches!(decorators.get_all("b"), [
1070 Value::Nested(inner) => inner,
1071 ]);
1072
1073 assert_eq!(inner.get_all("c"), &[Value::Int(1)]);
1074 assert_eq!(inner.get_all("d"), &[Value::Int(2), Value::Int(3)]);
1075 }
1076
1077 #[test]
1078 fn type_override() {
1079 let attr = parse_attr(
1080 "#[typeshare(typescript(type = \"string\"))]
1081 #[typeshare(swift = \"Foo\", swift(type=\"NSString\"))]",
1082 );
1083
1084 let decorators = get_decorators(&attr);
1085
1086 eprintln!("{decorators:#?}");
1087
1088 assert_eq!(
1089 decorators.type_override_for_lang("swift").unwrap(),
1090 "NSString"
1091 );
1092 assert_eq!(
1093 decorators.type_override_for_lang("typescript").unwrap(),
1094 "string"
1095 );
1096 assert_eq!(decorators.type_override_for_lang("kotlin"), None);
1097 }
1098}