Skip to main content

typeshare_engine/
parser.rs

1//! Source file parsing.
2use 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/// An enum that encapsulates units of code generation for Typeshare.
36/// Analogous to `syn::Item`, even though our variants are more limited.
37#[non_exhaustive]
38#[derive(Debug, Clone)]
39pub enum RustItem {
40    /// A `struct` definition
41    Struct(RustStruct),
42    /// An `enum` definition
43    Enum(RustEnum),
44    /// A `type` definition or newtype struct.
45    Alias(RustTypeAlias),
46    /// A `const` definition
47    Const(RustConst),
48}
49
50/// The results of parsing Rust source input.
51#[derive(Default, Debug)]
52pub struct ParsedData {
53    /// Structs defined in the source
54    pub structs: Vec<RustStruct>,
55
56    /// Enums defined in the source
57    pub enums: Vec<RustEnum>,
58
59    /// Type aliases defined in the source
60    pub aliases: Vec<RustTypeAlias>,
61
62    /// Constant variables defined in the source
63    pub consts: Vec<RustConst>,
64
65    /// Imports used by this file
66    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        // currently we ignore consts, which aren't types. May revisit this
92        // later.
93
94        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/// Input data for parsing each source file.
114#[derive(Debug)]
115pub struct ParserInput {
116    /// Rust source file path.
117    file_path: PathBuf,
118    /// The crate name the source file belongs to, if we could detect it
119    crate_name: Option<CrateName>,
120}
121
122/// Walk the source folder and collect all parser inputs.
123pub 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
140// /// This function produces the `import_candidates`
141// /// Collect all the typeshared types into a mapping of crate names to typeshared types. This
142// /// mapping is used to lookup and generated import statements for generated files.
143// pub fn all_types(file_mappings: &HashMap<CrateName, ParsedData>) -> CrateTypes {
144//     file_mappings
145//         .iter()
146//         .map(|(crate_name, parsed_data)| (crate_name, &parsed_data.type_names))
147//         .fold(
148//             HashMap::new(),
149//             |mut import_map: CrateTypes, (crate_name, type_names)| {
150//                 match import_map.entry(crate_name.clone()) {
151//                     Entry::Occupied(mut e) => {
152//                         e.get_mut().extend(type_names.iter().cloned());
153//                     }
154//                     Entry::Vacant(e) => {
155//                         e.insert(type_names.clone());
156//                     }
157//                 }
158//                 import_map
159//             },
160//         )
161// }
162
163fn 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
178/// Collect all the parsed sources into a mapping of crate name to parsed data.
179pub 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            // Performance nit: we don't need to clone in the error case;
189            // map_err is taking unconditional ownership unnecessarily
190            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
271/// Check if we have not parsed any relavent typehsared types.
272fn 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
279/// Parse the given Rust source string into `ParsedData`.
280pub 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    // We will only produce output for files that contain the `#[typeshare]`
287    // attribute, so this is a quick and easy performance win
288    if !source_code.contains("#[typeshare") {
289        return Ok(None);
290    }
291
292    // Parse and process the input, ensuring we parse only items marked with
293    // `#[typeshare]`
294    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
303/// Parses a struct into a definition that more succinctly represents what
304/// typeshare needs to generate code for other languages.
305///
306/// This function can currently return something other than a struct, which is a
307/// hack.
308pub(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    // Check if this struct should be parsed as a type alias.
327    // TODO: we shouldn't lie and return a type alias when parsing a struct. this
328    // is a temporary hack
329    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        // Structs
341        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        // Tuple structs
383        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        // Unit structs or `None`
404        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
414/// Parses an enum into a definition that more succinctly represents what
415/// typeshare needs to generate code for other languages.
416///
417/// This function can currently return something other than an enum, which is a
418/// hack.
419pub(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    // TODO: we shouldn't lie and return a type alias when parsing an enum. this
434    // is a temporary hack
435    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    // Grab the `#[serde(tag = "...", content = "...")]` values if they exist
448    let maybe_tag_key = get_tag_key(&e.attrs);
449    let maybe_content_key = get_content_key(&e.attrs);
450
451    // Parse all of the enum's variants
452    let variants = e
453        .variants
454        .iter()
455        // Filter out variants we've been told to skip
456        .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    // Check if the enum references itself recursively in any of its variants
465    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    // Figure out if we're dealing with a unit enum or an algebraic enum
483    if variants
484        .iter()
485        .all(|v| matches!(v, RustEnumVariant::Unit(_)))
486    {
487        // All enum variants are unit-type
488        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        // At least one enum variant is either a tuple or an anonymous struct
517        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
540/// Parse an enum variant.
541fn 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    // Get the value of `#[serde(rename_all)]` for this specific variant rather
552    // than the overall enum
553    //
554    // The value of the attribute for the enum overall does not apply to enum
555    // variant fields.
556    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
613/// Parses a type alias into a definition that more succinctly represents what
614/// typeshare needs to generate code for other languages.
615pub(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
642/// Parses a const variant.
643pub(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    // serialized_as needs to be supported in case the user wants to use a different type
648    // for the constant variable in a different language
649    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
689// Helpers
690
691/// Checks the given attrs for `#[typeshare]`
692pub(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    // TODO: what to do if there are multiple instances of serialized_as?
705    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
729/// Returns all arguments passed into `#[{ident}(...)]` where `{ident}` can be `serde` or `typeshare` attributes
730fn 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    // TODO: we'd like to replace this with `heck`, but it's not clear that
759    // we'd preserve backwards compatibility
760    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
780/// Parses any comment out of the given slice of attributes
781fn 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
794// `#[typeshare(skip)]` or `#[serde(skip)]`
795fn 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
820/// Checks the struct or enum for decorators like `#[typeshare(typescript = "readonly")]`
821/// Takes a slice of `syn::Attribute`, returns a `HashMap<language, Vec<decorator>>`, where `language` is `SupportedLanguage`
822/// and `decorator` is `FieldDecorator`. Field decorators are ordered in a `BTreeSet` for consistent code generation.
823fn 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
837/// Check if the thing tagged by these attributes (type, field, whatever) is
838/// accepted by at least one of the given valid OSes. This returns true for a
839/// given OS so long as it isn't explicitly rejected.
840pub 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
875/// For parsing decorators: a single `key` or `key = "value"` in an attribute,
876/// where `key` is an identifier and `value` is some literal
877struct 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        // Use `parse_any` to allow parsing keyword identifiers like `type`
885        let key = input.call(Ident::parse_any)?;
886
887        // If this is `key = value,`, parse a literal
888        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        // If this is `key(...)`, parse a nested decorator set
902        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        // If this is `key,`, the key is plain, no value attached
920        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    // Regression test for an earlier breakage
1045    #[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}