1pub trait Span {
45 fn start_line(&self) -> usize;
46 fn end_line(&self) -> usize;
47 fn start_column(&self) -> usize;
48 fn end_column(&self) -> usize;
49
50 #[doc(hidden)]
51 fn is_empty(&self) -> bool {
52 self.start_line() == self.end_line() && self.start_column() == self.end_column()
53 }
54 #[doc(hidden)]
55 fn is_single_line(&self) -> bool {
56 self.start_line() == self.end_line()
57 }
58
59 fn to_range(&self) -> String {
67 format!(
68 "{}:{}..{}:{}",
69 self.start_line(),
70 self.start_column(),
71 self.end_line(),
72 self.end_column(),
73 )
74 }
75
76 fn debug(&self, code: &str) -> String {
80 internal::debug_span(self, code)
81 }
82}
83
84#[cfg(feature = "proc-macro2")]
85mod proc_macro2_span {
86 impl crate::Span for proc_macro2::Span {
87 fn start_line(&self) -> usize {
88 self.start().line
89 }
90 fn end_line(&self) -> usize {
91 self.end().line
92 }
93 fn start_column(&self) -> usize {
94 self.start().column
95 }
96 fn end_column(&self) -> usize {
97 self.end().column
98 }
99 }
100}
101
102pub fn debug_span(span: impl Span, code: &str) -> String {
128 internal::debug_span(&span, code)
129}
130
131#[doc(hidden)]
132pub mod internal {
133 use crate::Span;
134
135 pub fn debug_span(span: &(impl Span + ?Sized), code: &str) -> String {
136 if span.is_empty() {
137 debug_empty_span(span, code)
138 } else if span.is_single_line() {
139 debug_single_line_span(span, code)
140 } else {
141 debug_multi_line_span(span, code)
142 }
143 }
144
145 pub fn debug_empty_span(_span: &(impl Span + ?Sized), _code: &str) -> String {
146 "".to_string()
147 }
148
149 pub fn debug_single_line_span(span: &(impl Span + ?Sized), code: &str) -> String {
150 let empty_line = empty_line(span);
151 let range_line = range_line(span);
152 let code_line = code_line(span, code);
153 let marker_line = marker_line(span);
154 format!(
155 "{}\n{}\n{}\n{}\n{}\n",
156 range_line, empty_line, code_line, marker_line, empty_line,
157 )
158 }
159
160 pub fn debug_multi_line_span(span: &(impl Span + ?Sized), code: &str) -> String {
161 let empty_line = empty_line(span);
162 let range_line = range_line(span);
163 let start_line = start_line(span, code);
164 let code_lines = code_lines(span, code);
165 let end_line = end_line(span, code);
166 format!(
167 "{}\n{}\n{}\n{}\n{}\n{}\n",
168 range_line, empty_line, start_line, code_lines, end_line, empty_line,
169 )
170 }
171
172 pub fn range_line(span: &(impl Span + ?Sized)) -> String {
173 let line_number_width = span.end_line().to_string().len();
174 let range = span.to_range();
175 format!("{:width$}--> {}", "", range, width = line_number_width,)
176 }
177
178 pub fn empty_line(span: &(impl Span + ?Sized)) -> String {
179 let line_number_width = span.end_line().to_string().len();
180 format!("{:width$} |", "", width = line_number_width)
181 }
182
183 pub fn marker_line(span: &(impl Span + ?Sized)) -> String {
184 let line_number_width = span.end_line().to_string().len();
185 let start_column = span.start_column();
186 let end_column = span.end_column();
187
188 let marker = "^".repeat(end_column - start_column);
189 format!(
190 "{:width$} | {:space$}{}",
191 "",
192 "",
193 marker,
194 space = start_column,
195 width = line_number_width,
196 )
197 }
198
199 pub fn code_line(span: &(impl Span + ?Sized), code: &str) -> String {
200 let line_number_width = span.end_line().to_string().len();
201 let line = code.lines().nth(span.start_line() - 1).unwrap();
202 format!(
203 "{:width$} | {}",
204 span.start_line(),
205 line,
206 width = line_number_width,
207 )
208 }
209
210 const PADDING: usize = 3;
211
212 pub fn start_line(span: &(impl Span + ?Sized), code: &str) -> String {
213 let line_number_width = span.end_line().to_string().len();
214 let start_line = span.start_line();
215 let end_line = span.end_line();
216 let start_column = span.start_column();
217
218 let lines = code
219 .lines()
220 .skip(start_line - 1)
221 .take(end_line - start_line + 1);
222 let max_line_len = lines.map(|line| line.len()).max().unwrap();
223 format!(
224 "{:width$} | {}┌{}╮",
225 "",
226 " ".repeat(start_column),
227 "─".repeat(max_line_len + PADDING - start_column),
228 width = line_number_width,
229 )
230 }
231
232 pub fn code_lines(span: &(impl Span + ?Sized), code: &str) -> String {
233 let line_number_width = span.end_line().to_string().len();
234 let start_line = span.start_line();
235 let end_line = span.end_line();
236 let lines = code
237 .lines()
238 .skip(start_line - 1)
239 .take(end_line - start_line + 1);
240 let max_line_len = lines.clone().map(|line| line.len()).max().unwrap();
241 lines
242 .into_iter()
243 .enumerate()
244 .map(|(i, line)| {
245 let line_number = start_line + i;
246 format!(
247 "{: >line_number_width$} | {}{}│",
248 line_number,
249 line,
250 " ".repeat(max_line_len + PADDING + 1 - line.len()),
251 )
252 })
253 .collect::<Vec<_>>()
254 .join("\n")
255 }
256
257 pub fn end_line(span: &(impl Span + ?Sized), code: &str) -> String {
258 let line_number_width = span.end_line().to_string().len();
259 let start_line = span.start_line();
260 let end_line = span.end_line();
261 let end_column = span.end_column();
262
263 let lines = code
264 .lines()
265 .skip(start_line - 1)
266 .take(end_line - start_line + 1);
267 let max_line_len = lines.map(|line| line.len()).max().unwrap();
268 format!(
269 "{:width$} | {}└{}╯",
270 "",
271 " ".repeat(end_column - 1),
272 "─".repeat(max_line_len + PADDING - end_column + 1),
273 width = line_number_width,
274 )
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281 use syn::spanned::Spanned;
282 use syn::Data;
283 use unindent::Unindent;
284
285 #[test]
286 fn test_empty_span() {
287 let input = r###"
288 struct Foo;
289 "###
290 .unindent();
291 let span = proc_macro2::Span::call_site();
292 let output = debug_span(span, &input);
293 insta::assert_snapshot!(output, @"");
294 }
295
296 #[test]
297 fn test_single_line() {
298 let input = r###"
299 struct Foo;
300 "###
301 .unindent();
302 let derive_input: syn::DeriveInput = syn::parse_str(&input).unwrap();
303 let span = derive_input.ident.span();
304 let output = debug_span(span, &input);
305 insta::assert_snapshot!(output, @r###"
306 --> 1:7..1:10
307 |
308 1 | struct Foo;
309 | ^^^
310 |
311 "###);
312 }
313 #[test]
314 fn test_single_line_large_line_number() {
315 let input = r###"
316 struct Foo;
317 "###
318 .unindent();
319 let input = "\n".repeat(120) + &input;
320 let derive_input: syn::DeriveInput = syn::parse_str(&input).unwrap();
321 let span = derive_input.ident.span();
322 let output = debug_span(span, &input);
323 insta::assert_snapshot!(output, @r###"
324 --> 121:7..121:10
325 |
326 121 | struct Foo;
327 | ^^^
328 |
329 "###);
330 }
331
332 #[test]
333 fn test_multi_line() {
334 let input = r###"
335 struct Foo {
336 a: i32,
337 b: i32,
338 }
339 "###
340 .unindent();
341 let derive_input: syn::DeriveInput = syn::parse_str(&input).unwrap();
342 let span = match derive_input.data {
343 Data::Struct(s) => s.fields.span(),
344 _ => panic!("expected struct"),
345 };
346
347 let output = debug_span(span, &input);
348 insta::assert_snapshot!(output, @r###"
349 --> 1:11..4:1
350 |
351 | ┌────╮
352 1 | struct Foo { │
353 2 | a: i32, │
354 3 | b: i32, │
355 4 | } │
356 | └───────────────╯
357 |
358 "###);
359 }
360
361 #[test]
362 fn test_multi_line_large_line_number() {
363 let input = r###"
364 struct Foo {
365 a: i32,
366 b: i32,
367 }
368 "###
369 .unindent();
370 let input = "\n".repeat(120) + &input;
371 let derive_input: syn::DeriveInput = syn::parse_str(&input).unwrap();
372 let span = match derive_input.data {
373 Data::Struct(s) => s.fields.span(),
374 _ => panic!("expected struct"),
375 };
376
377 let output = debug_span(span, &input);
378 insta::assert_snapshot!(output, @r###"
379 --> 121:11..124:1
380 |
381 | ┌────╮
382 121 | struct Foo { │
383 122 | a: i32, │
384 123 | b: i32, │
385 124 | } │
386 | └───────────────╯
387 |
388 "###);
389 }
390
391 #[test]
392 fn test_multi_line_large_line() {
393 let input = r###"
394 struct Foo {
395 a: std::collections::HashMap<i32, i32>,
396 b: i32,
397 }
398 "###
399 .unindent();
400 let derive_input: syn::DeriveInput = syn::parse_str(&input).unwrap();
401 let span = match derive_input.data {
402 Data::Struct(s) => s.fields.span(),
403 _ => panic!("expected struct"),
404 };
405
406 let output = debug_span(span, &input);
407 insta::assert_snapshot!(output, @r###"
408 --> 1:11..4:1
409 |
410 | ┌───────────────────────────────────╮
411 1 | struct Foo { │
412 2 | a: std::collections::HashMap<i32, i32>, │
413 3 | b: i32, │
414 4 | } │
415 | └──────────────────────────────────────────────╯
416 |
417 "###);
418 }
419
420 #[test]
421 fn test_syn_error() {
422 let input = r###"
423 struct Foo {
424 a: i32
425 bar: i32,
426 }
427 "###
428 .unindent();
429 let derive_input: Result<syn::DeriveInput, _> = syn::parse_str(&input);
430 let error = match derive_input {
431 Ok(_) => panic!("expected error"),
432 Err(e) => e,
433 };
434 let span = error.span();
435 let output = debug_span(span, &input);
436 insta::assert_snapshot!(error.to_string(), @"expected `,`");
437 insta::assert_snapshot!(output, @r###"
438 --> 3:4..3:7
439 |
440 3 | bar: i32,
441 | ^^^
442 |
443 "###);
444 }
445
446 #[test]
447 fn test_debug_method() {
448 let input = r###"
449 struct Foo;
450 "###
451 .unindent();
452 let derive_input: syn::DeriveInput = syn::parse_str(&input).unwrap();
453 let span = derive_input.ident.span();
454 let output = span.debug(&input);
455 insta::assert_snapshot!(output, @r###"
456 --> 1:7..1:10
457 |
458 1 | struct Foo;
459 | ^^^
460 |
461 "###);
462 }
463}