Skip to main content

rustidy_format/
lib.rs

1//! Formatting
2
3// Features
4#![feature(
5	decl_macro,
6	never_type,
7	coverage_attribute,
8	macro_metavar_expr_concat,
9	trait_alias,
10	iter_advance_by
11)]
12
13// Modules
14mod tag;
15pub mod vec;
16pub mod whitespace;
17pub mod output;
18
19// Exports
20pub use {
21	self::{
22		output::{FormatMultilineOutput, FormatOutput},
23		tag::{FormatTag, FormatTags},
24		whitespace::{WhitespaceFormat, WhitespaceFormatKind},
25	},
26	rustidy_macros::{Format, Formattable},
27};
28
29// Imports
30use {
31	crate as rustidy_format,
32	arcstr::ArcStr,
33	core::{marker::PhantomData, ops::ControlFlow},
34	rustidy_util::{ArenaData, ArenaIdx, AstStr, Config, Oob, Whitespace},
35	std::borrow::Cow,
36};
37
38/// Formattable types
39pub trait Formattable {
40	/// Accesses the prefix whitespace of this type.
41	///
42	/// # Return
43	/// - `Ok()` if the prefix whitespace was found.
44	/// - `Err(Break(()))` if no prefix whitespace existed and the type wasn't empty
45	/// - `Err(Continue(()))` if no prefix whitespace existed but the type was empty.
46	fn with_prefix_ws<O>(
47		&mut self,
48		ctx: &mut Context,
49		f: &mut impl FnMut(&mut Whitespace,&mut Context) -> O,
50	) -> Result<O, ControlFlow<()>>;
51
52	/// Returns if the prefix whitespace is pure.
53	fn prefix_ws_is_pure(&mut self, ctx: &mut Context) -> Option<bool> {
54		self
55			.with_prefix_ws(ctx, &mut |ws, _ctx| ws.is_pure())
56			.ok()
57	}
58
59	/// Joins a string as a prefix onto the prefix whitespace of this type.
60	fn prefix_ws_join_prefix(&mut self, ctx: &mut Context, ws: Whitespace) -> Result<(), Whitespace> {
61		let mut join_ws = Some(ws);
62		let _ = self.with_prefix_ws(ctx, &mut |ws, _| {
63			ws.join_prefix(
64				join_ws
65					.take()
66					.expect("`with_prefix_ws` called multiple times")
67			);
68		});
69
70		match join_ws {
71			Some(ws) => Err(ws),
72			None => Ok(()),
73		}
74	}
75
76	/// Iterates over all strings in this type.
77	///
78	/// # Returns
79	/// - `Break()` if `f` returned `Break()`
80	/// - `Continue(true)` if this type was empty.
81	/// - `Continue(false)` if this type was non-empty.
82	fn with_strings<O>(
83		&mut self,
84		ctx: &mut Context,
85		exclude_prefix_ws: bool,
86		f: &mut impl FnMut(&mut AstStr,&mut Context) -> ControlFlow<O>,
87	) -> ControlFlow<O, bool>;
88
89	/// Returns the formatting output for this type, without formatting it.
90	fn format_output(&mut self, ctx: &mut Context) -> FormatOutput;
91}
92
93/// Type formatting
94pub trait Format<PrefixWs, Args>: Formattable {
95	/// Formats this type.
96	// TODO: Rename this to be less confusing with `Context::format`?
97	// TODO: Since `FormatOutput` is getting pretty big, we should pass
98	//       it by reference instead.
99	fn format(
100		&mut self,
101		ctx: &mut Context,
102		prefix_ws: PrefixWs,
103		args: Args
104	) -> FormatOutput;
105}
106
107impl<T: Formattable> Formattable for &'_ mut T {
108	fn with_prefix_ws<O>(
109		&mut self,
110		ctx: &mut Context,
111		f: &mut impl FnMut(&mut Whitespace,&mut Context) -> O,
112	) -> Result<O, ControlFlow<()>> {
113		(**self).with_prefix_ws(ctx, f)
114	}
115
116	fn with_strings<O>(
117		&mut self,
118		ctx: &mut Context,
119		exclude_prefix_ws: bool,
120		f: &mut impl FnMut(&mut AstStr,&mut Context) -> ControlFlow<O>,
121	) -> ControlFlow<O, bool> {
122		(**self)
123			.with_strings(ctx, exclude_prefix_ws, f)
124	}
125
126	fn format_output(&mut self, ctx: &mut Context) -> FormatOutput {
127		(**self).format_output(ctx)
128	}
129}
130
131impl<T: Format<PrefixWs, Args>, PrefixWs, Args> Format<PrefixWs, Args> for &'_ mut T {
132	fn format(
133		&mut self,
134		ctx: &mut Context,
135		prefix_ws: PrefixWs,
136		args: Args
137	) -> FormatOutput {
138		(**self).format(ctx, prefix_ws, args)
139	}
140}
141
142impl<T: Formattable> Formattable for Box<T> {
143	fn with_prefix_ws<O>(
144		&mut self,
145		ctx: &mut Context,
146		f: &mut impl FnMut(&mut Whitespace,&mut Context) -> O,
147	) -> Result<O, ControlFlow<()>> {
148		(**self).with_prefix_ws(ctx, f)
149	}
150
151	fn with_strings<O>(
152		&mut self,
153		ctx: &mut Context,
154		exclude_prefix_ws: bool,
155		f: &mut impl FnMut(&mut AstStr,&mut Context) -> ControlFlow<O>,
156	) -> ControlFlow<O, bool> {
157		(**self)
158			.with_strings(ctx, exclude_prefix_ws, f)
159	}
160
161	fn format_output(&mut self, ctx: &mut Context) -> FormatOutput {
162		(**self).format_output(ctx)
163	}
164}
165
166impl<T: Format<PrefixWs, Args>, PrefixWs, Args> Format<PrefixWs, Args> for Box<T> {
167	fn format(
168		&mut self,
169		ctx: &mut Context,
170		prefix_ws: PrefixWs,
171		args: Args
172	) -> FormatOutput {
173		(**self).format(ctx, prefix_ws, args)
174	}
175}
176
177impl<T: Formattable> Formattable for Option<T> {
178	fn with_prefix_ws<O>(
179		&mut self,
180		ctx: &mut Context,
181		f: &mut impl FnMut(&mut Whitespace,&mut Context) -> O,
182	) -> Result<O, ControlFlow<()>> {
183		match self {
184			Self::Some(value) => value.with_prefix_ws(ctx, f),
185			Self::None => Err(ControlFlow::Continue(())),
186		}
187	}
188
189	fn with_strings<O>(
190		&mut self,
191		ctx: &mut Context,
192		exclude_prefix_ws: bool,
193		f: &mut impl FnMut(&mut AstStr,&mut Context) -> ControlFlow<O>,
194	) -> ControlFlow<O, bool> {
195		match self {
196			Some(value) => value.with_strings(ctx, exclude_prefix_ws, f),
197			None => ControlFlow::Continue(true),
198		}
199	}
200
201	fn format_output(&mut self, ctx: &mut Context) -> FormatOutput {
202		match self {
203			Self::Some(value) => value.format_output(ctx),
204			Self::None => FormatOutput::default(),
205		}
206	}
207}
208
209impl<T: Format<PrefixWs, Args>, PrefixWs, Args> Format<PrefixWs, Args> for Option<T> {
210	fn format(
211		&mut self,
212		ctx: &mut Context,
213		prefix_ws: PrefixWs,
214		args: Args
215	) -> FormatOutput {
216		match self {
217			Some(value) => value.format(ctx, prefix_ws, args),
218			_ => FormatOutput::default(),
219		}
220	}
221}
222
223impl Formattable for ! {
224	fn with_prefix_ws<O>(
225		&mut self,
226		_ctx: &mut Context,
227		_f: &mut impl FnMut(&mut Whitespace,&mut Context) -> O,
228	) -> Result<O, ControlFlow<()>> {
229		*self
230	}
231
232	fn with_strings<O>(
233		&mut self,
234		_ctx: &mut Context,
235		_exclude_prefix_ws: bool,
236		_f: &mut impl FnMut(&mut AstStr,&mut Context) -> ControlFlow<O>,
237	) -> ControlFlow<O, bool> {
238		*self
239	}
240
241	fn format_output(&mut self, _ctx: &mut Context) -> FormatOutput {
242		*self
243	}
244}
245
246impl<PrefixWs, Args> Format<PrefixWs, Args> for ! {
247	fn format(
248		&mut self,
249		_ctx: &mut Context,
250		_prefix_ws: PrefixWs,
251		_args: Args
252	) -> FormatOutput {
253		*self
254	}
255}
256
257impl<T> Formattable for PhantomData<T> {
258	fn with_prefix_ws<O>(
259		&mut self,
260		_ctx: &mut Context,
261		_f: &mut impl FnMut(&mut Whitespace,&mut Context) -> O,
262	) -> Result<O, ControlFlow<()>> {
263		Err(ControlFlow::Continue(()))
264	}
265
266	fn with_strings<O>(
267		&mut self,
268		_ctx: &mut Context,
269		_exclude_prefix_ws: bool,
270		_f: &mut impl FnMut(&mut AstStr,&mut Context) -> ControlFlow<O>,
271	) -> ControlFlow<O, bool> {
272		ControlFlow::Continue(true)
273	}
274
275	fn format_output(&mut self, _ctx: &mut Context) -> FormatOutput {
276		FormatOutput::default()
277	}
278}
279
280impl<T> Format<(), ()> for PhantomData<T> {
281	fn format(&mut self, _ctx: &mut Context, _prefix_ws: (), _args: ()) -> FormatOutput {
282		FormatOutput::default()
283	}
284}
285
286impl Formattable for () {
287	fn with_prefix_ws<O>(
288		&mut self,
289		_ctx: &mut Context,
290		_f: &mut impl FnMut(&mut Whitespace,&mut Context) -> O,
291	) -> Result<O, ControlFlow<()>> {
292		Err(ControlFlow::Continue(()))
293	}
294
295	fn with_strings<O>(
296		&mut self,
297		_ctx: &mut Context,
298		_exclude_prefix_ws: bool,
299		_f: &mut impl FnMut(&mut AstStr,&mut Context) -> ControlFlow<O>,
300	) -> ControlFlow<O, bool> {
301		ControlFlow::Continue(true)
302	}
303
304	fn format_output(&mut self, _ctx: &mut Context) -> FormatOutput {
305		FormatOutput::default()
306	}
307}
308
309impl Format<(), ()> for () {
310	fn format(&mut self, _ctx: &mut Context, _prefix_ws: (), _args: ()) -> FormatOutput {
311		FormatOutput::default()
312	}
313}
314
315macro tuple_impl(
316	$N:literal, $($T:ident),* $(,)?
317) {
318	#[derive(Debug, Formattable, Format)]
319	#[expect(non_snake_case)]
320	struct ${concat( Tuple, $N )}< $( $T, )* > {
321		$( $T: $T, )*
322	}
323
324	#[automatically_derived]
325	#[expect(non_snake_case)]
326	impl< $($T: Formattable,)* > Formattable for ( $($T,)* ) {
327		fn with_prefix_ws<O>(
328			&mut self,
329			ctx: &mut Context,
330			f: &mut impl FnMut(&mut Whitespace, &mut Context) -> O,
331		) -> Result<O, ControlFlow<()>> {
332			let ( $($T,)* ) = self;
333			${concat( Tuple, $N )} { $( $T, )* }.with_prefix_ws(ctx, f)
334		}
335
336		fn with_strings<O>(
337			&mut self,
338			ctx: &mut Context,
339			exclude_prefix_ws: bool,
340			f: &mut impl FnMut(&mut AstStr, &mut Context) -> ControlFlow<O>,
341		) -> ControlFlow<O, bool> {
342			let ( $($T,)* ) = self;
343			${concat( Tuple, $N )} { $( $T, )* }.with_strings(ctx, exclude_prefix_ws, f)
344		}
345
346		fn format_output(&mut self, ctx: &mut Context) -> FormatOutput {
347			let ( $($T,)* ) = self;
348			${concat( Tuple, $N )} { $( $T, )* }.format_output(ctx)
349		}
350	}
351
352	// TODO: Make this impl generic for all prefix whitespace/args?
353	#[automatically_derived]
354	#[expect(non_snake_case)]
355	impl< $($T: Format<WhitespaceConfig, ()>,)*> Format<WhitespaceConfig, ()> for ( $($T,)* ) {
356		fn format(&mut self, ctx: &mut Context, prefix_ws: WhitespaceConfig, args: ()) -> FormatOutput {
357			let ( $($T,)* ) = self;
358			${concat( Tuple, $N )} { $( $T, )* }.format(ctx, prefix_ws, args)
359		}
360	}
361}
362
363tuple_impl! { 1, T0 }
364tuple_impl! { 2, T0, T1 }
365tuple_impl! { 3, T0, T1, T2 }
366
367impl Formattable for AstStr {
368	fn with_prefix_ws<O>(
369		&mut self,
370		_ctx: &mut Context,
371		_f: &mut impl FnMut(&mut Whitespace,&mut Context) -> O,
372	) -> Result<O, ControlFlow<()>> {
373		match self.is_empty() {
374			true => Err(ControlFlow::Continue(())),
375			false => Err(ControlFlow::Break(())),
376		}
377	}
378
379	fn with_strings<O>(
380		&mut self,
381		ctx: &mut Context,
382		_exclude_prefix_ws: bool,
383		f: &mut impl FnMut(&mut Self,&mut Context) -> ControlFlow<O>,
384	) -> ControlFlow<O, bool> {
385		f(self, ctx)?;
386
387		ControlFlow::Continue(self.is_empty())
388	}
389
390	fn format_output(&mut self, _ctx: &mut Context) -> FormatOutput {
391		// TODO: Optimize these by not iterating over the string multiple times.
392		FormatOutput {
393			prefix_ws_len: None,
394			len: self.len(),
395			is_empty: self.is_empty(),
396			is_blank: self.is_blank(),
397			multiline: FormatMultilineOutput::from_ast_str_repr(self.repr())
398		}
399	}
400}
401
402impl<T: ArenaData + Formattable> Formattable for ArenaIdx<T> {
403	fn with_prefix_ws<O>(
404		&mut self,
405		ctx: &mut Context,
406		f: &mut impl FnMut(&mut Whitespace,&mut Context) -> O,
407	) -> Result<O, ControlFlow<()>> {
408		(**self).with_prefix_ws(ctx, f)
409	}
410
411	fn with_strings<O>(
412		&mut self,
413		ctx: &mut Context,
414		exclude_prefix_ws: bool,
415		f: &mut impl FnMut(&mut AstStr,&mut Context) -> ControlFlow<O>,
416	) -> ControlFlow<O, bool> {
417		(**self)
418			.with_strings(ctx, exclude_prefix_ws, f)
419	}
420
421	fn format_output(&mut self, ctx: &mut Context) -> FormatOutput {
422		(**self).format_output(ctx)
423	}
424}
425
426impl<T: ArenaData + Format<PrefixWs, Args>, PrefixWs, Args> Format<PrefixWs, Args> for ArenaIdx<T> {
427	fn format(
428		&mut self,
429		ctx: &mut Context,
430		prefix_ws: PrefixWs,
431		args: Args
432	) -> FormatOutput {
433		(**self).format(ctx, prefix_ws, args)
434	}
435}
436
437/// Format context
438pub struct Context<'a> {
439	input:        ArcStr,
440	config:       Cow<'a, Config>,
441	indent_depth: usize,
442	tags:         Oob<'a, FormatTags>,
443}
444
445impl<'a> Context<'a> {
446	/// Creates a new context
447	#[must_use]
448	pub fn new(input: impl Into<ArcStr>, config: &'a Config) -> Self {
449		Self {
450			input: input.into(),
451			config: Cow::Borrowed(config),
452			indent_depth: 0,
453			tags: Oob::Owned(FormatTags::new()),
454		}
455	}
456
457	/// Formats a value
458	pub fn format<T, PrefixWs>(&mut self, value: &mut T, prefix_ws: PrefixWs) -> FormatOutput
459	where
460		T: Format<PrefixWs, ()>
461	{
462		self.format_with(value, prefix_ws, ())
463	}
464
465	/// Formats a value with arguments
466	pub fn format_with<T, PrefixWs, A>(&mut self, value: &mut T, prefix_ws: PrefixWs, args: A) -> FormatOutput
467	where
468		T: Format<PrefixWs, A>
469	{
470		match self.config().skip {
471			true => value.format_output(self),
472			false => value.format(self, prefix_ws, args),
473		}
474	}
475
476	/// Returns the input
477	#[must_use]
478	pub const fn input(&self) -> &ArcStr {
479		&self.input
480	}
481
482	/// Returns the config
483	#[must_use]
484	pub fn config(&self) -> &Config {
485		&self.config
486	}
487
488	/// Returns the config mutably
489	#[must_use]
490	pub fn config_mut(&mut self) -> &mut Config {
491		Cow::to_mut(&mut self.config)
492	}
493
494	/// Returns the indentation level
495	#[must_use]
496	pub const fn indent(&self) -> usize {
497		self.indent_depth
498	}
499
500	/// Runs `f` with a further indentation level
501	pub fn with_indent<O>(&mut self, f: impl FnOnce(&mut Self) -> O) -> O {
502		self.with_indent_offset(1, f)
503	}
504
505	/// Runs `f` with a further indentation level if `pred` is true
506	pub fn with_indent_if<O>(&mut self, pred: bool, f: impl FnOnce(&mut Self) -> O) -> O {
507		self.with_indent_offset_if(1, pred, f)
508	}
509
510	// TODO: Should `without_indent[_if]` be removed?
511	/// Runs `f` with one less indentation level
512	pub fn without_indent<O>(&mut self, f: impl for<'b> FnOnce(&'b mut Self) -> O) -> O {
513		self.with_indent_offset(-1, f)
514	}
515
516	/// Runs `f` with one less indentation level if `pred` is true, otherwise
517	/// runs it with the current indent
518	pub fn without_indent_if<O>(
519		&mut self,
520		pred: bool,
521		f: impl for<'b> FnOnce(&'b mut Self) -> O
522	) -> O {
523		self.with_indent_offset_if(-1, pred, f)
524	}
525
526	/// Runs `f` with an indentation offset of `offset`
527	pub fn with_indent_offset<O>(
528		&mut self,
529		offset: i16,
530		f: impl for<'b> FnOnce(&'b mut Self) -> O
531	) -> O {
532		let prev_depth = self.indent_depth;
533		self.indent_depth = prev_depth
534			.saturating_add_signed(isize::from(offset));
535		let output = f(self);
536		self.indent_depth = prev_depth;
537		output
538	}
539
540	/// Runs `f` with an indentation offset of `offset` if `pred` is true
541	pub fn with_indent_offset_if<O>(
542		&mut self,
543		offset: i16,
544		pred: bool,
545		f: impl for<'b> FnOnce(&'b mut Self) -> O,
546	) -> O {
547		match pred {
548			true => self.with_indent_offset(offset, f),
549			false => f(self),
550		}
551	}
552
553	#[doc(hidden)]
554	pub const fn set_indent_depth(&mut self, indent_depth: usize) {
555		self.indent_depth = indent_depth;
556	}
557
558	/// Creates a sub-context.
559	///
560	/// Sub contexts have their own configuration
561	pub fn sub_context(&mut self) -> Context<'_> {
562		Context {
563			input: ArcStr::clone(&self.input),
564			config: Cow::Borrowed(&self.config),
565			indent_depth: self.indent_depth,
566			tags: Oob::Borrowed(&mut self.tags),
567		}
568	}
569
570	/// Adds a tag.
571	///
572	/// Returns if the tag was present
573	pub fn add_tag(&mut self, tag: FormatTag) -> bool {
574		self.tags.add(tag)
575	}
576
577	/// Removes a tag.
578	///
579	/// Returns if the tag was present
580	pub fn remove_tag(&mut self, tag: FormatTag) -> bool {
581		self.tags.remove(tag)
582	}
583
584	/// Sets whether a tag is present.
585	///
586	/// Returns if the tag was present
587	pub fn set_tag(&mut self, tag: FormatTag, present: bool) -> bool {
588		self.tags.set(tag, present)
589	}
590
591	/// Returns if a tag exists
592	#[must_use]
593	pub fn has_tag(&self, tag: FormatTag) -> bool {
594		self.tags.contains(tag)
595	}
596
597	/// Runs `f` with a tag, removing it after
598	pub fn with_tag<O>(&mut self, tag: FormatTag, f: impl FnOnce(&mut Self) -> O) -> O {
599		let was_present = self.add_tag(tag);
600		let output = f(self);
601		self.set_tag(tag, was_present);
602
603		output
604	}
605
606	/// Runs `f` with a tag if `pred` is true, removing it after
607	pub fn with_tag_if<O>(
608		&mut self,
609		pred: bool,
610		tag: FormatTag,
611		f: impl FnOnce(&mut Self) -> O
612	) -> O {
613		match pred {
614			true => self.with_tag(tag, f),
615			false => f(self),
616		}
617	}
618
619	/// Runs `f` without a tag, adding it after if it existed
620	pub fn without_tag<O>(&mut self, tag: FormatTag, f: impl FnOnce(&mut Self) -> O) -> O {
621		let was_present = self.remove_tag(tag);
622		let output = f(self);
623		self.set_tag(tag, was_present);
624
625		output
626	}
627
628	/// Runs `f` without a tag if `pred` is true, adding it after if it existed
629	pub fn without_tag_if<O>(
630		&mut self,
631		pred: bool,
632		tag: FormatTag,
633		f: impl FnOnce(&mut Self) -> O
634	) -> O {
635		match pred {
636			true => self.without_tag(tag, f),
637			false => f(self),
638		}
639	}
640}
641
642/// Whitespace formatting configuration
643#[derive(Clone, Copy, Debug)]
644pub struct WhitespaceConfig {
645	format: Option<WhitespaceFormatKind>,
646}
647
648const _: () = const {
649	assert!(size_of::<WhitespaceConfig>() <= 8);
650};