Skip to main content

rustidy_ast/
attr.rs

1//! Attributes
2
3// Module
4pub mod with;
5pub mod meta;
6
7// Exports
8pub use self::with::{BracedWithInnerAttributes, WithOuterAttributes};
9
10// Imports
11use {
12	super::{
13		expr::Expression,
14		path::SimplePath,
15		token,
16		util::{Braced, Bracketed, Parenthesized},
17	},
18	self::meta::MetaItem,
19	app_error::{AppError, Context, bail},
20	core::fmt::Debug,
21	rustidy_ast_util::{Longest, RemainingBlockComment, RemainingLine, delimited},
22	rustidy_format::{Format, Formattable, WhitespaceFormat},
23	rustidy_parse::{ParsableFrom, Parse, ParserTag},
24	rustidy_print::Print,
25	rustidy_util::{Config, Whitespace},
26};
27
28#[derive(PartialEq, Eq, Clone, Debug)]
29#[derive(strum::EnumTryAs)]
30#[derive(serde::Serialize, serde::Deserialize)]
31#[derive(Parse, Formattable, Format, Print)]
32pub enum InnerAttrOrDocComment {
33	Attr(InnerAttribute),
34	DocComment(InnerDocComment),
35}
36
37/// `InnerAttribute`
38#[derive(PartialEq, Eq, Clone, Debug)]
39#[derive(serde::Serialize, serde::Deserialize)]
40#[derive(Parse, Formattable, Format, Print)]
41#[parse(name = "an inner attribute")]
42pub struct InnerAttribute {
43	pub pound: token::Pound,
44	#[format(prefix_ws = Whitespace::REMOVE)]
45	pub not:   token::Not,
46	#[parse(fatal)]
47	#[format(prefix_ws = Whitespace::REMOVE)]
48	#[format(args = delimited::FmtRemove)]
49	pub attr:  Bracketed<AttrOrMetaItem>,
50}
51
52/// Inner Doc comment
53#[derive(PartialEq, Eq, Clone, Debug)]
54#[derive(strum::EnumIs)]
55#[derive(serde::Serialize, serde::Deserialize)]
56#[derive(Parse, Formattable, Format, Print)]
57pub enum InnerDocComment {
58	Line(InnerLineDoc),
59	Block(InnerBlockDoc),
60}
61
62/// `INNER_LINE_DOC`
63#[derive(PartialEq, Eq, Clone, Debug)]
64#[derive(serde::Serialize, serde::Deserialize)]
65#[derive(Parse, Formattable, Format, Print)]
66pub struct InnerLineDoc {
67	pub prefix:  token::InnerLineDoc,
68	#[format(prefix_ws = ())]
69	pub comment: RemainingLine,
70}
71
72/// `INNER_BLOCK_DOC`
73#[derive(PartialEq, Eq, Clone, Debug)]
74#[derive(serde::Serialize, serde::Deserialize)]
75#[derive(Parse, Formattable, Format, Print)]
76pub struct InnerBlockDoc {
77	pub prefix:  token::InnerBlockDoc,
78	pub comment: RemainingBlockComment,
79}
80
81/// Outer attribute or doc comment
82#[derive(PartialEq, Eq, Clone, Debug)]
83#[derive(strum::EnumTryAs)]
84#[derive(serde::Serialize, serde::Deserialize)]
85#[derive(Parse, Formattable, Format, Print)]
86pub enum OuterAttrOrDocComment {
87	Attr(OuterAttribute),
88	DocComment(OuterDocComment),
89}
90
91/// `OuterAttribute`
92#[derive(PartialEq, Eq, Clone, Debug)]
93#[derive(serde::Serialize, serde::Deserialize)]
94#[derive(Parse, Formattable, Format, Print)]
95pub struct OuterAttribute {
96	pub pound: token::Pound,
97	#[format(prefix_ws = Whitespace::REMOVE)]
98	#[format(args = delimited::FmtRemove)]
99	pub open:  Bracketed<AttrOrMetaItem>,
100}
101
102/// Outer Doc comment
103#[derive(PartialEq, Eq, Clone, Debug)]
104#[derive(strum::EnumTryAs)]
105#[derive(serde::Serialize, serde::Deserialize)]
106#[derive(Parse, Formattable, Format, Print)]
107pub enum OuterDocComment {
108	Line(OuterLineDoc),
109	Block(OuterBlockDoc),
110}
111
112/// `OUTER_LINE_DOC`
113#[derive(PartialEq, Eq, Clone, Debug)]
114#[derive(serde::Serialize, serde::Deserialize)]
115#[derive(Parse, Formattable, Format, Print)]
116pub struct OuterLineDoc {
117	pub prefix:  token::OuterLineDoc,
118	#[format(prefix_ws = ())]
119	pub comment: RemainingLine,
120}
121
122/// `OUTER_BLOCK_DOC`
123#[derive(PartialEq, Eq, Clone, Debug)]
124#[derive(serde::Serialize, serde::Deserialize)]
125#[derive(Parse, Formattable, Format, Print)]
126pub struct OuterBlockDoc {
127	pub prefix:  token::OuterBlockDoc,
128	// TODO: This should technically need whitespace before if we find `/**/**/*/`,
129	//       but the reference doesn't seem to mention this, so we allow it.
130	pub comment: RemainingBlockComment,
131}
132
133#[derive(PartialEq, Eq, Clone, Debug)]
134#[derive(strum::EnumTryAs)]
135#[derive(serde::Serialize, serde::Deserialize)]
136#[derive(Parse, Formattable, Format, Print)]
137#[parse(from = Longest::<Attr, MetaItem>)]
138pub enum AttrOrMetaItem {
139	Meta(MetaItem),
140	Attr(Attr),
141}
142
143impl ParsableFrom<Longest<Attr, MetaItem>> for AttrOrMetaItem {
144	fn from_parsable(value: Longest<Attr, MetaItem>) -> Self {
145		match value {
146			Longest::Left(attr) => Self::Attr(attr),
147			Longest::Right(meta) => Self::Meta(meta),
148		}
149	}
150}
151
152/// `Attr`
153#[derive(PartialEq, Eq, Clone, Debug)]
154#[derive(serde::Serialize, serde::Deserialize)]
155#[derive(Parse, Formattable, Format, Print)]
156pub struct Attr {
157	// TODO: Unsafe attribute
158	pub path:  SimplePath,
159	#[format(prefix_ws = Whitespace::PRESERVE)]
160	pub input: Option<AttrInput>,
161}
162
163/// `AttrInput`
164#[derive(PartialEq, Eq, Clone, Debug)]
165#[derive(serde::Serialize, serde::Deserialize)]
166#[derive(Parse, Formattable, Format, Print)]
167pub enum AttrInput {
168	#[format(prefix_ws = Whitespace::REMOVE)]
169	DelimTokenTree(DelimTokenTree),
170	#[format(prefix_ws = Whitespace::SINGLE)]
171	EqExpr(AttrInputEqExpr),
172}
173
174#[derive(PartialEq, Eq, Clone, Debug)]
175#[derive(serde::Serialize, serde::Deserialize)]
176#[derive(Parse, Formattable, Format, Print)]
177pub struct AttrInputEqExpr {
178	pub eq:   token::Eq,
179	#[format(prefix_ws = Whitespace::SINGLE)]
180	pub expr: Expression,
181}
182
183/// `DelimTokenTree`
184#[derive(PartialEq, Eq, Clone, Debug)]
185#[derive(serde::Serialize, serde::Deserialize)]
186#[derive(Parse, Formattable, Format, Print)]
187pub enum DelimTokenTree {
188	#[format(args = delimited::fmt_preserve())]
189	Parens(Parenthesized<DelimTokenTreeInner>),
190	#[format(args = delimited::fmt_preserve())]
191	Brackets(Bracketed<DelimTokenTreeInner>),
192	#[format(args = delimited::fmt_preserve())]
193	Braces(Braced<DelimTokenTreeInner>),
194}
195
196#[derive(PartialEq, Eq, Clone, Debug)]
197#[derive(serde::Serialize, serde::Deserialize)]
198#[derive(Parse, Formattable, Format, Print)]
199pub struct DelimTokenTreeInner(
200	#[parse(fatal)]
201	#[format(args = rustidy_format::vec::args_prefix_ws(Whitespace::PRESERVE))]
202	pub Vec<TokenTree>,
203);
204
205/// `TokenTree`
206#[derive(PartialEq, Eq, Clone, Debug)]
207#[derive(serde::Serialize, serde::Deserialize)]
208#[derive(Parse, Formattable, Format, Print)]
209pub enum TokenTree {
210	Tree(DelimTokenTree),
211	Token(TokenNonDelimited),
212}
213
214/// `Token` except delimiters
215#[derive(PartialEq, Eq, Clone, Debug)]
216#[derive(serde::Serialize, serde::Deserialize)]
217#[derive(Parse, Formattable, Format, Print)]
218pub struct TokenNonDelimited(#[parse(with_tag = ParserTag::SkipDelimiters)] pub token::Token);
219
220/// Updates the configuration based on an attribute
221// TODO: We need to return the position for better error messages.
222pub fn update_from_attr(attr: &AttrOrMetaItem, ctx: &mut rustidy_format::Context) -> Result<(), AppError> {
223	let meta = match attr {
224		AttrOrMetaItem::Meta(meta) => match meta.path().starts_with("rustidy") {
225			true => meta,
226			false => return Ok(()),
227		},
228		AttrOrMetaItem::Attr(attr) => match attr.path.starts_with("rustidy") {
229			true => bail!("`#[rustidy]` attributes must be meta items"),
230			false => return Ok(()),
231		},
232	};
233
234	match meta.path().as_str().as_str() {
235		"rustidy::config" => self::update_config(meta, ctx)?,
236		"rustidy::skip" => ctx.config_mut().skip = true,
237		path => bail!("Unknown `#[rustidy]` attribute: {path:?}"),
238	}
239
240	Ok(())
241}
242
243/// Parses a `#[rustidy::config]` attribute
244fn update_config(meta: &MetaItem, ctx: &mut rustidy_format::Context) -> Result<(), AppError> {
245	let MetaItem::Seq(meta) = meta else {
246		bail!("Expected `rustidy::config([...])`");
247	};
248
249	let Some(configs) = &meta.seq.value else {
250		return Ok(())
251	};
252
253	for config in configs.0.values() {
254		let config = try {
255			config.try_as_meta_ref()?.try_as_eq_expr_ref()?
256		};
257		let Some(config) = config else {
258			bail!("Expected `rustidy::config(<config-name> = <value>)`");
259		};
260
261		macro str() {
262			config
263				.expr
264				.as_string_literal()
265				.context("Expected a string literal")?
266				.contents()
267		}
268		macro int() {
269			config
270				.expr
271				.as_integer_literal()
272				.context("Expected an integer literal")?
273				.value()
274				.context("Unable to parse integer")?
275				.try_into()
276				.expect("`u64` didn't fit into `usize`")
277		}
278
279		macro fields(
280			$( $field:ident = $value:expr ),* $(,)?
281		) {
282			let Config {
283				$( $field, )*
284
285				// Note: Skip is controlled by `rustidy::skip`.
286				skip: _,
287			} = ctx.config_mut();
288
289			match config.path.as_str().as_str() {
290				$(
291					stringify!($field) => *$field = $value,
292				)*
293				ident => bail!("Unknown configuration: {ident:?}"),
294			}
295		}
296
297		// TODO: Should we allow resetting `Option` types?
298		fields! {
299			indent = str!().into(),
300			min_empty_lines = int!(),
301			max_empty_lines = int!(),
302			max_use_tree_len = int!(),
303			array_expr_cols = Some(int!()),
304			max_array_expr_len = int!(),
305			max_chain_len = int!(),
306			max_inline_tuple_struct_len = int!(),
307		}
308	};
309
310	Ok(())
311}