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.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/// Input data for parsing each source file.
101#[derive(Debug)]
102pub struct ParserInput {
103    /// Rust source file path.
104    file_path: PathBuf,
105    /// The crate name the source file belongs to, if we could detect it
106    crate_name: Option<CrateName>,
107}
108
109/// Walk the source folder and collect all parser inputs.
110pub 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
127// /// This function produces the `import_candidates`
128// /// Collect all the typeshared types into a mapping of crate names to typeshared types. This
129// /// mapping is used to lookup and generated import statements for generated files.
130// pub fn all_types(file_mappings: &HashMap<CrateName, ParsedData>) -> CrateTypes {
131//     file_mappings
132//         .iter()
133//         .map(|(crate_name, parsed_data)| (crate_name, &parsed_data.type_names))
134//         .fold(
135//             HashMap::new(),
136//             |mut import_map: CrateTypes, (crate_name, type_names)| {
137//                 match import_map.entry(crate_name.clone()) {
138//                     Entry::Occupied(mut e) => {
139//                         e.get_mut().extend(type_names.iter().cloned());
140//                     }
141//                     Entry::Vacant(e) => {
142//                         e.insert(type_names.clone());
143//                     }
144//                 }
145//                 import_map
146//             },
147//         )
148// }
149
150fn 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
165/// Collect all the parsed sources into a mapping of crate name to parsed data.
166pub 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            // Performance nit: we don't need to clone in the error case;
175            // map_err is taking unconditional ownership unnecessarily
176            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
255/// Check if we have not parsed any relavent typehsared types.
256fn 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
260/// Parse the given Rust source string into `ParsedData`.
261pub fn parse(
262    source_code: &str,
263    ignored_types: &[&str],
264    file_mode: FilesMode<&CrateName>,
265) -> Result<Option<ParsedData>, ParseErrorSet> {
266    // We will only produce output for files that contain the `#[typeshare]`
267    // attribute, so this is a quick and easy performance win
268    if !source_code.contains("#[typeshare") {
269        return Ok(None);
270    }
271
272    // Parse and process the input, ensuring we parse only items marked with
273    // `#[typeshare]`
274    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
283/// Parses a struct into a definition that more succinctly represents what
284/// typeshare needs to generate code for other languages.
285///
286/// This function can currently return something other than a struct, which is a
287/// hack.
288pub(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    // Check if this struct should be parsed as a type alias.
304    // TODO: we shouldn't lie and return a type alias when parsing a struct. this
305    // is a temporary hack
306    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            ty: 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        // Structs
317        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        // Tuple structs
355        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                ty: ty,
370                comments: parse_comment_attrs(&s.attrs),
371                generic_types,
372            })
373        }
374        // Unit structs or `None`
375        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
385/// Parses an enum into a definition that more succinctly represents what
386/// typeshare needs to generate code for other languages.
387///
388/// This function can currently return something other than an enum, which is a
389/// hack.
390pub(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    // TODO: we shouldn't lie and return a type alias when parsing an enum. this
405    // is a temporary hack
406    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            ty: 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    // Grab the `#[serde(tag = "...", content = "...")]` values if they exist
418    let maybe_tag_key = get_tag_key(&e.attrs);
419    let maybe_content_key = get_content_key(&e.attrs);
420
421    // Parse all of the enum's variants
422    let variants = e
423        .variants
424        .iter()
425        // Filter out variants we've been told to skip
426        .filter(|v| !is_skipped(&v.attrs))
427        .map(|v| parse_enum_variant(v, &serde_rename_all))
428        .collect::<Result<Vec<_>, _>>()?;
429
430    // Check if the enum references itself recursively in any of its variants
431    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    // Figure out if we're dealing with a unit enum or an algebraic enum
449    if variants
450        .iter()
451        .all(|v| matches!(v, RustEnumVariant::Unit(_)))
452    {
453        // All enum variants are unit-type
454        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        // At least one enum variant is either a tuple or an anonymous struct
483        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
506/// Parse an enum variant.
507fn 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    // Get the value of `#[serde(rename_all)]` for this specific variant rather
517    // than the overall enum
518    //
519    // The value of the attribute for the enum overall does not apply to enum
520    // variant fields.
521    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
570/// Parses a type alias into a definition that more succinctly represents what
571/// typeshare needs to generate code for other languages.
572pub(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        ty,
593        comments: parse_comment_attrs(&t.attrs),
594        generic_types,
595    }))
596}
597
598/// Parses a const variant.
599pub(crate) fn parse_const(c: &ItemConst) -> Result<RustItem, ParseError> {
600    let expr = parse_const_expr(&c.expr)?;
601    let decorators = get_decorators(&c.attrs);
602
603    // serialized_as needs to be supported in case the user wants to use a different type
604    // for the constant variable in a different language
605    let ty = match get_serialized_as_type(&decorators) {
606        Some(ty) => parse_rust_type_from_string(ty)?,
607        None => parse_rust_type(&c.ty)?,
608    };
609
610    match &ty {
611        RustType::Special(SpecialRustType::HashMap(_, _))
612        | RustType::Special(SpecialRustType::Vec(_))
613        | RustType::Special(SpecialRustType::Option(_)) => {
614            return Err(ParseError::new(&c.ty, ParseErrorKind::RustConstTypeInvalid));
615        }
616        RustType::Special(_) => (),
617        RustType::Simple { .. } => (),
618        _ => return Err(ParseError::new(&c.ty, ParseErrorKind::RustConstTypeInvalid)),
619    };
620
621    Ok(RustItem::Const(RustConst {
622        id: get_ident(Some(&c.ident), &c.attrs, &None),
623        ty,
624        expr,
625    }))
626}
627
628fn parse_const_expr(e: &Expr) -> Result<RustConstExpr, ParseError> {
629    let value = match e {
630        Expr::Lit(ExprLit {
631            lit: Lit::Int(lit), ..
632        }) => lit
633            .base10_parse()
634            .map_err(|_| ParseError::new(&lit, ParseErrorKind::RustConstExprInvalid))?,
635
636        Expr::Group(ExprGroup { expr, .. }) | Expr::Paren(ExprParen { expr, .. }) => {
637            return parse_const_expr(expr)
638        }
639        _ => return Err(ParseError::new(e, ParseErrorKind::RustConstExprInvalid)),
640    };
641
642    Ok(RustConstExpr::Int(value))
643}
644
645// Helpers
646
647/// Checks the given attrs for `#[typeshare]`
648pub(crate) fn has_typeshare_annotation(attrs: &[syn::Attribute]) -> bool {
649    attrs
650        .iter()
651        .flat_map(|attr| attr.path().segments.clone())
652        .any(|segment| segment.ident == TYPESHARE)
653}
654
655pub(crate) fn serde_rename_all(attrs: &[syn::Attribute]) -> Option<String> {
656    get_name_value_meta_items(attrs, "rename_all", SERDE).next()
657}
658
659pub(crate) fn get_serialized_as_type(decorators: &DecoratorSet) -> Option<&str> {
660    // TODO: what to do if there are multiple instances of serialized_as?
661    match decorators.get("serialized_as")?.first()? {
662        decorator::Value::String(s) => Some(s),
663        _ => None,
664    }
665}
666
667pub(crate) fn get_name_value_meta_items<'a>(
668    attrs: &'a [syn::Attribute],
669    name: &'a str,
670    ident: &'static str,
671) -> impl Iterator<Item = String> + 'a {
672    attrs.iter().flat_map(move |attr| {
673        get_meta_items(attr, ident)
674            .iter()
675            .filter_map(|arg| match arg {
676                Meta::NameValue(name_value) if name_value.path.is_ident(name) => {
677                    expr_to_string(&name_value.value)
678                }
679                _ => None,
680            })
681            .collect::<Vec<_>>()
682    })
683}
684
685/// Returns all arguments passed into `#[{ident}(...)]` where `{ident}` can be `serde` or `typeshare` attributes
686fn get_meta_items(attr: &syn::Attribute, ident: &str) -> Vec<Meta> {
687    if attr.path().is_ident(ident) {
688        attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
689            .iter()
690            .flat_map(|meta| meta.iter())
691            .cloned()
692            .collect()
693    } else {
694        Vec::default()
695    }
696}
697
698fn get_ident(ident: Option<&Ident>, attrs: &[syn::Attribute], rename_all: &Option<String>) -> Id {
699    let original = ident.map_or("???".to_string(), |id| id.to_string().replace("r#", ""));
700
701    let mut renamed = rename_all_to_case(original.clone(), rename_all);
702
703    if let Some(s) = serde_rename(attrs) {
704        renamed = s;
705    }
706
707    Id {
708        original: TypeName::new_string(original),
709        renamed: TypeName::new_string(renamed),
710    }
711}
712
713fn rename_all_to_case(original: String, case: &Option<String>) -> String {
714    // TODO: we'd like to replace this with `heck`, but it's not clear that
715    // we'd preserve backwards compatibility
716    match case {
717        None => original,
718        Some(value) => match value.as_str() {
719            "lowercase" => original.to_lowercase(),
720            "UPPERCASE" => original.to_uppercase(),
721            "PascalCase" => original.to_pascal_case(),
722            "camelCase" => original.to_camel_case(),
723            "snake_case" => original.to_snake_case(),
724            "SCREAMING_SNAKE_CASE" => original.to_screaming_snake_case(),
725            "kebab-case" => original.to_kebab_case(),
726            "SCREAMING-KEBAB-CASE" => original.to_screaming_kebab_case(),
727            _ => original,
728        },
729    }
730}
731
732fn serde_rename(attrs: &[syn::Attribute]) -> Option<String> {
733    get_name_value_meta_items(attrs, "rename", SERDE).next()
734}
735
736/// Parses any comment out of the given slice of attributes
737fn parse_comment_attrs(attrs: &[Attribute]) -> Vec<String> {
738    attrs
739        .iter()
740        .map(|attr| attr.meta.clone())
741        .filter_map(|meta| match meta {
742            Meta::NameValue(name_value) if name_value.path.is_ident("doc") => {
743                expr_to_string(&name_value.value)
744            }
745            _ => None,
746        })
747        .collect()
748}
749
750// `#[typeshare(skip)]` or `#[serde(skip)]`
751fn is_skipped(attrs: &[syn::Attribute]) -> bool {
752    attrs.iter().any(|attr| {
753        get_meta_items(attr, SERDE)
754            .into_iter()
755            .chain(get_meta_items(attr, TYPESHARE))
756            .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident("skip")))
757    })
758}
759
760fn serde_attr(attrs: &[syn::Attribute], ident: &str) -> bool {
761    attrs.iter().any(|attr| {
762        get_meta_items(attr, SERDE)
763            .iter()
764            .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident(ident)))
765    })
766}
767
768fn serde_default(attrs: &[syn::Attribute]) -> bool {
769    serde_attr(attrs, "default")
770}
771
772fn serde_flatten(attrs: &[syn::Attribute]) -> bool {
773    serde_attr(attrs, "flatten")
774}
775
776/// Checks the struct or enum for decorators like `#[typeshare(typescript = "readonly")]`
777/// Takes a slice of `syn::Attribute`, returns a `HashMap<language, Vec<decorator>>`, where `language` is `SupportedLanguage`
778/// and `decorator` is `FieldDecorator`. Field decorators are ordered in a `BTreeSet` for consistent code generation.
779fn get_decorators(attrs: &[Attribute]) -> DecoratorSet {
780    attrs
781        .iter()
782        .flat_map(|attr| match attr.meta {
783            Meta::List(ref meta) => Some(meta),
784            Meta::Path(_) | Meta::NameValue(_) => None,
785        })
786        .filter(|meta| meta.path.is_ident(TYPESHARE))
787        .filter_map(|meta| {
788            meta.parse_args_with(Punctuated::<KeyMaybeValue, Token![,]>::parse_terminated)
789                .ok()
790        })
791        .flatten()
792        .fold(DecoratorSet::new(), |mut set, decorator| {
793            set.entry(decorator.key).or_default().push(decorator.value);
794
795            set
796        })
797}
798
799fn expr_to_string(expr: &Expr) -> Option<String> {
800    match expr {
801        Expr::Lit(expr_lit) => literal_to_string(&expr_lit.lit),
802        _ => None,
803    }
804}
805
806fn literal_to_string(lit: &syn::Lit) -> Option<String> {
807    match lit {
808        syn::Lit::Str(str) => Some(str.value().trim().to_string()),
809        _ => None,
810    }
811}
812
813fn get_tag_key(attrs: &[syn::Attribute]) -> Option<String> {
814    get_name_value_meta_items(attrs, "tag", SERDE).next()
815}
816
817fn get_content_key(attrs: &[syn::Attribute]) -> Option<String> {
818    get_name_value_meta_items(attrs, "content", SERDE).next()
819}
820
821/// For parsing decorators: a single `key` or `key = "value"` in an attribute,
822/// where `key` is an identifier and `value` is some literal
823struct KeyMaybeValue {
824    key: String,
825    value: decorator::Value,
826}
827
828impl syn::parse::Parse for KeyMaybeValue {
829    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
830        let key: Ident = input.parse()?;
831        let eq: Option<syn::Token![=]> = input.parse()?;
832        let value: Option<syn::Lit> = eq.map(|_| input.parse()).transpose()?;
833
834        let value = match value {
835            None => decorator::Value::None,
836            Some(syn::Lit::Str(lit)) => decorator::Value::String(lit.value()),
837            Some(syn::Lit::Int(lit)) => decorator::Value::Int(lit.base10_parse()?),
838            Some(syn::Lit::Bool(lit)) => decorator::Value::Bool(lit.value),
839            Some(lit) => {
840                return Err(syn::Error::new(
841                    lit.span(),
842                    "unsupported decorator type (need string, int, or bool)",
843                ))
844            }
845        };
846
847        Ok(KeyMaybeValue {
848            key: key.to_string(),
849            value,
850        })
851    }
852}
853
854#[test]
855fn test_rename_all_to_case() {
856    let test_word = "test_case";
857
858    let tests = [
859        ("lowercase", "test_case"),
860        ("UPPERCASE", "TEST_CASE"),
861        ("PascalCase", "TestCase"),
862        ("camelCase", "testCase"),
863        ("snake_case", "test_case"),
864        ("SCREAMING_SNAKE_CASE", "TEST_CASE"),
865        ("kebab-case", "test-case"),
866        ("SCREAMING-KEBAB-CASE", "TEST-CASE"),
867        ("invalid case", "test_case"),
868    ];
869
870    for test in tests {
871        assert_eq!(
872            rename_all_to_case(test_word.to_string(), &Some(test.0.to_string())),
873            test.1
874        );
875    }
876}