bon_macros/util/
ide.rs
1#![deny(
3 clippy::unwrap_used,
4 clippy::expect_used,
5 clippy::panic,
6 clippy::unreachable,
7 clippy::unimplemented,
8 clippy::todo,
9 clippy::exit
10)]
11
12use crate::util::prelude::*;
13use proc_macro2::TokenTree;
14use syn::parse::{Parse, ParseStream, Parser};
15use syn::{token, Token};
16
17pub(crate) fn parse_comma_separated_meta(input: ParseStream<'_>) -> syn::Result<Vec<Meta>> {
18 let mut output = vec![];
19
20 while !input.is_empty() {
21 let value = input.parse::<Meta>()?;
22
23 output.push(value);
24
25 while !input.is_empty() {
26 if input.peek(Token![,]) {
27 input.parse::<Token![,]>()?;
28 break;
29 }
30
31 input.parse::<TokenTree>()?;
33 }
34 }
35
36 Ok(output)
37}
38
39#[derive(Clone, Debug)]
40pub(crate) enum Meta {
41 None,
42 Path(syn::Path),
43 List(MetaList),
44 NameValue(MetaNameValue),
45}
46
47impl Parse for Meta {
48 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
49 let path = loop {
50 let path = input.call(syn::Path::parse_mod_style).ok();
51
52 if let Some(path) = path {
53 break path;
54 }
55
56 if input.parse::<TokenTree>().is_err() {
57 return Ok(Self::None);
58 }
59 };
60
61 let meta = if input.peek(token::Paren) {
62 let content;
63 syn::parenthesized!(content in input);
64
65 Self::List(MetaList {
66 path,
67 tokens: content.parse()?,
68 })
69 } else if input.peek(token::Bracket) {
70 let content;
71 syn::bracketed!(content in input);
72
73 Self::List(MetaList {
74 path,
75 tokens: content.parse()?,
76 })
77 } else if input.peek(token::Brace) {
78 let content;
79 syn::braced!(content in input);
80
81 Self::List(MetaList {
82 path,
83 tokens: content.parse()?,
84 })
85 } else if input.peek(Token![=]) {
86 Self::NameValue(MetaNameValue {
87 path,
88 eq_token: input.parse()?,
89 value: input.parse().ok(),
90 })
91 } else {
92 Self::Path(path)
93 };
94
95 Ok(meta)
96 }
97}
98
99#[derive(Clone, Debug)]
100pub(crate) struct MetaList {
101 pub(crate) path: syn::Path,
102 pub(crate) tokens: TokenStream,
103}
104
105#[derive(Clone, Debug)]
106pub(crate) struct MetaNameValue {
107 pub(crate) path: syn::Path,
108
109 #[allow(dead_code)]
110 pub(crate) eq_token: syn::Token![=],
111
112 #[allow(dead_code)]
113 pub(crate) value: Option<syn::Expr>,
114}
115
116fn paths_from_meta(meta: Vec<Meta>) -> Vec<syn::Path> {
117 meta.into_iter()
118 .filter_map(|meta| match meta {
119 Meta::Path(path) => Some(path),
120 Meta::NameValue(meta) => Some(meta.path),
121 Meta::List(meta) => Some(meta.path),
122 Meta::None => None,
123 })
124 .collect()
125}
126
127pub(crate) fn generate_completion_triggers(meta: Vec<Meta>) -> TokenStream {
138 let bon = meta
139 .iter()
140 .find_map(|meta| match meta {
141 Meta::NameValue(meta) if meta.path.is_ident("crate") => Some(meta.value.as_ref()),
142 _ => None,
143 })
144 .flatten()
145 .and_then(|expr| Some(expr.require_path_mod_style().ok()?.clone()))
146 .unwrap_or_else(|| syn::parse_quote!(::bon));
147
148 let completions = CompletionsSchema::with_children(
149 "builder_top_level",
150 vec![
151 CompletionsSchema::leaf("builder_type"),
152 CompletionsSchema::leaf("start_fn"),
153 CompletionsSchema::leaf("finish_fn"),
154 CompletionsSchema::leaf("state_mod"),
155 CompletionsSchema::leaf("on").set_custom_filter(|meta| {
156 if let Some(first) = meta.first() {
157 if let Meta::Path(path) = first {
158 if path.is_ident("into")
159 || path.is_ident("required")
160 || path.is_ident("overwritable")
161 {
162 return;
163 }
164 }
165
166 meta.remove(0);
167 }
168 }),
169 CompletionsSchema::leaf("derive"),
170 ],
171 );
172
173 let completion_triggers = completions.generate_completion_triggers(&bon, meta, &[]);
174
175 quote! {
176 #[allow(unexpected_cfgs)]
180 const _: () = {
181 #[cfg(rust_analyzer)]
182 {
183 #completion_triggers
184 }
185 };
186 }
187}
188
189struct CompletionsSchema {
190 key: &'static str,
191 children: Vec<CompletionsSchema>,
192 custom_filter: Option<fn(&mut Vec<Meta>)>,
193}
194
195impl CompletionsSchema {
196 fn leaf(key: &'static str) -> Self {
197 Self {
198 key,
199 children: vec![],
200 custom_filter: None,
201 }
202 }
203
204 fn with_children(key: &'static str, children: Vec<Self>) -> Self {
205 Self {
206 key,
207 children,
208 custom_filter: None,
209 }
210 }
211
212 fn set_custom_filter(mut self, custom_filter: fn(&mut Vec<Meta>)) -> Self {
213 self.custom_filter = Some(custom_filter);
214 self
215 }
216
217 fn generate_completion_triggers(
218 &self,
219 bon: &syn::Path,
220 mut meta: Vec<Meta>,
221 module_prefix: &[&syn::Ident],
222 ) -> TokenStream {
223 if let Some(custom_filter) = self.custom_filter {
224 custom_filter(&mut meta);
225 };
226
227 let module_suffix = syn::Ident::new(self.key, Span::call_site());
228 let module_name = module_prefix
229 .iter()
230 .copied()
231 .chain([&module_suffix])
232 .collect::<Vec<_>>();
233
234 let child_completion_triggers = self
235 .children
236 .iter()
237 .map(|child| {
238 let child_metas = meta
239 .iter()
240 .filter_map(|meta| {
241 let meta = match meta {
242 Meta::List(meta) => meta,
243 _ => return None,
244 };
245
246 if !meta.path.is_ident(&child.key) {
247 return None;
248 }
249
250 parse_comma_separated_meta.parse2(meta.tokens.clone()).ok()
251 })
252 .concat();
253
254 child.generate_completion_triggers(bon, child_metas, &module_name)
255 })
256 .collect::<Vec<_>>();
257
258 let paths = paths_from_meta(meta);
259 let module_name_snake_case =
260 syn::Ident::new(&module_name.iter().join("_"), Span::call_site());
261
262 quote! {
263 mod #module_name_snake_case {
264 use #bon::__::ide #(::#module_name)* ::*;
270 use self::{ #( #paths as _, )* };
271 }
272
273 #(#child_completion_triggers)*
274 }
275 }
276}