cedar_policy_formatter/pprint/
utils.rs1use std::borrow::Borrow;
18
19use itertools::Itertools;
20use pretty::RcDoc;
21
22use crate::token::regex_constants;
23
24use super::token::{Comment, WrappedToken};
25
26pub fn add_brackets<'a>(
28 d: RcDoc<'a>,
29 leftp: RcDoc<'a>,
30 rightp: RcDoc<'a>,
31 offset: isize,
32) -> RcDoc<'a> {
33 leftp.append(d.nest(offset)).append(rightp)
34}
35
36pub fn get_leading_comment_doc_from_str<'src>(leading_comment: &[&'src str]) -> RcDoc<'src> {
38 if leading_comment.is_empty() {
39 RcDoc::nil()
40 } else {
41 RcDoc::hardline()
42 .append(create_multiline_doc(leading_comment))
43 .append(RcDoc::hardline())
44 }
45}
46
47fn create_multiline_doc<'src>(text: &[&'src str]) -> RcDoc<'src> {
52 RcDoc::intersperse(text.iter().map(|c| RcDoc::text(*c)), RcDoc::hardline())
53}
54
55pub fn get_trailing_comment_doc_from_str<'src>(
59 trailing_comment: &'src str,
60 next_doc: RcDoc<'src>,
61) -> RcDoc<'src> {
62 if !trailing_comment.is_empty() {
63 RcDoc::space()
64 .append(RcDoc::text(trailing_comment))
65 .append(RcDoc::hardline())
66 } else {
67 next_doc
68 }
69}
70
71fn get_token_at_start<'a, 'src>(
72 span: Option<miette::SourceSpan>,
73 tokens: &'a mut [WrappedToken<'src>],
74) -> Option<&'a mut WrappedToken<'src>> {
75 let span = span?;
76 tokens
77 .as_mut()
78 .iter_mut()
79 .find(|t| t.span.start == span.offset())
80}
81
82pub fn get_comment_at_start<'src>(
83 span: Option<miette::SourceSpan>,
84 tokens: &mut [WrappedToken<'src>],
85) -> Option<Comment<'src>> {
86 Some(get_token_at_start(span, tokens)?.consume_comment())
87}
88
89pub fn get_leading_comment_at_start<'src>(
90 span: Option<miette::SourceSpan>,
91 tokens: &mut [WrappedToken<'src>],
92) -> Option<Vec<&'src str>> {
93 Some(get_token_at_start(span, tokens)?.consume_leading_comment())
94}
95
96fn get_token_after_end<'a, 'src>(
97 span: Option<miette::SourceSpan>,
98 tokens: &'a mut [WrappedToken<'src>],
99) -> Option<&'a mut WrappedToken<'src>> {
100 let span = span?;
101 let end = span.offset() + span.len();
102 tokens.iter_mut().find_or_first(|t| t.span.start >= end)
103}
104
105fn get_token_at_end<'a, 'src>(
106 span: Option<miette::SourceSpan>,
107 tokens: &'a mut [WrappedToken<'src>],
108) -> Option<&'a mut WrappedToken<'src>> {
109 let span = span?;
110 let end = span.offset() + span.len();
111 tokens.iter_mut().find(|t| t.span.end == end)
112}
113
114pub fn get_comment_at_end<'src>(
115 span: Option<miette::SourceSpan>,
116 tokens: &mut [WrappedToken<'src>],
117) -> Option<Comment<'src>> {
118 Some(get_token_at_end(span, tokens)?.consume_comment())
119}
120
121pub fn get_comment_after_end<'src>(
122 span: Option<miette::SourceSpan>,
123 tokens: &mut [WrappedToken<'src>],
124) -> Option<Comment<'src>> {
125 Some(get_token_after_end(span, tokens)?.consume_comment())
126}
127
128pub fn get_comment_in_range<'src>(
129 span: Option<miette::SourceSpan>,
130 tokens: &mut [WrappedToken<'src>],
131) -> Option<Vec<Comment<'src>>> {
132 let span = span?;
133 Some(
134 tokens
135 .iter_mut()
136 .skip_while(|t| t.span.start < span.offset())
137 .take_while(|t| t.span.end <= span.offset() + span.len())
138 .map(|t| t.consume_comment())
139 .collect(),
140 )
141}
142
143pub fn add_comment<'src>(
147 d: RcDoc<'src>,
148 comment: impl Borrow<Comment<'src>>,
149 next_doc: RcDoc<'src>,
150) -> RcDoc<'src> {
151 let leading_comment = comment.borrow().leading_comment();
152 let trailing_comment = comment.borrow().trailing_comment();
153 let leading_comment_doc = get_leading_comment_doc_from_str(leading_comment);
154 let trailing_comment_doc = get_trailing_comment_doc_from_str(trailing_comment, next_doc);
155 leading_comment_doc.append(d).append(trailing_comment_doc)
156}
157
158fn remove_empty_interior_lines(s: &str) -> String {
164 let mut new_s = String::new();
165 if s.starts_with('\n') {
166 new_s.push('\n');
167 }
168 new_s.push_str(
169 s.split_inclusive('\n')
170 .filter(|ss| !ss.trim().is_empty() || !ss.contains('\n'))
173 .collect::<Vec<_>>()
174 .join("")
175 .as_str(),
176 );
177 new_s
178}
179
180pub fn remove_empty_lines(text: &str) -> String {
182 let mut index = 0;
183 let mut final_text = String::new();
184
185 while index < text.len() {
186 let comment_match = regex_constants::COMMENT.find_at(text, index);
191 let string_match = regex_constants::STRING.find_at(text, index);
192 match (comment_match, string_match) {
193 (Some(m1), Some(m2)) => {
194 let m = std::cmp::min_by_key(m1, m2, |m| m.start());
196 #[expect(
197 clippy::string_slice,
198 reason = "Slicing `text` is safe since `index <= m.start()` and both are within the bounds of `text`."
199 )]
200 final_text.push_str(&remove_empty_interior_lines(&text[index..m.start()]));
201 final_text.push_str(m.as_str());
202 index = m.end();
203 }
204 (Some(m), None) | (None, Some(m)) => {
205 #[expect(
206 clippy::string_slice,
207 reason = "Slicing `text` is safe since `index <= m.start()` and both are within the bounds of `text`."
208 )]
209 final_text.push_str(&remove_empty_interior_lines(&text[index..m.start()]));
210 final_text.push_str(m.as_str());
211 index = m.end();
212 }
213 (None, None) => {
214 #[expect(
215 clippy::string_slice,
216 reason = "Slicing `text` is safe since `index` is within the bounds of `text`."
217 )]
218 final_text.push_str(&remove_empty_interior_lines(&text[index..]));
219 break;
220 }
221 }
222 }
223 final_text.trim().to_string()
225}