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