forge_fmt/
helpers.rs
1use crate::{
2 inline_config::{InlineConfig, InvalidInlineConfigItem},
3 Comments, Formatter, FormatterConfig, FormatterError, Visitable,
4};
5use ariadne::{Color, Fmt, Label, Report, ReportKind, Source};
6use itertools::Itertools;
7use solang_parser::{diagnostics::Diagnostic, pt::*};
8use std::{fmt::Write, path::Path};
9
10#[derive(Debug)]
12pub struct Parsed<'a> {
13 pub src: &'a str,
15 pub pt: SourceUnit,
17 pub comments: Comments,
19 pub inline_config: InlineConfig,
21 pub invalid_inline_config_items: Vec<(Loc, InvalidInlineConfigItem)>,
23}
24
25pub fn parse(src: &str) -> Result<Parsed<'_>, FormatterError> {
27 parse_raw(src).map_err(|diag| FormatterError::Parse(src.to_string(), None, diag))
28}
29
30pub fn parse2<'s>(src: &'s str, path: Option<&Path>) -> Result<Parsed<'s>, FormatterError> {
32 parse_raw(src)
33 .map_err(|diag| FormatterError::Parse(src.to_string(), path.map(ToOwned::to_owned), diag))
34}
35
36pub fn parse_raw(src: &str) -> Result<Parsed<'_>, Vec<Diagnostic>> {
38 let (pt, comments) = solang_parser::parse(src, 0)?;
39 let comments = Comments::new(comments, src);
40 let (inline_config_items, invalid_inline_config_items): (Vec<_>, Vec<_>) =
41 comments.parse_inline_config_items().partition_result();
42 let inline_config = InlineConfig::new(inline_config_items, src);
43 Ok(Parsed { src, pt, comments, inline_config, invalid_inline_config_items })
44}
45
46pub fn format_to<W: Write>(
48 writer: W,
49 mut parsed: Parsed<'_>,
50 config: FormatterConfig,
51) -> Result<(), FormatterError> {
52 trace!(?parsed, ?config, "Formatting");
53 let mut formatter =
54 Formatter::new(writer, parsed.src, parsed.comments, parsed.inline_config, config);
55 parsed.pt.visit(&mut formatter)
56}
57
58pub fn format(src: &str) -> Result<String, FormatterError> {
60 let parsed = parse(src)?;
61
62 let mut output = String::new();
63 format_to(&mut output, parsed, FormatterConfig::default())?;
64
65 Ok(output)
66}
67
68pub fn offset_to_line_column(content: &str, start: usize) -> (usize, usize) {
70 debug_assert!(content.len() > start);
71
72 let mut line_counter = 1;
74 for (offset, c) in content.chars().enumerate() {
75 if c == '\n' {
76 line_counter += 1;
77 }
78 if offset > start {
79 return (line_counter, offset - start)
80 }
81 }
82
83 unreachable!("content.len() > start")
84}
85
86pub fn format_diagnostics_report(
88 content: &str,
89 path: Option<&Path>,
90 diagnostics: &[Diagnostic],
91) -> String {
92 if diagnostics.is_empty() {
93 return String::new();
94 }
95
96 let filename =
97 path.map(|p| p.file_name().unwrap().to_string_lossy().to_string()).unwrap_or_default();
98 let mut s = Vec::new();
99 for diag in diagnostics {
100 let span = (filename.as_str(), diag.loc.start()..diag.loc.end());
101 let mut report = Report::build(ReportKind::Error, span.clone())
102 .with_message(format!("{:?}", diag.ty))
103 .with_label(
104 Label::new(span)
105 .with_color(Color::Red)
106 .with_message(diag.message.as_str().fg(Color::Red)),
107 );
108
109 for note in &diag.notes {
110 report = report.with_note(¬e.message);
111 }
112
113 report.finish().write((filename.as_str(), Source::from(content)), &mut s).unwrap();
114 }
115 String::from_utf8(s).unwrap()
116}
117
118pub fn import_path_string(path: &ImportPath) -> String {
119 match path {
120 ImportPath::Filename(s) => s.string.clone(),
121 ImportPath::Path(p) => p.to_string(),
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
131 fn test_interface_format() {
132 let s = "interface I {\n function increment() external;\n function number() external view returns (uint256);\n function setNumber(uint256 newNumber) external;\n}";
133 let _formatted = format(s).unwrap();
134 }
135}