typeshare_engine/
parser.rs

1//! Source file parsing.
2use 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, ExprGroup, ExprLit, ExprParen, Fields,
11    GenericParam, Ident, ItemConst, ItemEnum, ItemStruct, ItemType, Lit, 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/// An enum that encapsulates units of code generation for Typeshare.
30/// Analogous to `syn::Item`, even though our variants are more limited.
31#[non_exhaustive]
32#[derive(Debug, Clone, PartialEq)]
33pub enum RustItem {
34    /// A `struct` definition
35    Struct(RustStruct),
36    /// An `enum` definition
37    Enum(RustEnum),
38    /// A `type` definition or newtype struct.
39    Alias(RustTypeAlias),
40    /// A `const` definition
41    Const(RustConst),
42}
43
44/// The results of parsing Rust source input.
45#[derive(Default, Debug)]
46pub struct ParsedData {
47    /// Structs defined in the source
48    pub structs: Vec<RustStruct>,
49    /// Enums defined in the source
50    pub enums: Vec<RustEnum>,
51    /// Type aliases defined in the source
52    pub aliases: Vec<RustTypeAlias>,
53    /// Constant variables defined in the source
54    pub consts: Vec<RustConst>,
55    /// Imports used by this file
56    /// TODO: This is currently almost empty. Import computation was found to
57    /// be pretty broken during the migration to Typeshare 2, so that part
58    /// of multi-file output was stripped out to be restored later.
59    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.consts.extend(other.consts);
68        self.import_types.extend(other.import_types);
69    }
70
71    pub fn add(&mut self, item: RustItem) {
72        match item {
73            RustItem::Struct(rust_struct) => self.structs.push(rust_struct),
74            RustItem::Enum(rust_enum) => self.enums.push(rust_enum),
75            RustItem::Alias(rust_type_alias) => self.aliases.push(rust_type_alias),
76            RustItem::Const(rust_const) => self.consts.push(rust_const),
77        }
78    }
79
80    pub fn all_type_names(&self) -> impl Iterator<Item = &'_ TypeName> + use<'_> {
81        let s = self.structs.iter().map(|s| &s.id.renamed);
82        let e = self.enums.iter().map(|e| &e.shared().id.renamed);
83        let a = self.aliases.iter().map(|a| &a.id.renamed);
84        // currently we ignore consts, which aren't types. May revisit this
85        // later.
86
87        s.chain(e).chain(a)
88    }
89
90    pub fn sort_contents(&mut self) {
91        self.structs
92            .sort_unstable_by(|lhs, rhs| Ord::cmp(&lhs.id.original, &rhs.id.original));
93
94        self.enums.sort_unstable_by(|lhs, rhs| {
95            Ord::cmp(&lhs.shared().id.original, &rhs.shared().id.original)
96        });
97
98        self.aliases
99            .sort_unstable_by(|lhs, rhs| Ord::cmp(&lhs.id.original, &rhs.id.original));
100
101        self.consts
102            .sort_unstable_by(|lhs, rhs| Ord::cmp(&lhs.id.original, &rhs.id.original));
103    }
104}
105
106/// Input data for parsing each source file.
107#[derive(Debug)]
108pub struct ParserInput {
109    /// Rust source file path.
110    file_path: PathBuf,
111    /// The crate name the source file belongs to, if we could detect it
112    crate_name: Option<CrateName>,
113}
114
115/// Walk the source folder and collect all parser inputs.
116pub fn parser_inputs(walker_builder: Walk) -> Vec<ParserInput> {
117    walker_builder
118        .filter_map(Result::ok)
119        .filter(|dir_entry| !dir_entry.path().is_dir())
120        .map(|dir_entry| {
121            let path = dir_entry.path();
122            let crate_name = CrateName::find_crate_name(path);
123            let file_path = path.to_path_buf();
124
125            ParserInput {
126                file_path,
127                crate_name,
128            }
129        })
130        .collect()
131}
132
133// /// This function produces the `import_candidates`
134// /// Collect all the typeshared types into a mapping of crate names to typeshared types. This
135// /// mapping is used to lookup and generated import statements for generated files.
136// pub fn all_types(file_mappings: &HashMap<CrateName, ParsedData>) -> CrateTypes {
137//     file_mappings
138//         .iter()
139//         .map(|(crate_name, parsed_data)| (crate_name, &parsed_data.type_names))
140//         .fold(
141//             HashMap::new(),
142//             |mut import_map: CrateTypes, (crate_name, type_names)| {
143//                 match import_map.entry(crate_name.clone()) {
144//                     Entry::Occupied(mut e) => {
145//                         e.get_mut().extend(type_names.iter().cloned());
146//                     }
147//                     Entry::Vacant(e) => {
148//                         e.insert(type_names.clone());
149//                     }
150//                 }
151//                 import_map
152//             },
153//         )
154// }
155
156fn add_parsed_data(
157    container: &mut HashMap<Option<CrateName>, ParsedData>,
158    crate_name: Option<CrateName>,
159    parsed_data: ParsedData,
160) {
161    match container.entry(crate_name) {
162        Entry::Vacant(entry) => {
163            entry.insert(parsed_data);
164        }
165        Entry::Occupied(entry) => {
166            entry.into_mut().merge(parsed_data);
167        }
168    }
169}
170
171/// Collect all the parsed sources into a mapping of crate name to parsed data.
172pub fn parse_input(
173    inputs: Vec<ParserInput>,
174    ignored_types: &[&str],
175    mode: FilesMode<()>,
176) -> Result<HashMap<Option<CrateName>, ParsedData>, Vec<FileParseErrors>> {
177    inputs
178        .into_par_iter()
179        .map(|parser_input| {
180            // Performance nit: we don't need to clone in the error case;
181            // map_err is taking unconditional ownership unnecessarily
182            let content = std::fs::read_to_string(&parser_input.file_path).map_err(|err| {
183                FileParseErrors::new(
184                    parser_input.file_path.clone(),
185                    parser_input.crate_name.clone(),
186                    crate::FileErrorKind::ReadError(err),
187                )
188            })?;
189
190            let parsed_data = parse(
191                &content,
192                ignored_types,
193                match mode {
194                    FilesMode::Single => FilesMode::Single,
195                    FilesMode::Multi(()) => match parser_input.crate_name {
196                        None => {
197                            return Err(FileParseErrors::new(
198                                parser_input.file_path.clone(),
199                                parser_input.crate_name,
200                                crate::FileErrorKind::UnknownCrate,
201                            ))
202                        }
203                        Some(ref crate_name) => FilesMode::Multi(crate_name),
204                    },
205                },
206            )
207            .map_err(|err| {
208                FileParseErrors::new(
209                    parser_input.file_path.clone(),
210                    parser_input.crate_name.clone(),
211                    crate::FileErrorKind::ParseErrors(err),
212                )
213            })?;
214
215            let parsed_data = parsed_data.and_then(|parsed_data| {
216                if is_parsed_data_empty(&parsed_data) {
217                    None
218                } else {
219                    Some(parsed_data)
220                }
221            });
222
223            Ok(parsed_data.map(|parsed_data| (parser_input.crate_name, parsed_data)))
224        })
225        .filter_map(|data| data.transpose())
226        .fold(
227            || Ok(HashMap::new()),
228            |mut accum, result| {
229                match (&mut accum, result) {
230                    (Ok(accum), Ok((crate_name, parsed_data))) => {
231                        add_parsed_data(accum, crate_name, parsed_data)
232                    }
233                    (Ok(_), Err(error)) => {
234                        accum = Err(Vec::from([error]));
235                    }
236                    (Err(accum), Err(error)) => accum.push(error),
237                    (Err(_), Ok(_)) => {}
238                }
239
240                accum
241            },
242        )
243        .reduce(
244            || Ok(HashMap::new()),
245            |old, new| match (old, new) {
246                (Ok(mut old), Ok(new)) => {
247                    new.into_iter().for_each(|(crate_name, parsed_data)| {
248                        add_parsed_data(&mut old, crate_name, parsed_data)
249                    });
250                    Ok(old)
251                }
252                (Err(errors), Ok(_)) | (Ok(_), Err(errors)) => Err(errors),
253                (Err(mut err1), Err(err2)) => {
254                    err1.extend(err2);
255                    Err(err1)
256                }
257            },
258        )
259}
260
261/// Check if we have not parsed any relavent typehsared types.
262fn is_parsed_data_empty(parsed_data: &ParsedData) -> bool {
263    parsed_data.enums.is_empty()
264        && parsed_data.aliases.is_empty()
265        && parsed_data.structs.is_empty()
266        && parsed_data.consts.is_empty()
267}
268
269/// Parse the given Rust source string into `ParsedData`.
270pub fn parse(
271    source_code: &str,
272    ignored_types: &[&str],
273    file_mode: FilesMode<&CrateName>,
274) -> Result<Option<ParsedData>, ParseErrorSet> {
275    // We will only produce output for files that contain the `#[typeshare]`
276    // attribute, so this is a quick and easy performance win
277    if !source_code.contains("#[typeshare") {
278        return Ok(None);
279    }
280
281    // Parse and process the input, ensuring we parse only items marked with
282    // `#[typeshare]`
283    let mut import_visitor = TypeShareVisitor::new(ignored_types, file_mode);
284    let file_contents = syn::parse_file(source_code)
285        .map_err(|err| ParseError::new(&err.span(), ParseErrorKind::SynError(err)))?;
286
287    import_visitor.visit_file(&file_contents);
288
289    import_visitor.parsed_data().map(Some)
290}
291
292/// Parses a struct into a definition that more succinctly represents what
293/// typeshare needs to generate code for other languages.
294///
295/// This function can currently return something other than a struct, which is a
296/// hack.
297pub(crate) fn parse_struct(s: &ItemStruct) -> Result<RustItem, ParseError> {
298    let serde_rename_all = serde_rename_all(&s.attrs);
299
300    let generic_types = s
301        .generics
302        .params
303        .iter()
304        .filter_map(|param| match param {
305            GenericParam::Type(type_param) => Some(TypeName::new(&type_param.ident)),
306            _ => None,
307        })
308        .collect();
309
310    let decorators = get_decorators(&s.attrs);
311
312    // Check if this struct should be parsed as a type alias.
313    // TODO: we shouldn't lie and return a type alias when parsing a struct. this
314    // is a temporary hack
315    if let Some(ty) = get_serialized_as_type(&decorators) {
316        return Ok(RustItem::Alias(RustTypeAlias {
317            id: get_ident(Some(&s.ident), &s.attrs, &None),
318            ty: parse_rust_type_from_string(&ty)?,
319            comments: parse_comment_attrs(&s.attrs),
320            generic_types,
321        }));
322    }
323
324    Ok(match &s.fields {
325        // Structs
326        Fields::Named(f) => {
327            let fields = f
328                .named
329                .iter()
330                .filter(|field| !is_skipped(&field.attrs))
331                .map(|f| {
332                    let decorators = get_decorators(&f.attrs);
333
334                    let ty = match get_serialized_as_type(&decorators) {
335                        Some(ty) => parse_rust_type_from_string(&ty)?,
336                        None => parse_rust_type(&f.ty)?,
337                    };
338
339                    if serde_flatten(&f.attrs) {
340                        return Err(ParseError::new(&f, ParseErrorKind::SerdeFlattenNotAllowed));
341                    }
342
343                    let has_default = serde_default(&f.attrs);
344
345                    Ok(RustField {
346                        id: get_ident(f.ident.as_ref(), &f.attrs, &serde_rename_all),
347                        ty,
348                        comments: parse_comment_attrs(&f.attrs),
349                        has_default,
350                        decorators,
351                    })
352                })
353                .collect::<Result<_, ParseError>>()?;
354
355            RustItem::Struct(RustStruct {
356                id: get_ident(Some(&s.ident), &s.attrs, &None),
357                generic_types,
358                fields,
359                comments: parse_comment_attrs(&s.attrs),
360                decorators,
361            })
362        }
363        // Tuple structs
364        Fields::Unnamed(f) => {
365            let Some(f) = f.unnamed.iter().exactly_one().ok() else {
366                return Err(ParseError::new(f, ParseErrorKind::ComplexTupleStruct));
367            };
368
369            let decorators = get_decorators(&f.attrs);
370
371            let ty = match get_serialized_as_type(&decorators) {
372                Some(ty) => parse_rust_type_from_string(&ty)?,
373                None => parse_rust_type(&f.ty)?,
374            };
375
376            RustItem::Alias(RustTypeAlias {
377                id: get_ident(Some(&s.ident), &s.attrs, &None),
378                ty: ty,
379                comments: parse_comment_attrs(&s.attrs),
380                generic_types,
381            })
382        }
383        // Unit structs or `None`
384        Fields::Unit => RustItem::Struct(RustStruct {
385            id: get_ident(Some(&s.ident), &s.attrs, &None),
386            generic_types,
387            fields: vec![],
388            comments: parse_comment_attrs(&s.attrs),
389            decorators,
390        }),
391    })
392}
393
394/// Parses an enum into a definition that more succinctly represents what
395/// typeshare needs to generate code for other languages.
396///
397/// This function can currently return something other than an enum, which is a
398/// hack.
399pub(crate) fn parse_enum(e: &ItemEnum) -> Result<RustItem, ParseError> {
400    let generic_types = e
401        .generics
402        .params
403        .iter()
404        .filter_map(|param| match param {
405            GenericParam::Type(type_param) => Some(TypeName::new(&type_param.ident)),
406            _ => None,
407        })
408        .collect();
409
410    let serde_rename_all = serde_rename_all(&e.attrs);
411    let decorators = get_decorators(&e.attrs);
412
413    // TODO: we shouldn't lie and return a type alias when parsing an enum. this
414    // is a temporary hack
415    if let Some(ty) = get_serialized_as_type(&decorators) {
416        return Ok(RustItem::Alias(RustTypeAlias {
417            id: get_ident(Some(&e.ident), &e.attrs, &None),
418            ty: parse_rust_type_from_string(&ty)?,
419            comments: parse_comment_attrs(&e.attrs),
420            generic_types,
421        }));
422    }
423
424    let original_enum_ident = TypeName::new(&e.ident);
425
426    // Grab the `#[serde(tag = "...", content = "...")]` values if they exist
427    let maybe_tag_key = get_tag_key(&e.attrs);
428    let maybe_content_key = get_content_key(&e.attrs);
429
430    // Parse all of the enum's variants
431    let variants = e
432        .variants
433        .iter()
434        // Filter out variants we've been told to skip
435        .filter(|v| !is_skipped(&v.attrs))
436        .map(|v| parse_enum_variant(v, &serde_rename_all))
437        .collect::<Result<Vec<_>, _>>()?;
438
439    // Check if the enum references itself recursively in any of its variants
440    let is_recursive = variants.iter().any(|v| match v {
441        RustEnumVariant::Unit(_) => false,
442        RustEnumVariant::Tuple { ty, .. } => ty.contains_type(&original_enum_ident),
443        RustEnumVariant::AnonymousStruct { fields, .. } => fields
444            .iter()
445            .any(|f| f.ty.contains_type(&original_enum_ident)),
446        _ => panic!("unrecgonized enum type"),
447    });
448
449    let shared = RustEnumShared {
450        id: get_ident(Some(&e.ident), &e.attrs, &None),
451        comments: parse_comment_attrs(&e.attrs),
452        decorators,
453        generic_types,
454        is_recursive,
455    };
456
457    // Figure out if we're dealing with a unit enum or an algebraic enum
458    if variants
459        .iter()
460        .all(|v| matches!(v, RustEnumVariant::Unit(_)))
461    {
462        // All enum variants are unit-type
463        if maybe_tag_key.is_some() {
464            return Err(ParseError::new(
465                &e,
466                ParseErrorKind::SerdeTagNotAllowed {
467                    enum_ident: original_enum_ident,
468                },
469            ));
470        }
471        if maybe_content_key.is_some() {
472            return Err(ParseError::new(
473                &e,
474                ParseErrorKind::SerdeContentNotAllowed {
475                    enum_ident: original_enum_ident,
476                },
477            ));
478        }
479
480        Ok(RustItem::Enum(RustEnum::Unit {
481            shared,
482            unit_variants: variants
483                .into_iter()
484                .map(|variant| match variant {
485                    RustEnumVariant::Unit(unit) => unit,
486                    _ => unreachable!("non-unit variant; this was checked earlier"),
487                })
488                .collect(),
489        }))
490    } else {
491        // At least one enum variant is either a tuple or an anonymous struct
492        Ok(RustItem::Enum(RustEnum::Algebraic {
493            tag_key: maybe_tag_key.ok_or_else(|| {
494                ParseError::new(
495                    &e,
496                    ParseErrorKind::SerdeTagRequired {
497                        enum_ident: original_enum_ident.clone(),
498                    },
499                )
500            })?,
501            content_key: maybe_content_key.ok_or_else(|| {
502                ParseError::new(
503                    &e,
504                    ParseErrorKind::SerdeContentRequired {
505                        enum_ident: original_enum_ident.clone(),
506                    },
507                )
508            })?,
509            shared,
510            variants,
511        }))
512    }
513}
514
515/// Parse an enum variant.
516fn parse_enum_variant(
517    v: &syn::Variant,
518    enum_serde_rename_all: &Option<String>,
519) -> Result<RustEnumVariant, ParseError> {
520    let shared = RustEnumVariantShared {
521        id: get_ident(Some(&v.ident), &v.attrs, enum_serde_rename_all),
522        comments: parse_comment_attrs(&v.attrs),
523    };
524
525    // Get the value of `#[serde(rename_all)]` for this specific variant rather
526    // than the overall enum
527    //
528    // The value of the attribute for the enum overall does not apply to enum
529    // variant fields.
530    let variant_serde_rename_all = serde_rename_all(&v.attrs);
531
532    match &v.fields {
533        syn::Fields::Unit => Ok(RustEnumVariant::Unit(shared)),
534        syn::Fields::Unnamed(associated_type) => {
535            let Some(field) = associated_type.unnamed.iter().exactly_one().ok() else {
536                return Err(ParseError::new(
537                    associated_type,
538                    ParseErrorKind::MultipleUnnamedAssociatedTypes,
539                ));
540            };
541            let decorators = get_decorators(&field.attrs);
542
543            let ty = match get_serialized_as_type(&decorators) {
544                Some(ty) => parse_rust_type_from_string(&ty)?,
545                None => parse_rust_type(&field.ty)?,
546            };
547
548            Ok(RustEnumVariant::Tuple { ty, shared })
549        }
550        syn::Fields::Named(fields_named) => Ok(RustEnumVariant::AnonymousStruct {
551            fields: fields_named
552                .named
553                .iter()
554                .filter(|f| !is_skipped(&f.attrs))
555                .map(|f| {
556                    let decorators = get_decorators(&f.attrs);
557
558                    let field_type = match get_serialized_as_type(&decorators) {
559                        Some(ty) => parse_rust_type_from_string(&ty)?,
560                        None => parse_rust_type(&f.ty)?,
561                    };
562
563                    let has_default = serde_default(&f.attrs);
564
565                    Ok(RustField {
566                        id: get_ident(f.ident.as_ref(), &f.attrs, &variant_serde_rename_all),
567                        ty: field_type,
568                        comments: parse_comment_attrs(&f.attrs),
569                        has_default,
570                        decorators,
571                    })
572                })
573                .try_collect()?,
574            shared,
575        }),
576    }
577}
578
579/// Parses a type alias into a definition that more succinctly represents what
580/// typeshare needs to generate code for other languages.
581pub(crate) fn parse_type_alias(t: &ItemType) -> Result<RustItem, ParseError> {
582    let decorators = get_decorators(&t.attrs);
583
584    let ty = match get_serialized_as_type(&decorators) {
585        Some(ty) => parse_rust_type_from_string(&ty)?,
586        None => parse_rust_type(&t.ty)?,
587    };
588
589    let generic_types = t
590        .generics
591        .params
592        .iter()
593        .filter_map(|param| match param {
594            GenericParam::Type(type_param) => Some(TypeName::new(&type_param.ident)),
595            _ => None,
596        })
597        .collect();
598
599    Ok(RustItem::Alias(RustTypeAlias {
600        id: get_ident(Some(&t.ident), &t.attrs, &None),
601        ty,
602        comments: parse_comment_attrs(&t.attrs),
603        generic_types,
604    }))
605}
606
607/// Parses a const variant.
608pub(crate) fn parse_const(c: &ItemConst) -> Result<RustItem, ParseError> {
609    let expr = parse_const_expr(&c.expr)?;
610    let decorators = get_decorators(&c.attrs);
611
612    // serialized_as needs to be supported in case the user wants to use a different type
613    // for the constant variable in a different language
614    let ty = match get_serialized_as_type(&decorators) {
615        Some(ty) => parse_rust_type_from_string(ty)?,
616        None => parse_rust_type(&c.ty)?,
617    };
618
619    match &ty {
620        RustType::Special(SpecialRustType::HashMap(_, _))
621        | RustType::Special(SpecialRustType::Vec(_))
622        | RustType::Special(SpecialRustType::Option(_)) => {
623            return Err(ParseError::new(&c.ty, ParseErrorKind::RustConstTypeInvalid));
624        }
625        RustType::Special(_) => (),
626        RustType::Simple { .. } => (),
627        _ => return Err(ParseError::new(&c.ty, ParseErrorKind::RustConstTypeInvalid)),
628    };
629
630    Ok(RustItem::Const(RustConst {
631        id: get_ident(Some(&c.ident), &c.attrs, &None),
632        ty,
633        expr,
634    }))
635}
636
637fn parse_const_expr(e: &Expr) -> Result<RustConstExpr, ParseError> {
638    let value = match e {
639        Expr::Lit(ExprLit {
640            lit: Lit::Int(lit), ..
641        }) => lit
642            .base10_parse()
643            .map_err(|_| ParseError::new(&lit, ParseErrorKind::RustConstExprInvalid))?,
644
645        Expr::Group(ExprGroup { expr, .. }) | Expr::Paren(ExprParen { expr, .. }) => {
646            return parse_const_expr(expr)
647        }
648        _ => return Err(ParseError::new(e, ParseErrorKind::RustConstExprInvalid)),
649    };
650
651    Ok(RustConstExpr::Int(value))
652}
653
654// Helpers
655
656/// Checks the given attrs for `#[typeshare]`
657pub(crate) fn has_typeshare_annotation(attrs: &[syn::Attribute]) -> bool {
658    attrs
659        .iter()
660        .flat_map(|attr| attr.path().segments.clone())
661        .any(|segment| segment.ident == TYPESHARE)
662}
663
664pub(crate) fn serde_rename_all(attrs: &[syn::Attribute]) -> Option<String> {
665    get_name_value_meta_items(attrs, "rename_all", SERDE).next()
666}
667
668pub(crate) fn get_serialized_as_type(decorators: &DecoratorSet) -> Option<&str> {
669    // TODO: what to do if there are multiple instances of serialized_as?
670    match decorators.get("serialized_as")?.first()? {
671        decorator::Value::String(s) => Some(s),
672        _ => None,
673    }
674}
675
676pub(crate) fn get_name_value_meta_items<'a>(
677    attrs: &'a [syn::Attribute],
678    name: &'a str,
679    ident: &'static str,
680) -> impl Iterator<Item = String> + 'a {
681    attrs.iter().flat_map(move |attr| {
682        get_meta_items(attr, ident)
683            .iter()
684            .filter_map(|arg| match arg {
685                Meta::NameValue(name_value) if name_value.path.is_ident(name) => {
686                    expr_to_string(&name_value.value)
687                }
688                _ => None,
689            })
690            .collect::<Vec<_>>()
691    })
692}
693
694/// Returns all arguments passed into `#[{ident}(...)]` where `{ident}` can be `serde` or `typeshare` attributes
695fn get_meta_items(attr: &syn::Attribute, ident: &str) -> Vec<Meta> {
696    if attr.path().is_ident(ident) {
697        attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
698            .iter()
699            .flat_map(|meta| meta.iter())
700            .cloned()
701            .collect()
702    } else {
703        Vec::default()
704    }
705}
706
707fn get_ident(ident: Option<&Ident>, attrs: &[syn::Attribute], rename_all: &Option<String>) -> Id {
708    let original = ident.map_or("???".to_string(), |id| id.to_string().replace("r#", ""));
709
710    let mut renamed = rename_all_to_case(original.clone(), rename_all);
711
712    if let Some(s) = serde_rename(attrs) {
713        renamed = s;
714    }
715
716    Id {
717        original: TypeName::new_string(original),
718        renamed: TypeName::new_string(renamed),
719    }
720}
721
722fn rename_all_to_case(original: String, case: &Option<String>) -> String {
723    // TODO: we'd like to replace this with `heck`, but it's not clear that
724    // we'd preserve backwards compatibility
725    match case {
726        None => original,
727        Some(value) => match value.as_str() {
728            "lowercase" => original.to_lowercase(),
729            "UPPERCASE" => original.to_uppercase(),
730            "PascalCase" => original.to_pascal_case(),
731            "camelCase" => original.to_camel_case(),
732            "snake_case" => original.to_snake_case(),
733            "SCREAMING_SNAKE_CASE" => original.to_screaming_snake_case(),
734            "kebab-case" => original.to_kebab_case(),
735            "SCREAMING-KEBAB-CASE" => original.to_screaming_kebab_case(),
736            _ => original,
737        },
738    }
739}
740
741fn serde_rename(attrs: &[syn::Attribute]) -> Option<String> {
742    get_name_value_meta_items(attrs, "rename", SERDE).next()
743}
744
745/// Parses any comment out of the given slice of attributes
746fn parse_comment_attrs(attrs: &[Attribute]) -> Vec<String> {
747    attrs
748        .iter()
749        .map(|attr| attr.meta.clone())
750        .filter_map(|meta| match meta {
751            Meta::NameValue(name_value) if name_value.path.is_ident("doc") => {
752                expr_to_string(&name_value.value)
753            }
754            _ => None,
755        })
756        .collect()
757}
758
759// `#[typeshare(skip)]` or `#[serde(skip)]`
760fn is_skipped(attrs: &[syn::Attribute]) -> bool {
761    attrs.iter().any(|attr| {
762        get_meta_items(attr, SERDE)
763            .into_iter()
764            .chain(get_meta_items(attr, TYPESHARE))
765            .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident("skip")))
766    })
767}
768
769fn serde_attr(attrs: &[syn::Attribute], ident: &str) -> bool {
770    attrs.iter().any(|attr| {
771        get_meta_items(attr, SERDE)
772            .iter()
773            .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident(ident)))
774    })
775}
776
777fn serde_default(attrs: &[syn::Attribute]) -> bool {
778    serde_attr(attrs, "default")
779}
780
781fn serde_flatten(attrs: &[syn::Attribute]) -> bool {
782    serde_attr(attrs, "flatten")
783}
784
785/// Checks the struct or enum for decorators like `#[typeshare(typescript = "readonly")]`
786/// Takes a slice of `syn::Attribute`, returns a `HashMap<language, Vec<decorator>>`, where `language` is `SupportedLanguage`
787/// and `decorator` is `FieldDecorator`. Field decorators are ordered in a `BTreeSet` for consistent code generation.
788fn get_decorators(attrs: &[Attribute]) -> DecoratorSet {
789    attrs
790        .iter()
791        .flat_map(|attr| match attr.meta {
792            Meta::List(ref meta) => Some(meta),
793            Meta::Path(_) | Meta::NameValue(_) => None,
794        })
795        .filter(|meta| meta.path.is_ident(TYPESHARE))
796        .filter_map(|meta| {
797            meta.parse_args_with(Punctuated::<KeyMaybeValue, Token![,]>::parse_terminated)
798                .ok()
799        })
800        .flatten()
801        .fold(DecoratorSet::new(), |mut set, decorator| {
802            set.entry(decorator.key).or_default().push(decorator.value);
803
804            set
805        })
806}
807
808fn expr_to_string(expr: &Expr) -> Option<String> {
809    match expr {
810        Expr::Lit(expr_lit) => literal_to_string(&expr_lit.lit),
811        _ => None,
812    }
813}
814
815fn literal_to_string(lit: &syn::Lit) -> Option<String> {
816    match lit {
817        syn::Lit::Str(str) => Some(str.value().trim().to_string()),
818        _ => None,
819    }
820}
821
822fn get_tag_key(attrs: &[syn::Attribute]) -> Option<String> {
823    get_name_value_meta_items(attrs, "tag", SERDE).next()
824}
825
826fn get_content_key(attrs: &[syn::Attribute]) -> Option<String> {
827    get_name_value_meta_items(attrs, "content", SERDE).next()
828}
829
830/// For parsing decorators: a single `key` or `key = "value"` in an attribute,
831/// where `key` is an identifier and `value` is some literal
832struct KeyMaybeValue {
833    key: String,
834    value: decorator::Value,
835}
836
837impl syn::parse::Parse for KeyMaybeValue {
838    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
839        let key: Ident = input.parse()?;
840        let eq: Option<syn::Token![=]> = input.parse()?;
841        let value: Option<syn::Lit> = eq.map(|_| input.parse()).transpose()?;
842
843        let value = match value {
844            None => decorator::Value::None,
845            Some(syn::Lit::Str(lit)) => decorator::Value::String(lit.value()),
846            Some(syn::Lit::Int(lit)) => decorator::Value::Int(lit.base10_parse()?),
847            Some(syn::Lit::Bool(lit)) => decorator::Value::Bool(lit.value),
848            Some(lit) => {
849                return Err(syn::Error::new(
850                    lit.span(),
851                    "unsupported decorator type (need string, int, or bool)",
852                ))
853            }
854        };
855
856        Ok(KeyMaybeValue {
857            key: key.to_string(),
858            value,
859        })
860    }
861}
862
863#[test]
864fn test_rename_all_to_case() {
865    let test_word = "test_case";
866
867    let tests = [
868        ("lowercase", "test_case"),
869        ("UPPERCASE", "TEST_CASE"),
870        ("PascalCase", "TestCase"),
871        ("camelCase", "testCase"),
872        ("snake_case", "test_case"),
873        ("SCREAMING_SNAKE_CASE", "TEST_CASE"),
874        ("kebab-case", "test-case"),
875        ("SCREAMING-KEBAB-CASE", "TEST-CASE"),
876        ("invalid case", "test_case"),
877    ];
878
879    for test in tests {
880        assert_eq!(
881            rename_all_to_case(test_word.to_string(), &Some(test.0.to_string())),
882            test.1
883        );
884    }
885}