deno_error_macro/
lib.rs

1// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2#![deny(clippy::unnecessary_wraps)]
3#![deny(clippy::print_stderr)]
4#![deny(clippy::print_stdout)]
5
6use proc_macro2::Ident;
7use proc_macro2::TokenStream;
8use quote::format_ident;
9use quote::quote;
10use quote::ToTokens;
11use syn::parse::Parse;
12use syn::parse::ParseStream;
13use syn::parse2;
14use syn::spanned::Spanned;
15use syn::Attribute;
16use syn::Data;
17use syn::DeriveInput;
18use syn::Error;
19use syn::Field;
20use syn::Fields;
21use syn::LitStr;
22use syn::Member;
23use syn::Meta;
24use syn::Token;
25use syn::Type;
26
27const IDENTIFIABLE_ERRORS: [&str; 7] = [
28  "Error",
29  "RangeError",
30  "TypeError",
31  "SyntaxError",
32  "URIError",
33  "ReferenceError",
34  "NotSupportedError",
35];
36
37#[proc_macro_derive(JsError, attributes(class, property, properties, inherit))]
38pub fn derive_js_error(
39  item: proc_macro::TokenStream,
40) -> proc_macro::TokenStream {
41  match js_error(item.into()) {
42    Ok(output) => output.into(),
43    Err(err) => err.into_compile_error().into(),
44  }
45}
46
47fn js_error(item: TokenStream) -> Result<TokenStream, Error> {
48  let input = parse2::<DeriveInput>(item)?;
49
50  let additional_properties = input
51    .attrs
52    .iter()
53    .filter_map(|attr| {
54      if attr.path().is_ident("property") {
55        Some(attr.parse_args())
56      } else {
57        None
58      }
59    })
60    .collect::<Result<Vec<AdditionalProperty>, Error>>()?;
61
62  let (class, out_properties) = match input.data {
63    Data::Enum(data) => {
64      let top_class_attr = input
65        .attrs
66        .into_iter()
67        .find_map(|attr| ClassAttrValue::from_attribute(attr).transpose())
68        .transpose()?;
69      if let Some(top_class_attr) = &top_class_attr {
70        if matches!(top_class_attr, ClassAttrValue::Inherit(_)) {
71          return Err(Error::new(
72            top_class_attr.to_tokens(&None).unwrap_err().span(),
73            "top level class attribute cannot be inherit",
74          ));
75        }
76      }
77
78      let mut get_class = vec![];
79      let mut get_properties = vec![];
80
81      for variant in data.variants {
82        let variant_additional_properties = variant
83          .attrs
84          .iter()
85          .filter_map(|attr| {
86            if attr.path().is_ident("property") {
87              Some(attr.parse_args())
88            } else {
89              None
90            }
91          })
92          .collect::<Result<Vec<AdditionalProperty>, Error>>()?;
93
94        let inherit_properties = variant
95          .attrs
96          .iter()
97          .find_map(|attr| {
98            if attr.path().is_ident("properties") {
99              Some(attr.parse_args::<InheritProperties>())
100            } else {
101              None
102            }
103          })
104          .transpose()?;
105
106        let class_attr = variant
107          .attrs
108          .into_iter()
109          .find_map(|attr| ClassAttrValue::from_attribute(attr).transpose())
110          .unwrap_or_else(|| {
111            top_class_attr.clone().ok_or_else(|| {
112              Error::new(variant.ident.span(), "class attribute is missing")
113            })
114          })?;
115
116        let (
117          class,
118          properties,
119          _inherit_class_member,
120          inherit_property_member,
121          parsed_properties,
122        ) = handle_variant_or_struct(
123          inherit_properties,
124          class_attr,
125          variant_additional_properties,
126          variant.fields,
127        )?;
128
129        let variant_ident = variant.ident;
130
131        let class_match_arm_identifiers = {
132          let mut parsed_properties = parsed_properties
133            .iter()
134            .enumerate()
135            .map(|(i, property)| {
136              let i = format_ident!("__{i}");
137              let member = &property.ident;
138              quote!(#member: #i,)
139            })
140            .collect::<Vec<_>>();
141
142          if let Some((member, _)) = &_inherit_class_member {
143            parsed_properties.push(quote!(#member: inherit,));
144          }
145
146          parsed_properties
147        };
148
149        let class_match_arm =
150          quote!(Self::#variant_ident { #(#class_match_arm_identifiers)* .. });
151
152        let match_arm_identifiers = {
153          let mut parsed_properties = parsed_properties
154            .into_iter()
155            .enumerate()
156            .map(|(i, property)| {
157              let i = format_ident!("__{i}");
158              let member = property.ident;
159              quote!(#member: #i,)
160            })
161            .collect::<Vec<_>>();
162
163          if let Some((member, _)) = &inherit_property_member {
164            parsed_properties.push(quote!(#member: inherit,));
165          }
166
167          parsed_properties
168        };
169
170        let match_arm =
171          quote!(Self::#variant_ident { #(#match_arm_identifiers)* .. });
172
173        get_class.push(quote! {
174          #class_match_arm => #class,
175        });
176
177        let properties =
178          properties.unwrap_or_else(|| quote!(std::iter::empty()));
179        get_properties.push(quote! {
180          #match_arm => Box::new(#properties),
181        });
182      }
183
184      (
185        quote! {
186          match self {
187            #(#get_class)*
188          }
189        },
190        Some(quote! {
191          match self {
192            #(#get_properties)*
193          }
194        }),
195      )
196    }
197    Data::Struct(data) => {
198      let inherit_properties = input
199        .attrs
200        .iter()
201        .find_map(|attr| {
202          if attr.path().is_ident("properties") {
203            Some(attr.parse_args::<InheritProperties>())
204          } else {
205            None
206          }
207        })
208        .transpose()?;
209
210      let class_attr = input
211        .attrs
212        .into_iter()
213        .find_map(|attr| ClassAttrValue::from_attribute(attr).transpose())
214        .unwrap_or_else(|| {
215          if data.fields.len() == 1 {
216            Ok(ClassAttrValue::Inherit(kw::inherit::default()))
217          } else {
218            Err(Error::new(
219              input.ident.span(),
220              "class attribute is missing and could not be inferred",
221            ))
222          }
223        })?;
224
225      let (
226        class,
227        properties,
228        inherit_class_member,
229        inherit_property_member,
230        parsed_properties,
231      ) = handle_variant_or_struct(
232        inherit_properties,
233        class_attr,
234        vec![],
235        data.fields,
236      )?;
237
238      let class_specifier_var = inherit_class_member.map(|(member, _)| {
239        quote! {
240          let inherit = &self.#member;
241        }
242      });
243
244      let property_specifier_var =
245        inherit_property_member.map(|(member, _)| {
246          quote! {
247            let inherit = &self.#member;
248          }
249        });
250
251      let parsed_properties = parsed_properties
252        .into_iter()
253        .enumerate()
254        .map(|(i, property)| {
255          let i = format_ident!("__{i}");
256          let member = property.ident;
257          quote! {
258            let #i = &self.#member;
259          }
260        })
261        .collect::<Vec<_>>();
262
263      let out_properties = if property_specifier_var.is_none()
264        && parsed_properties.is_empty()
265        && properties.is_none()
266      {
267        None
268      } else {
269        let properties =
270          properties.unwrap_or_else(|| quote!(std::iter::empty()));
271        Some(quote! {
272          Box::new({
273            #property_specifier_var
274            #(#parsed_properties)*
275            #properties
276          })
277        })
278      };
279
280      (
281        quote! {
282          #class_specifier_var
283          #class
284        },
285        out_properties,
286      )
287    }
288    Data::Union(_) => {
289      return Err(Error::new(input.span(), "Unions are not supported"))
290    }
291  };
292
293  let properties = if !additional_properties.is_empty() {
294    let additional_properties = additional_properties
295      .into_iter()
296      .map(|AdditionalProperty { name, value, .. }| quote!((#name.into(), ::deno_error::PropertyValue::from(#value))));
297
298    let additional_properties =
299      quote!([#(#additional_properties),*].into_iter());
300    if let Some(out_properties) = out_properties {
301      quote!(Box::new({ *#out_properties }.chain(#additional_properties)))
302    } else {
303      quote!(Box::new(#additional_properties))
304    }
305  } else {
306    let out_properties =
307      out_properties.unwrap_or_else(|| quote!(Box::new(std::iter::empty())));
308    quote!(#out_properties)
309  };
310
311  let ident = input.ident;
312
313  Ok(quote! {
314    #[allow(unused_qualifications)]
315    impl ::deno_error::JsErrorClass for #ident {
316      fn get_class(&self) -> ::std::borrow::Cow<'static, str> {
317        #class
318      }
319      fn get_message(&self) -> ::std::borrow::Cow<'static, str> {
320        self.to_string().into()
321      }
322      fn get_additional_properties(
323        &self
324      ) -> ::deno_error::AdditionalProperties {
325        #properties
326      }
327      fn get_ref(&self) -> &(dyn ::std::error::Error + Send + Sync + 'static) {
328        self
329      }
330    }
331  })
332}
333
334#[allow(clippy::type_complexity)]
335fn handle_variant_or_struct(
336  inherit_properties: Option<InheritProperties>,
337  class_attr: ClassAttrValue,
338  additional_properties: Vec<AdditionalProperty>,
339  fields: Fields,
340) -> Result<
341  (
342    TokenStream,
343    Option<TokenStream>,
344    Option<(Member, TokenStream)>,
345    Option<(Member, TokenStream)>,
346    Vec<ParsedFieldProperty>,
347  ),
348  Error,
349> {
350  let parsed_properties = get_properties_from_fields(&fields)?;
351
352  let inherit_properties =
353    inherit_properties.unwrap_or_else(|| match &class_attr {
354      ClassAttrValue::Inherit(kw) => InheritProperties::Inherit(*kw),
355      _ => InheritProperties::NoInherit(Default::default()),
356    });
357
358  let properties = if !parsed_properties.is_empty() {
359    let properties = parsed_properties
360      .iter()
361      .enumerate()
362      .map(|(i, property)| {
363        let i = format_ident!("__{i}");
364        let ident_str = &property.name;
365
366        quote! {
367          (::std::borrow::Cow::Borrowed(#ident_str), #i.into())
368        }
369      })
370      .collect::<Vec<_>>();
371
372    Some(quote!([#(#properties),*].into_iter()))
373  } else {
374    None
375  };
376
377  let (inherit_class_member, inherit_property_member) = match fields {
378    Fields::Named(fields_named) => {
379      let class_field = if fields_named.named.len() == 1
380        && matches!(class_attr, ClassAttrValue::Inherit(_))
381      {
382        fields_named.named.first()
383      } else {
384        fields_named.named.iter().find(get_inherit_attr_field)
385      };
386
387      let class_field = class_field.map(|field| {
388        (
389          Member::Named(field.ident.clone().unwrap()),
390          field_inherit_reference(field),
391        )
392      });
393
394      let property_field = if fields_named.named.len() == 1
395        && matches!(inherit_properties, InheritProperties::Inherit(_))
396      {
397        fields_named.named.first()
398      } else {
399        fields_named.named.iter().find(get_inherit_attr_field)
400      };
401
402      let property_field = property_field.map(|field| {
403        (
404          Member::Named(field.ident.clone().unwrap()),
405          field_inherit_reference(field),
406        )
407      });
408
409      (class_field, property_field)
410    }
411    Fields::Unnamed(fields_unnamed) => {
412      let class_field = if fields_unnamed.unnamed.len() == 1
413        && matches!(class_attr, ClassAttrValue::Inherit(_))
414      {
415        fields_unnamed.unnamed.first().map(|field| (0, field))
416      } else {
417        fields_unnamed
418          .unnamed
419          .iter()
420          .enumerate()
421          .find(|(_, field)| get_inherit_attr_field(field))
422      };
423
424      let class_field = class_field.map(|(i, field)| {
425        (
426          Member::Unnamed(syn::Index::from(i)),
427          field_inherit_reference(field),
428        )
429      });
430
431      let property_field = if fields_unnamed.unnamed.len() == 1
432        && matches!(inherit_properties, InheritProperties::Inherit(_))
433      {
434        fields_unnamed.unnamed.first().map(|field| (0, field))
435      } else {
436        fields_unnamed
437          .unnamed
438          .iter()
439          .enumerate()
440          .find(|(_, field)| get_inherit_attr_field(field))
441      };
442
443      let property_field = property_field.map(|(i, field)| {
444        (
445          Member::Unnamed(syn::Index::from(i)),
446          field_inherit_reference(field),
447        )
448      });
449
450      (class_field, property_field)
451    }
452    Fields::Unit => (None, None),
453  };
454
455  let class = class_attr.to_tokens(&inherit_class_member)?;
456
457  let properties = if let Some((_, tokens)) = &inherit_property_member {
458    let inherited_properties = quote!(::deno_error::JsErrorClass::get_additional_properties(
459      #tokens
460    ));
461
462    if let Some(properties) = properties {
463      Some(quote!(#properties.chain(#inherited_properties)))
464    } else {
465      Some(inherited_properties)
466    }
467  } else {
468    properties
469  };
470
471  let properties = if !additional_properties.is_empty() {
472    let additional_properties = additional_properties
473      .into_iter()
474      .map(|AdditionalProperty { name, value, .. }| {
475        // Check if the value is a literal number
476        match value {
477          syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(int_lit), .. }) => {
478            quote!((#name.into(), ::deno_error::PropertyValue::Number(#int_lit as f64)))
479          }
480          syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Float(float_lit), .. }) => {
481            quote!((#name.into(), ::deno_error::PropertyValue::Number(#float_lit)))
482          }
483          _ => {
484            quote!((#name.into(), #value.into()))
485          }
486        }
487      });
488
489    let additional_properties =
490      quote!([#(#additional_properties),*].into_iter());
491
492    if let Some(properties) = properties {
493      Some(quote!(#properties.chain(#additional_properties)))
494    } else {
495      Some(additional_properties)
496    }
497  } else {
498    properties
499  };
500
501  Ok((
502    class,
503    properties,
504    inherit_class_member,
505    inherit_property_member,
506    parsed_properties,
507  ))
508}
509
510fn get_inherit_attr_field(field: &&Field) -> bool {
511  field
512    .attrs
513    .iter()
514    .any(|attr| attr.path().is_ident("inherit"))
515}
516
517mod kw {
518  syn::custom_keyword!(class);
519  syn::custom_keyword!(property);
520  syn::custom_keyword!(inherit);
521  syn::custom_keyword!(no_inherit);
522}
523
524#[derive(Debug, Clone)]
525enum ClassAttrValue {
526  Lit(syn::LitStr),
527  Ident(Ident),
528  Inherit(kw::inherit),
529}
530
531impl ClassAttrValue {
532  fn from_attribute(attr: Attribute) -> Result<Option<Self>, Error> {
533    if attr.path().is_ident("class") {
534      let list = attr.meta.require_list()?;
535      let value = list.parse_args::<Self>()?;
536
537      match &value {
538        ClassAttrValue::Lit(lit) => {
539          if IDENTIFIABLE_ERRORS.contains(&lit.value().as_str()) {
540            return Err(Error::new(
541              lit.span(),
542              format!("An identifier can be used instead of '{}'", lit.value()),
543            ));
544          }
545        }
546        ClassAttrValue::Ident(ident) => {
547          let ident_str = ident.to_string();
548
549          // needs to call to_lowercase to handle _ since checking if its both
550          // lower or uppercase returns false
551          if ident_str.to_lowercase() != ident_str {
552            return Err(Error::new(
553              ident.span(),
554              "Identifier passed is not lowercase",
555            ));
556          }
557        }
558        ClassAttrValue::Inherit(_) => {}
559      }
560
561      return Ok(Some(value));
562    }
563
564    Ok(None)
565  }
566
567  fn to_tokens(
568    &self,
569    inherit_member: &Option<(Member, TokenStream)>,
570  ) -> Result<TokenStream, Error> {
571    let class_tokens = match self {
572      ClassAttrValue::Lit(lit) => quote!(::std::borrow::Cow::Borrowed(#lit)),
573      ClassAttrValue::Ident(ident) => {
574        let error_name =
575          format_ident!("{}_ERROR", ident.to_string().to_uppercase());
576        quote!(::std::borrow::Cow::Borrowed(::deno_error::builtin_classes::#error_name))
577      }
578      ClassAttrValue::Inherit(inherit) => {
579        let (_, tokens) = inherit_member.as_ref().ok_or_else(|| {
580          Error::new(
581            inherit.span,
582            "class attribute was set to inherit, but multiple fields are available and none was marked as inherit",
583          )
584        })?;
585
586        quote!(::deno_error::JsErrorClass::get_class(#tokens))
587      }
588    };
589
590    Ok(class_tokens)
591  }
592}
593
594impl Parse for ClassAttrValue {
595  fn parse(input: ParseStream) -> syn::Result<Self> {
596    let lookahead = input.lookahead1();
597
598    if lookahead.peek(syn::LitStr) {
599      Ok(Self::Lit(input.parse()?))
600    } else if lookahead.peek(kw::inherit) {
601      Ok(Self::Inherit(input.parse()?))
602    } else if lookahead.peek(syn::Ident) {
603      Ok(Self::Ident(input.parse()?))
604    } else if lookahead.peek(Token![type]) {
605      let type_token = input.parse::<Token![type]>()?;
606      Ok(Self::Ident(Ident::new("type", type_token.span)))
607    } else {
608      Err(lookahead.error())
609    }
610  }
611}
612
613#[derive(Debug)]
614struct ParsedFieldProperty {
615  ident: Member,
616  name: String,
617}
618
619fn get_properties_from_fields(
620  fields: &Fields,
621) -> Result<Vec<ParsedFieldProperty>, Error> {
622  const PROPERTY_IDENT: &str = "property";
623  let mut out_fields = vec![];
624
625  match fields {
626    Fields::Named(named) => {
627      for field in &named.named {
628        for attr in &field.attrs {
629          if attr.path().is_ident(PROPERTY_IDENT) {
630            let name = match &attr.meta {
631              Meta::Path(_) => None,
632              Meta::List(list) => {
633                return Err(Error::new(
634                  list.delimiter.span().open(),
635                  "expected `=`",
636                ));
637              }
638              Meta::NameValue(meta) => {
639                Some(parse2::<LitStr>(meta.value.to_token_stream())?.value())
640              }
641            };
642
643            let ident = field.ident.clone().unwrap();
644            let name = name.unwrap_or_else(|| ident.to_string());
645            let ident = Member::Named(field.ident.clone().unwrap());
646            out_fields.push(ParsedFieldProperty { name, ident });
647
648            break;
649          }
650        }
651      }
652    }
653    Fields::Unnamed(unnamed) => {
654      for (i, field) in unnamed.unnamed.iter().enumerate() {
655        for attr in &field.attrs {
656          if attr.path().is_ident(PROPERTY_IDENT) {
657            let name_value = attr.meta.require_name_value()?;
658            let name =
659              parse2::<LitStr>(name_value.value.to_token_stream())?.value();
660
661            let ident = Member::Unnamed(syn::Index::from(i));
662            out_fields.push(ParsedFieldProperty { name, ident });
663
664            break;
665          }
666        }
667      }
668    }
669    Fields::Unit => {}
670  }
671
672  Ok(out_fields)
673}
674
675#[derive(Debug)]
676struct AdditionalProperty {
677  name: LitStr,
678  _eq: Token![=],
679  value: syn::Expr,
680}
681
682impl Parse for AdditionalProperty {
683  fn parse(input: ParseStream) -> syn::Result<Self> {
684    Ok(Self {
685      name: input.parse()?,
686      _eq: input.parse()?,
687      value: input.parse()?,
688    })
689  }
690}
691
692#[derive(Debug)]
693#[allow(dead_code)]
694enum InheritProperties {
695  Inherit(kw::inherit),
696  NoInherit(kw::no_inherit),
697}
698
699impl Parse for InheritProperties {
700  fn parse(input: ParseStream) -> syn::Result<Self> {
701    let lookahead = input.lookahead1();
702
703    if lookahead.peek(kw::inherit) {
704      Ok(InheritProperties::Inherit(input.parse()?))
705    } else if lookahead.peek(kw::no_inherit) {
706      Ok(InheritProperties::NoInherit(input.parse()?))
707    } else {
708      Err(lookahead.error())
709    }
710  }
711}
712
713fn field_inherit_reference(field: &Field) -> TokenStream {
714  let is_wrapped = match &field.ty {
715    Type::Path(e) => {
716      if let Some(first) = e.path.segments.last() {
717        matches!(first.ident.to_string().as_str(), "Box" | "Rc" | "Arc")
718      } else {
719        false
720      }
721    }
722    _ => false,
723  };
724
725  if is_wrapped {
726    quote!(&**inherit)
727  } else {
728    quote!(inherit)
729  }
730}