Skip to content

Commit 28cd15d

Browse files
formatter: introduce context_after; fix comment between if-else (FuelLabs#3556)
closes FuelLabs#3529 ~possibly blocked by FuelLabs#3555 (will be closed by FuelLabs#3557)~ FuelLabs#3555 is now resolved. This PR introduces the concept of `context_after` during comments formatting - in some cases like in [the fundraiser contract](https://github.com/FuelLabs/sway-applications/blob/68184dc44099ae15983b77da0fbada02c3169810/fundraiser/project/fundraiser-contract/src/main.sw#L280-L282) where a comment appears in a newline between a `}` and an `else`, it might be useful to know the offset required in terms of whitespaces from a comment to the next token.
1 parent 949dfb9 commit 28cd15d

File tree

2 files changed

+124
-13
lines changed

2 files changed

+124
-13
lines changed

swayfmt/src/formatter/mod.rs

+48
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,54 @@ fn func_with_multiline_collections() {
11521152
fn func_with_multiline_collections() {
11531153
let x = ("hello", "world");
11541154
}
1155+
"#;
1156+
let mut formatter = Formatter::default();
1157+
let formatted_sway_code =
1158+
Formatter::format(&mut formatter, Arc::from(sway_code_to_format), None).unwrap();
1159+
assert_eq!(correct_sway_code, formatted_sway_code);
1160+
assert!(test_stability(formatted_sway_code, formatter));
1161+
}
1162+
1163+
#[test]
1164+
fn comments_between_if_else() {
1165+
let sway_code_to_format = r#"script;
1166+
1167+
fn main() {
1168+
if pledge_history_index != 0 {
1169+
// This is a comment
1170+
storage.pledge_history.insert((user, pledge_history_index), pledge);
1171+
}
1172+
// This is also a comment,
1173+
// but multiline
1174+
else if true {
1175+
// This is yet another comment
1176+
storage.pledge_count.insert(user, pledge_count + 1);
1177+
}
1178+
// This is the last comment
1179+
else {
1180+
storage.pledge_count.insert(user, pledge_count + 1);
1181+
}
1182+
}
1183+
"#;
1184+
1185+
let correct_sway_code = r#"script;
1186+
1187+
fn main() {
1188+
if pledge_history_index != 0 {
1189+
// This is a comment
1190+
storage.pledge_history.insert((user, pledge_history_index), pledge);
1191+
}
1192+
// This is also a comment,
1193+
// but multiline
1194+
else if true {
1195+
// This is yet another comment
1196+
storage.pledge_count.insert(user, pledge_count + 1);
1197+
}
1198+
// This is the last comment
1199+
else {
1200+
storage.pledge_count.insert(user, pledge_count + 1);
1201+
}
1202+
}
11551203
"#;
11561204
let mut formatter = Formatter::default();
11571205
let formatted_sway_code =

swayfmt/src/utils/map/comments.rs

+76-13
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,19 @@ fn add_comments(
170170
Ok(())
171171
}
172172

173-
// A CommentWithContext is the Comment and the offset before it. The offset can be between the (from) item we searched for this comment or from the last comment inside range
174-
type CommentWithContext = (Comment, String);
173+
// A `CommentWithContext` is the `Comment` and the offset before and after it.
174+
// A context is simply the chars between two points around a comment.
175+
//
176+
// The pre-context can be from the item we searched for this comment or from the last comment inside range.
177+
//
178+
// The post-context is an Option<String> that gets populated only if there is an "else" token following the `Comment`.
179+
// It starts from end of the `Comment` and goes until the beginning of the `else` token.
180+
// There may be other tokens we might need to look-ahead for in future.
181+
struct CommentWithContext {
182+
pre_context: String,
183+
comment: Comment,
184+
post_context: Option<String>,
185+
}
175186

176187
/// Returns a list of comments between given spans. For each comment returns the Context
177188
/// Context of a comment is basically the offset (the characters between the last item/comment) to the current comment
@@ -191,12 +202,26 @@ fn get_comments_between_spans(
191202
from.end
192203
} else {
193204
// There is a comment before this one, so we should get the context starting from the last comment's end to the beginning of the current comment
194-
comments_with_context[index - 1].0.span.end()
205+
comments_with_context[index - 1].comment.span.end()
195206
};
196-
comments_with_context.push((
197-
comment.clone(),
198-
unformatted_code[starting_position_for_context..comment_span.start].to_string(),
199-
));
207+
208+
let mut rest_of_code = unformatted_code
209+
.get(comment_span.end..)
210+
.unwrap_or_default()
211+
.lines()
212+
.take(2);
213+
214+
// consume '\n'
215+
let _ = rest_of_code.next();
216+
// actual next line of code that we're interested in
217+
let next_line = rest_of_code.next().unwrap_or_default();
218+
219+
comments_with_context.push(CommentWithContext {
220+
pre_context: unformatted_code[starting_position_for_context..comment_span.start]
221+
.to_string(),
222+
comment: comment.clone(),
223+
post_context: get_post_context(unformatted_code, comment_span, next_line),
224+
});
200225
}
201226
}
202227
comments_with_context
@@ -221,7 +246,33 @@ fn format_context(context: &str, threshold: usize) -> String {
221246
formatted_context
222247
}
223248

224-
/// Inserts after given span and returns the offset. While inserting comments this also inserts Context of the comments so that the alignment whitespaces/newlines are intact
249+
// In certain cases where comments come in between unusual places,
250+
// ..
251+
// }
252+
// // This is a comment
253+
// else {
254+
// ..
255+
// We need to know the context after the comment as well.
256+
fn get_post_context(
257+
unformatted_code: &Arc<str>,
258+
comment_span: &ByteSpan,
259+
next_line: &str,
260+
) -> Option<String> {
261+
if next_line.trim_start().starts_with("else") {
262+
let else_token_start = next_line
263+
.char_indices()
264+
.find(|(_, c)| !c.is_whitespace())
265+
.map(|(i, _)| i)
266+
.unwrap_or_default();
267+
Some(unformatted_code[comment_span.end..comment_span.end + else_token_start].to_string())
268+
} else {
269+
// If we don't find anything to format in the context after, we simply
270+
// return an empty context.
271+
None
272+
}
273+
}
274+
275+
/// Inserts after given span and returns the offset. While inserting comments this also inserts contexts of the comments so that the alignment whitespaces/newlines are intact
225276
fn insert_after_span(
226277
from: &ByteSpan,
227278
comments_to_insert: Vec<CommentWithContext>,
@@ -233,15 +284,27 @@ fn insert_after_span(
233284
let mut comment_str = String::new();
234285
let mut pre_module_comment = false;
235286
for comment_with_context in iter {
236-
let (comment_value, comment_context) = comment_with_context;
237-
if comment_value.span.start() == from.start {
287+
let CommentWithContext {
288+
pre_context,
289+
comment,
290+
post_context,
291+
} = comment_with_context;
292+
if comment.span.start() == from.start {
238293
pre_module_comment = true;
239294
}
295+
240296
write!(
241297
comment_str,
242-
"{}{}",
243-
format_context(comment_context, 2),
244-
&format_comment(comment_value)
298+
"{}{}{}",
299+
format_context(pre_context, 2),
300+
&format_comment(comment),
301+
format_context(
302+
post_context
303+
.as_ref()
304+
.map(|s| s.as_str())
305+
.unwrap_or_default(),
306+
2
307+
)
245308
)?;
246309
}
247310
let mut src_rope = Rope::from_str(formatted_code);

0 commit comments

Comments
 (0)