1use ignore::Walk;
3use itertools::Itertools;
4use rayon::iter::{IntoParallelIterator, ParallelIterator};
5use std::{
6 collections::{hash_map::Entry, HashMap, HashSet},
7 path::PathBuf,
8};
9use syn::{
10 punctuated::Punctuated, visit::Visit, Attribute, Expr, Fields, GenericParam, Ident, ItemEnum,
11 ItemStruct, ItemType, Meta, Token,
12};
13
14use typeshare_model::{
15 decorator::{self, DecoratorSet},
16 prelude::*,
17};
18
19use crate::{
20 rename::RenameExt,
21 type_parser::{parse_rust_type, parse_rust_type_from_string},
22 visitors::TypeShareVisitor,
23 FileParseErrors, ParseError, ParseErrorKind, ParseErrorSet,
24};
25
26const SERDE: &str = "serde";
27const TYPESHARE: &str = "typeshare";
28
29#[non_exhaustive]
32#[derive(Debug, Clone, PartialEq)]
33pub enum RustItem {
34 Struct(RustStruct),
36 Enum(RustEnum),
38 Alias(RustTypeAlias),
40 Const(RustConst),
42}
43
44#[derive(Default, Debug)]
46pub struct ParsedData {
47 pub structs: Vec<RustStruct>,
49 pub enums: Vec<RustEnum>,
51 pub aliases: Vec<RustTypeAlias>,
53 pub consts: Vec<RustConst>,
55 pub import_types: HashSet<ImportedType>,
60}
61
62impl ParsedData {
63 pub fn merge(&mut self, other: Self) {
64 self.structs.extend(other.structs);
65 self.enums.extend(other.enums);
66 self.aliases.extend(other.aliases);
67 self.import_types.extend(other.import_types);
68 }
69
70 pub fn add(&mut self, item: RustItem) {
71 match item {
72 RustItem::Struct(rust_struct) => self.structs.push(rust_struct),
73 RustItem::Enum(rust_enum) => self.enums.push(rust_enum),
74 RustItem::Alias(rust_type_alias) => self.aliases.push(rust_type_alias),
75 RustItem::Const(rust_const) => self.consts.push(rust_const),
76 }
77 }
78
79 pub fn all_type_names(&self) -> impl Iterator<Item = &'_ TypeName> + use<'_> {
80 let s = self.structs.iter().map(|s| &s.id.renamed);
81 let e = self.enums.iter().map(|e| &e.shared().id.renamed);
82 let a = self.aliases.iter().map(|a| &a.id.renamed);
83
84 s.chain(e).chain(a)
85 }
86
87 pub fn sort_contents(&mut self) {
88 self.structs
89 .sort_unstable_by(|lhs, rhs| Ord::cmp(&lhs.id.original, &rhs.id.original));
90
91 self.enums.sort_unstable_by(|lhs, rhs| {
92 Ord::cmp(&lhs.shared().id.original, &rhs.shared().id.original)
93 });
94
95 self.aliases
96 .sort_unstable_by(|lhs, rhs| Ord::cmp(&lhs.id.original, &rhs.id.original));
97 }
98}
99
100#[derive(Debug)]
102pub struct ParserInput {
103 file_path: PathBuf,
105 crate_name: Option<CrateName>,
107}
108
109pub fn parser_inputs(walker_builder: Walk) -> Vec<ParserInput> {
111 walker_builder
112 .filter_map(Result::ok)
113 .filter(|dir_entry| !dir_entry.path().is_dir())
114 .map(|dir_entry| {
115 let path = dir_entry.path();
116 let crate_name = CrateName::find_crate_name(path);
117 let file_path = path.to_path_buf();
118
119 ParserInput {
120 file_path,
121 crate_name,
122 }
123 })
124 .collect()
125}
126
127fn add_parsed_data(
151 container: &mut HashMap<Option<CrateName>, ParsedData>,
152 crate_name: Option<CrateName>,
153 parsed_data: ParsedData,
154) {
155 match container.entry(crate_name) {
156 Entry::Vacant(entry) => {
157 entry.insert(parsed_data);
158 }
159 Entry::Occupied(entry) => {
160 entry.into_mut().merge(parsed_data);
161 }
162 }
163}
164
165pub fn parse_input(
167 inputs: Vec<ParserInput>,
168 ignored_types: &[&str],
169 mode: FilesMode<()>,
170) -> Result<HashMap<Option<CrateName>, ParsedData>, Vec<FileParseErrors>> {
171 inputs
172 .into_par_iter()
173 .map(|parser_input| {
174 let content = std::fs::read_to_string(&parser_input.file_path).map_err(|err| {
177 FileParseErrors::new(
178 parser_input.file_path.clone(),
179 parser_input.crate_name.clone(),
180 crate::FileErrorKind::ReadError(err),
181 )
182 })?;
183
184 let parsed_data = parse(
185 &content,
186 ignored_types,
187 match mode {
188 FilesMode::Single => FilesMode::Single,
189 FilesMode::Multi(()) => match parser_input.crate_name {
190 None => {
191 return Err(FileParseErrors::new(
192 parser_input.file_path.clone(),
193 parser_input.crate_name,
194 crate::FileErrorKind::UnknownCrate,
195 ))
196 }
197 Some(ref crate_name) => FilesMode::Multi(crate_name),
198 },
199 },
200 )
201 .map_err(|err| {
202 FileParseErrors::new(
203 parser_input.file_path.clone(),
204 parser_input.crate_name.clone(),
205 crate::FileErrorKind::ParseErrors(err),
206 )
207 })?;
208
209 let parsed_data = parsed_data.and_then(|parsed_data| {
210 if is_parsed_data_empty(&parsed_data) {
211 None
212 } else {
213 Some(parsed_data)
214 }
215 });
216
217 Ok(parsed_data.map(|parsed_data| (parser_input.crate_name, parsed_data)))
218 })
219 .filter_map(|data| data.transpose())
220 .fold(
221 || Ok(HashMap::new()),
222 |mut accum, result| {
223 match (&mut accum, result) {
224 (Ok(accum), Ok((crate_name, parsed_data))) => {
225 add_parsed_data(accum, crate_name, parsed_data)
226 }
227 (Ok(_), Err(error)) => {
228 accum = Err(Vec::from([error]));
229 }
230 (Err(accum), Err(error)) => accum.push(error),
231 (Err(_), Ok(_)) => {}
232 }
233
234 accum
235 },
236 )
237 .reduce(
238 || Ok(HashMap::new()),
239 |old, new| match (old, new) {
240 (Ok(mut old), Ok(new)) => {
241 new.into_iter().for_each(|(crate_name, parsed_data)| {
242 add_parsed_data(&mut old, crate_name, parsed_data)
243 });
244 Ok(old)
245 }
246 (Err(errors), Ok(_)) | (Ok(_), Err(errors)) => Err(errors),
247 (Err(mut err1), Err(err2)) => {
248 err1.extend(err2);
249 Err(err1)
250 }
251 },
252 )
253}
254
255fn is_parsed_data_empty(parsed_data: &ParsedData) -> bool {
257 parsed_data.enums.is_empty() && parsed_data.aliases.is_empty() && parsed_data.structs.is_empty()
258}
259
260pub fn parse(
262 source_code: &str,
263 ignored_types: &[&str],
264 file_mode: FilesMode<&CrateName>,
265) -> Result<Option<ParsedData>, ParseErrorSet> {
266 if !source_code.contains("#[typeshare") {
269 return Ok(None);
270 }
271
272 let mut import_visitor = TypeShareVisitor::new(ignored_types, file_mode);
275 let file_contents = syn::parse_file(source_code)
276 .map_err(|err| ParseError::new(&err.span(), ParseErrorKind::SynError(err)))?;
277
278 import_visitor.visit_file(&file_contents);
279
280 import_visitor.parsed_data().map(Some)
281}
282
283pub(crate) fn parse_struct(s: &ItemStruct) -> Result<RustItem, ParseError> {
289 let serde_rename_all = serde_rename_all(&s.attrs);
290
291 let generic_types = s
292 .generics
293 .params
294 .iter()
295 .filter_map(|param| match param {
296 GenericParam::Type(type_param) => Some(TypeName::new(&type_param.ident)),
297 _ => None,
298 })
299 .collect();
300
301 let decorators = get_decorators(&s.attrs);
302
303 if let Some(ty) = get_serialized_as_type(&decorators) {
307 return Ok(RustItem::Alias(RustTypeAlias {
308 id: get_ident(Some(&s.ident), &s.attrs, &None),
309 r#type: parse_rust_type_from_string(&ty)?,
310 comments: parse_comment_attrs(&s.attrs),
311 generic_types,
312 }));
313 }
314
315 Ok(match &s.fields {
316 Fields::Named(f) => {
318 let fields = f
319 .named
320 .iter()
321 .filter(|field| !is_skipped(&field.attrs))
322 .map(|f| {
323 let decorators = get_decorators(&f.attrs);
324
325 let ty = match get_serialized_as_type(&decorators) {
326 Some(ty) => parse_rust_type_from_string(&ty)?,
327 None => parse_rust_type(&f.ty)?,
328 };
329
330 if serde_flatten(&f.attrs) {
331 return Err(ParseError::new(&f, ParseErrorKind::SerdeFlattenNotAllowed));
332 }
333
334 let has_default = serde_default(&f.attrs);
335
336 Ok(RustField {
337 id: get_ident(f.ident.as_ref(), &f.attrs, &serde_rename_all),
338 ty,
339 comments: parse_comment_attrs(&f.attrs),
340 has_default,
341 decorators,
342 })
343 })
344 .collect::<Result<_, ParseError>>()?;
345
346 RustItem::Struct(RustStruct {
347 id: get_ident(Some(&s.ident), &s.attrs, &None),
348 generic_types,
349 fields,
350 comments: parse_comment_attrs(&s.attrs),
351 decorators,
352 })
353 }
354 Fields::Unnamed(f) => {
356 let Some(f) = f.unnamed.iter().exactly_one().ok() else {
357 return Err(ParseError::new(f, ParseErrorKind::ComplexTupleStruct));
358 };
359
360 let decorators = get_decorators(&f.attrs);
361
362 let ty = match get_serialized_as_type(&decorators) {
363 Some(ty) => parse_rust_type_from_string(&ty)?,
364 None => parse_rust_type(&f.ty)?,
365 };
366
367 RustItem::Alias(RustTypeAlias {
368 id: get_ident(Some(&s.ident), &s.attrs, &None),
369 r#type: ty,
370 comments: parse_comment_attrs(&s.attrs),
371 generic_types,
372 })
373 }
374 Fields::Unit => RustItem::Struct(RustStruct {
376 id: get_ident(Some(&s.ident), &s.attrs, &None),
377 generic_types,
378 fields: vec![],
379 comments: parse_comment_attrs(&s.attrs),
380 decorators,
381 }),
382 })
383}
384
385pub(crate) fn parse_enum(e: &ItemEnum) -> Result<RustItem, ParseError> {
391 let generic_types = e
392 .generics
393 .params
394 .iter()
395 .filter_map(|param| match param {
396 GenericParam::Type(type_param) => Some(TypeName::new(&type_param.ident)),
397 _ => None,
398 })
399 .collect();
400
401 let serde_rename_all = serde_rename_all(&e.attrs);
402 let decorators = get_decorators(&e.attrs);
403
404 if let Some(ty) = get_serialized_as_type(&decorators) {
407 return Ok(RustItem::Alias(RustTypeAlias {
408 id: get_ident(Some(&e.ident), &e.attrs, &None),
409 r#type: parse_rust_type_from_string(&ty)?,
410 comments: parse_comment_attrs(&e.attrs),
411 generic_types,
412 }));
413 }
414
415 let original_enum_ident = TypeName::new(&e.ident);
416
417 let maybe_tag_key = get_tag_key(&e.attrs);
419 let maybe_content_key = get_content_key(&e.attrs);
420
421 let variants = e
423 .variants
424 .iter()
425 .filter(|v| !is_skipped(&v.attrs))
427 .map(|v| parse_enum_variant(v, &serde_rename_all))
428 .collect::<Result<Vec<_>, _>>()?;
429
430 let is_recursive = variants.iter().any(|v| match v {
432 RustEnumVariant::Unit(_) => false,
433 RustEnumVariant::Tuple { ty, .. } => ty.contains_type(&original_enum_ident),
434 RustEnumVariant::AnonymousStruct { fields, .. } => fields
435 .iter()
436 .any(|f| f.ty.contains_type(&original_enum_ident)),
437 _ => panic!("unrecgonized enum type"),
438 });
439
440 let shared = RustEnumShared {
441 id: get_ident(Some(&e.ident), &e.attrs, &None),
442 comments: parse_comment_attrs(&e.attrs),
443 decorators,
444 generic_types,
445 is_recursive,
446 };
447
448 if variants
450 .iter()
451 .all(|v| matches!(v, RustEnumVariant::Unit(_)))
452 {
453 if maybe_tag_key.is_some() {
455 return Err(ParseError::new(
456 &e,
457 ParseErrorKind::SerdeTagNotAllowed {
458 enum_ident: original_enum_ident,
459 },
460 ));
461 }
462 if maybe_content_key.is_some() {
463 return Err(ParseError::new(
464 &e,
465 ParseErrorKind::SerdeContentNotAllowed {
466 enum_ident: original_enum_ident,
467 },
468 ));
469 }
470
471 Ok(RustItem::Enum(RustEnum::Unit {
472 shared,
473 unit_variants: variants
474 .into_iter()
475 .map(|variant| match variant {
476 RustEnumVariant::Unit(unit) => unit,
477 _ => unreachable!("non-unit variant; this was checked earlier"),
478 })
479 .collect(),
480 }))
481 } else {
482 Ok(RustItem::Enum(RustEnum::Algebraic {
484 tag_key: maybe_tag_key.ok_or_else(|| {
485 ParseError::new(
486 &e,
487 ParseErrorKind::SerdeTagRequired {
488 enum_ident: original_enum_ident.clone(),
489 },
490 )
491 })?,
492 content_key: maybe_content_key.ok_or_else(|| {
493 ParseError::new(
494 &e,
495 ParseErrorKind::SerdeContentRequired {
496 enum_ident: original_enum_ident.clone(),
497 },
498 )
499 })?,
500 shared,
501 variants,
502 }))
503 }
504}
505
506fn parse_enum_variant(
508 v: &syn::Variant,
509 enum_serde_rename_all: &Option<String>,
510) -> Result<RustEnumVariant, ParseError> {
511 let shared = RustEnumVariantShared {
512 id: get_ident(Some(&v.ident), &v.attrs, enum_serde_rename_all),
513 comments: parse_comment_attrs(&v.attrs),
514 };
515
516 let variant_serde_rename_all = serde_rename_all(&v.attrs);
522
523 match &v.fields {
524 syn::Fields::Unit => Ok(RustEnumVariant::Unit(shared)),
525 syn::Fields::Unnamed(associated_type) => {
526 let Some(field) = associated_type.unnamed.iter().exactly_one().ok() else {
527 return Err(ParseError::new(
528 associated_type,
529 ParseErrorKind::MultipleUnnamedAssociatedTypes,
530 ));
531 };
532 let decorators = get_decorators(&field.attrs);
533
534 let ty = match get_serialized_as_type(&decorators) {
535 Some(ty) => parse_rust_type_from_string(&ty)?,
536 None => parse_rust_type(&field.ty)?,
537 };
538
539 Ok(RustEnumVariant::Tuple { ty, shared })
540 }
541 syn::Fields::Named(fields_named) => Ok(RustEnumVariant::AnonymousStruct {
542 fields: fields_named
543 .named
544 .iter()
545 .filter(|f| !is_skipped(&f.attrs))
546 .map(|f| {
547 let decorators = get_decorators(&f.attrs);
548
549 let field_type = match get_serialized_as_type(&decorators) {
550 Some(ty) => parse_rust_type_from_string(&ty)?,
551 None => parse_rust_type(&f.ty)?,
552 };
553
554 let has_default = serde_default(&f.attrs);
555
556 Ok(RustField {
557 id: get_ident(f.ident.as_ref(), &f.attrs, &variant_serde_rename_all),
558 ty: field_type,
559 comments: parse_comment_attrs(&f.attrs),
560 has_default,
561 decorators,
562 })
563 })
564 .try_collect()?,
565 shared,
566 }),
567 }
568}
569
570pub(crate) fn parse_type_alias(t: &ItemType) -> Result<RustItem, ParseError> {
573 let decorators = get_decorators(&t.attrs);
574
575 let ty = match get_serialized_as_type(&decorators) {
576 Some(ty) => parse_rust_type_from_string(&ty)?,
577 None => parse_rust_type(&t.ty)?,
578 };
579
580 let generic_types = t
581 .generics
582 .params
583 .iter()
584 .filter_map(|param| match param {
585 GenericParam::Type(type_param) => Some(TypeName::new(&type_param.ident)),
586 _ => None,
587 })
588 .collect();
589
590 Ok(RustItem::Alias(RustTypeAlias {
591 id: get_ident(Some(&t.ident), &t.attrs, &None),
592 r#type: ty,
593 comments: parse_comment_attrs(&t.attrs),
594 generic_types,
595 }))
596}
597
598pub(crate) fn has_typeshare_annotation(attrs: &[syn::Attribute]) -> bool {
602 attrs
603 .iter()
604 .flat_map(|attr| attr.path().segments.clone())
605 .any(|segment| segment.ident == TYPESHARE)
606}
607
608pub(crate) fn serde_rename_all(attrs: &[syn::Attribute]) -> Option<String> {
609 get_name_value_meta_items(attrs, "rename_all", SERDE).next()
610}
611
612pub(crate) fn get_serialized_as_type(decorators: &DecoratorSet) -> Option<&str> {
613 match decorators.get("serialized_as")?.first()? {
615 decorator::Value::String(s) => Some(s),
616 _ => None,
617 }
618}
619
620pub(crate) fn get_name_value_meta_items<'a>(
621 attrs: &'a [syn::Attribute],
622 name: &'a str,
623 ident: &'static str,
624) -> impl Iterator<Item = String> + 'a {
625 attrs.iter().flat_map(move |attr| {
626 get_meta_items(attr, ident)
627 .iter()
628 .filter_map(|arg| match arg {
629 Meta::NameValue(name_value) if name_value.path.is_ident(name) => {
630 expr_to_string(&name_value.value)
631 }
632 _ => None,
633 })
634 .collect::<Vec<_>>()
635 })
636}
637
638fn get_meta_items(attr: &syn::Attribute, ident: &str) -> Vec<Meta> {
640 if attr.path().is_ident(ident) {
641 attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
642 .iter()
643 .flat_map(|meta| meta.iter())
644 .cloned()
645 .collect()
646 } else {
647 Vec::default()
648 }
649}
650
651fn get_ident(ident: Option<&Ident>, attrs: &[syn::Attribute], rename_all: &Option<String>) -> Id {
652 let original = ident.map_or("???".to_string(), |id| id.to_string().replace("r#", ""));
653
654 let mut renamed = rename_all_to_case(original.clone(), rename_all);
655
656 if let Some(s) = serde_rename(attrs) {
657 renamed = s;
658 }
659
660 Id {
661 original: TypeName::new_string(original),
662 renamed: TypeName::new_string(renamed),
663 }
664}
665
666fn rename_all_to_case(original: String, case: &Option<String>) -> String {
667 match case {
670 None => original,
671 Some(value) => match value.as_str() {
672 "lowercase" => original.to_lowercase(),
673 "UPPERCASE" => original.to_uppercase(),
674 "PascalCase" => original.to_pascal_case(),
675 "camelCase" => original.to_camel_case(),
676 "snake_case" => original.to_snake_case(),
677 "SCREAMING_SNAKE_CASE" => original.to_screaming_snake_case(),
678 "kebab-case" => original.to_kebab_case(),
679 "SCREAMING-KEBAB-CASE" => original.to_screaming_kebab_case(),
680 _ => original,
681 },
682 }
683}
684
685fn serde_rename(attrs: &[syn::Attribute]) -> Option<String> {
686 get_name_value_meta_items(attrs, "rename", SERDE).next()
687}
688
689fn parse_comment_attrs(attrs: &[Attribute]) -> Vec<String> {
691 attrs
692 .iter()
693 .map(|attr| attr.meta.clone())
694 .filter_map(|meta| match meta {
695 Meta::NameValue(name_value) if name_value.path.is_ident("doc") => {
696 expr_to_string(&name_value.value)
697 }
698 _ => None,
699 })
700 .collect()
701}
702
703fn is_skipped(attrs: &[syn::Attribute]) -> bool {
705 attrs.iter().any(|attr| {
706 get_meta_items(attr, SERDE)
707 .into_iter()
708 .chain(get_meta_items(attr, TYPESHARE))
709 .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident("skip")))
710 })
711}
712
713fn serde_attr(attrs: &[syn::Attribute], ident: &str) -> bool {
714 attrs.iter().any(|attr| {
715 get_meta_items(attr, SERDE)
716 .iter()
717 .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident(ident)))
718 })
719}
720
721fn serde_default(attrs: &[syn::Attribute]) -> bool {
722 serde_attr(attrs, "default")
723}
724
725fn serde_flatten(attrs: &[syn::Attribute]) -> bool {
726 serde_attr(attrs, "flatten")
727}
728
729fn get_decorators(attrs: &[Attribute]) -> DecoratorSet {
733 attrs
734 .iter()
735 .flat_map(|attr| match attr.meta {
736 Meta::List(ref meta) => Some(meta),
737 Meta::Path(_) | Meta::NameValue(_) => None,
738 })
739 .filter(|meta| meta.path.is_ident(TYPESHARE))
740 .filter_map(|meta| {
741 meta.parse_args_with(Punctuated::<KeyMaybeValue, Token![,]>::parse_terminated)
742 .ok()
743 })
744 .flatten()
745 .fold(DecoratorSet::new(), |mut set, decorator| {
746 set.entry(decorator.key).or_default().push(decorator.value);
747
748 set
749 })
750}
751
752fn expr_to_string(expr: &Expr) -> Option<String> {
753 match expr {
754 Expr::Lit(expr_lit) => literal_to_string(&expr_lit.lit),
755 _ => None,
756 }
757}
758
759fn literal_to_string(lit: &syn::Lit) -> Option<String> {
760 match lit {
761 syn::Lit::Str(str) => Some(str.value().trim().to_string()),
762 _ => None,
763 }
764}
765
766fn get_tag_key(attrs: &[syn::Attribute]) -> Option<String> {
767 get_name_value_meta_items(attrs, "tag", SERDE).next()
768}
769
770fn get_content_key(attrs: &[syn::Attribute]) -> Option<String> {
771 get_name_value_meta_items(attrs, "content", SERDE).next()
772}
773
774struct KeyMaybeValue {
777 key: String,
778 value: decorator::Value,
779}
780
781impl syn::parse::Parse for KeyMaybeValue {
782 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
783 let key: Ident = input.parse()?;
784 let eq: Option<syn::Token![=]> = input.parse()?;
785 let value: Option<syn::Lit> = eq.map(|_| input.parse()).transpose()?;
786
787 let value = match value {
788 None => decorator::Value::None,
789 Some(syn::Lit::Str(lit)) => decorator::Value::String(lit.value()),
790 Some(syn::Lit::Int(lit)) => decorator::Value::Int(lit.base10_parse()?),
791 Some(syn::Lit::Bool(lit)) => decorator::Value::Bool(lit.value),
792 Some(lit) => {
793 return Err(syn::Error::new(
794 lit.span(),
795 "unsupported decorator type (need string, int, or bool)",
796 ))
797 }
798 };
799
800 Ok(KeyMaybeValue {
801 key: key.to_string(),
802 value,
803 })
804 }
805}
806
807#[test]
808fn test_rename_all_to_case() {
809 let test_word = "test_case";
810
811 let tests = [
812 ("lowercase", "test_case"),
813 ("UPPERCASE", "TEST_CASE"),
814 ("PascalCase", "TestCase"),
815 ("camelCase", "testCase"),
816 ("snake_case", "test_case"),
817 ("SCREAMING_SNAKE_CASE", "TEST_CASE"),
818 ("kebab-case", "test-case"),
819 ("SCREAMING-KEBAB-CASE", "TEST-CASE"),
820 ("invalid case", "test_case"),
821 ];
822
823 for test in tests {
824 assert_eq!(
825 rename_all_to_case(test_word.to_string(), &Some(test.0.to_string())),
826 test.1
827 );
828 }
829}