Alioth Code Coverage

help.rs90.98%

1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::cmp::Ordering;
16use std::iter::zip;
17
18use proc_macro::TokenStream;
19use proc_macro2::TokenStream as TokenStream2;
20use quote::quote;
21use syn::meta::ParseNestedMeta;
22use syn::punctuated::Punctuated;
23use syn::{
24 Attribute, Data, DataEnum, DataStruct, DeriveInput, Expr, ExprLit, Fields, FieldsNamed,
25 FieldsUnnamed, Ident, Lit, Meta, MetaNameValue, Token, parse_macro_input,
26};
27
28fn get_doc_from_attrs(attrs: &[Attribute]) -> String {275x
29 let mut lines = vec![];275x
30 for attr in attrs.iter() {518x
31 let Meta::NameValue(MetaNameValue {
32 path,313x
33 value: Expr::Lit(ExprLit {
34 lit: Lit::Str(s), ..313x
35 }),
36 ..
37 }) = &attr.meta313x
38 else {
39 continue;205x
40 };
41 if path.is_ident("doc") {313x
42 let v = s.value();313x
43 let mut trimmed = v.trim_end();313x
44 if let Some(t) = trimmed.strip_prefix(' ') {313x
45 trimmed = t;305x
46 }305x
47 if !trimmed.is_empty() {313x
48 lines.push(trimmed.to_string());305x
49 }305x
50 }
51 }
52 lines.join("\n")275x
53}275x
54
55fn get_serde_aliases_from_attrs(ident: &Ident, attrs: &[Attribute]) -> Vec<String> {273x
56 let mut aliases = vec![];273x
57 for attr in attrs.iter() {514x
58 if !attr.path().is_ident("serde") {514x
59 continue;362x
60 }152x
61 let Ok(nested) = attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)152x
62 else {
63 continue;
64 };
65 for meta in nested {166x
66 let Meta::NameValue(MetaNameValue {
67 path,86x
68 value:
69 Expr::Lit(ExprLit {
70 lit: Lit::Str(s), ..86x
71 }),
72 ..
73 }) = meta86x
74 else {
75 continue;80x
76 };
77 if !path.is_ident("alias") {86x
78 continue;12x
79 }74x
80 aliases.push(s.value());74x
81 }
82 }
83 aliases.push(ident.to_string());273x
84 aliases.sort_by(|l, r| {273x
85 if l.len() != r.len() {80x
86 l.len().cmp(&r.len())34x
87 } else {
88 for (a, b) in zip(l.chars(), r.chars()) {46x
89 if a == b {46x
90 continue;
91 }46x
92 if a.is_lowercase() == b.is_lowercase() {46x
93 return a.cmp(&b);
94 } else if a.is_lowercase() {46x
95 return Ordering::Less;
96 } else {
97 return Ordering::Greater;46x
98 }
99 }
100 Ordering::Equal
101 }
102 });80x
103 aliases273x
104}273x
105
106fn has_aco_attr(attrs: &[Attribute], name: &str) -> bool {499x
107 for attr in attrs.iter() {893x
108 if !attr.path().is_ident("serde_aco") {893x
109 continue;881x
110 }12x
111 let mut found = false;12x
112 let has_name = |meta: ParseNestedMeta| {12x
113 if meta.path.is_ident(name) {12x
114 found = true;10x
115 }10x
116 Ok(())12x
117 };12x
118 if attr.parse_nested_meta(has_name).is_err() {12x
119 continue;
120 }12x
121 if found {12x
122 return true;10x
123 }2x
124 }
125 false489x
126}499x
127
128fn is_hidden(attrs: &[Attribute]) -> bool {283x
129 has_aco_attr(attrs, "hide")283x
130}283x
131
132fn is_flattened(attrs: &[Attribute]) -> bool {216x
133 has_aco_attr(attrs, "flatten")216x
134}216x
135
136fn derive_named_struct_help(name: &Ident, fields: &FieldsNamed) -> TokenStream2 {96x
137 let mut field_docs = Vec::new();96x
138 for field in &fields.named {219x
139 if is_hidden(&field.attrs) {219x
140 continue;3x
141 }216x
142 let aliases;
143 let ident = if is_flattened(&field.attrs) {216x
144 ""2x
145 } else {
146 aliases = get_serde_aliases_from_attrs(field.ident.as_ref().unwrap(), &field.attrs);214x
147 &aliases[0]214x
148 };
149 let ty = &field.ty;216x
150 let doc = get_doc_from_attrs(&field.attrs);216x
151 field_docs.push(quote! {216x
152 FieldHelp {
153 ident: #ident,
154 doc: #doc,
155 ty: <#ty as Help>::HELP,
156 }
157 })
158 }
159
160 quote! {96x
161 TypedHelp::Struct{
162 name: stringify!(#name),
163 fields: &[#(#field_docs,)*],
164 }
165 }
166}96x
167
168fn derive_unnamed_struct_help(fields: &FieldsUnnamed) -> TokenStream2 {36x
169 if let Some(first) = fields.unnamed.first() {36x
170 let ty = &first.ty;36x
171 quote! { <#ty as Help>::HELP }36x
172 } else if fields.unnamed.is_empty() {
173 quote! { TypedHelp::Unit }
174 } else {
175 panic!("Unnamed struct must have only one field")
176 }
177}36x
178
179fn derive_struct_help(name: &Ident, data: &DataStruct) -> TokenStream2 {95x
180 match &data.fields {95x
181 Fields::Named(fields) => derive_named_struct_help(name, fields),89x
182 Fields::Unnamed(fields) => derive_unnamed_struct_help(fields),6x
183 Fields::Unit => quote! { TypedHelp::Unit },
184 }
185}95x
186
187fn derive_enum_help(name: &Ident, data: &DataEnum) -> TokenStream2 {41x
188 let mut variant_docs = vec![];41x
189 for variant in data.variants.iter() {65x
190 if is_hidden(&variant.attrs) {64x
191 continue;5x
192 }59x
193 let doc = get_doc_from_attrs(&variant.attrs);59x
194 let ty = match &variant.fields {59x
195 Fields::Unit => quote! {TypedHelp::Unit},22x
196 Fields::Named(fields) => derive_named_struct_help(name, fields),7x
197 Fields::Unnamed(fields) => derive_unnamed_struct_help(fields),30x
198 };
199 let aliases = get_serde_aliases_from_attrs(&variant.ident, &variant.attrs);59x
200 let ident = &aliases[0];59x
201 variant_docs.push(quote! {59x
202 FieldHelp {
203 ident: #ident,
204 doc: #doc,
205 ty: #ty,
206 }
207 })
208 }
209 quote! {41x
210 TypedHelp::Enum {
211 name: stringify!(#name),
212 variants: &[#(#variant_docs,)*],
213 }
214 }
215}41x
216
217pub fn derive_help(input: TokenStream) -> TokenStream {136x
218 let input = parse_macro_input!(input as DeriveInput);136x
219 let ty_name = &input.ident;136x
220 let body = match &input.data {136x
221 Data::Struct(data) => derive_struct_help(ty_name, data),95x
222 Data::Enum(data) => derive_enum_help(ty_name, data),41x
223 Data::Union(_) => unimplemented!("Data::Union not supported"),
224 };
225 TokenStream::from(quote! {136x
226 const _:() = {
227 use ::serde_aco::{Help, TypedHelp, FieldHelp};
228 impl Help for #ty_name {
229 const HELP: TypedHelp = #body;
230 }
231 };
232 })
233}136x
234