Skip to content

Commit cafabf0

Browse files
authored
fix(fmt): account for CRLF when handling cursor (#11874)
* fix(fmt): account for CRLF when handling cursor * style: clippy * perf: initial scan + avoid snippet alloc * fix: handle oob * style: helper fn for line break logic
1 parent f891afd commit cafabf0

File tree

4 files changed

+57
-19
lines changed

4 files changed

+57
-19
lines changed

crates/fmt/src/state/common.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ impl<'ast> State<'_, 'ast> {
147147
let quote = match self.config.quote_style {
148148
config::QuoteStyle::Double => '\"',
149149
config::QuoteStyle::Single => '\'',
150-
config::QuoteStyle::Preserve => self.char_at(quote_pos),
150+
config::QuoteStyle::Preserve => self.char_at(quote_pos).unwrap_or_default(),
151151
};
152152
debug_assert!(matches!(quote, '\"' | '\''), "{quote:?}");
153153
let s = solar::parse::interface::data_structures::fmt::from_fn(move |f| {
@@ -546,7 +546,7 @@ impl<'ast> State<'_, 'ast> {
546546
self.s.offset(offset);
547547
}
548548
} else if style.is_isolated() {
549-
Separator::Space.print(&mut self.s, &mut self.cursor);
549+
self.print_sep_unhandled(Separator::Space);
550550
self.s.offset(offset);
551551
}
552552
}
@@ -555,7 +555,7 @@ impl<'ast> State<'_, 'ast> {
555555
self.zerobreak();
556556
self.s.offset(offset);
557557
} else if self.cursor.enabled {
558-
Separator::Space.print(&mut self.s, &mut self.cursor);
558+
self.print_sep_unhandled(Separator::Space);
559559
self.s.offset(offset);
560560
self.cursor.advance_to(block_lo, true);
561561
}

crates/fmt/src/state/mod.rs

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ pub(super) struct State<'sess, 'ast> {
129129
inline_config: InlineConfig<()>,
130130
cursor: SourcePos,
131131

132+
has_crlf: bool,
132133
contract: Option<&'ast ast::ItemContract<'ast>>,
133134
single_line_stmt: Option<bool>,
134135
named_call_expr: bool,
@@ -170,6 +171,10 @@ impl SourcePos {
170171
self.enabled = enabled;
171172
}
172173

174+
pub(super) fn next_line(&mut self, is_at_crlf: bool) {
175+
self.pos += if is_at_crlf { 2 } else { 1 };
176+
}
177+
173178
pub(super) fn span(&self, to: BytePos) -> Span {
174179
Span::new(self.pos, to)
175180
}
@@ -183,14 +188,15 @@ pub(super) enum Separator {
183188
}
184189

185190
impl Separator {
186-
fn print(&self, p: &mut pp::Printer, cursor: &mut SourcePos) {
191+
fn print(&self, p: &mut pp::Printer, cursor: &mut SourcePos, is_at_crlf: bool) {
187192
match self {
188193
Self::Nbsp => p.nbsp(),
189194
Self::Space => p.space(),
190195
Self::Hardbreak => p.hardbreak(),
191196
Self::SpaceOrNbsp(breaks) => p.space_or_nbsp(*breaks),
192197
}
193-
cursor.advance(1);
198+
199+
cursor.next_line(is_at_crlf);
194200
}
195201
}
196202

@@ -217,6 +223,7 @@ impl<'sess> State<'sess, '_> {
217223
config,
218224
inline_config,
219225
cursor: SourcePos { pos: BytePos::from_u32(0), enabled: true },
226+
has_crlf: false,
220227
contract: None,
221228
single_line_stmt: None,
222229
named_call_expr: false,
@@ -228,6 +235,25 @@ impl<'sess> State<'sess, '_> {
228235
}
229236
}
230237

238+
/// Checks a span of the source for a carriage return (`\r`) to determine if the file
239+
/// uses CRLF line endings.
240+
///
241+
/// If a `\r` is found, `self.has_crlf` is set to `true`. This is intended to be
242+
/// called once at the beginning of the formatting process for efficiency.
243+
fn check_crlf(&mut self, span: Span) {
244+
if let Ok(snip) = self.sm.span_to_snippet(span)
245+
&& snip.contains('\r')
246+
{
247+
self.has_crlf = true;
248+
}
249+
}
250+
251+
/// Checks if the cursor is currently positioned at the start of a CRLF sequence (`\r\n`).
252+
/// The check is only meaningful if `self.has_crlf` is true.
253+
fn is_at_crlf(&self) -> bool {
254+
self.has_crlf && self.char_at(self.cursor.pos) == Some('\r')
255+
}
256+
231257
fn space_left(&self) -> usize {
232258
std::cmp::min(
233259
self.s.space_left(),
@@ -272,9 +298,9 @@ impl<'sess> State<'sess, '_> {
272298

273299
/// Span to source.
274300
impl State<'_, '_> {
275-
fn char_at(&self, pos: BytePos) -> char {
301+
fn char_at(&self, pos: BytePos) -> Option<char> {
276302
let res = self.sm.lookup_byte_offset(pos);
277-
res.sf.src[res.pos.to_usize()..].chars().next().unwrap()
303+
res.sf.src.get(res.pos.to_usize()..)?.chars().next()
278304
}
279305

280306
fn print_span(&mut self, span: Span) {
@@ -340,11 +366,19 @@ impl State<'_, '_> {
340366
}
341367

342368
fn print_sep(&mut self, sep: Separator) {
343-
if self.handle_span(self.cursor.span(self.cursor.pos + BytePos(1)), true) {
369+
if self.handle_span(
370+
self.cursor.span(self.cursor.pos + if self.is_at_crlf() { 2 } else { 1 }),
371+
true,
372+
) {
344373
return;
345374
}
346375

347-
sep.print(&mut self.s, &mut self.cursor);
376+
self.print_sep_unhandled(sep);
377+
}
378+
379+
fn print_sep_unhandled(&mut self, sep: Separator) {
380+
let is_at_crlf = self.is_at_crlf();
381+
sep.print(&mut self.s, &mut self.cursor, is_at_crlf);
348382
}
349383

350384
fn print_ident(&mut self, ident: &ast::Ident) {
@@ -698,7 +732,7 @@ impl<'sess> State<'sess, '_> {
698732
let hb = |this: &mut Self| {
699733
this.hardbreak();
700734
if pos.is_last {
701-
this.cursor.advance(1);
735+
this.cursor.next_line(this.is_at_crlf());
702736
}
703737
};
704738
if line.is_empty() {
@@ -732,7 +766,7 @@ impl<'sess> State<'sess, '_> {
732766
let hb = |this: &mut Self| {
733767
this.hardbreak();
734768
if pos.is_last {
735-
this.cursor.advance(1);
769+
this.cursor.next_line(this.is_at_crlf());
736770
}
737771
};
738772
if line.is_empty() {
@@ -808,7 +842,7 @@ impl<'sess> State<'sess, '_> {
808842
// Pre-requisite: ensure that blank links are printed at the beginning of new line.
809843
if !self.last_token_is_break() && !self.is_bol_or_only_ind() {
810844
config.hardbreak(&mut self.s);
811-
self.cursor.advance(1);
845+
self.cursor.next_line(self.is_at_crlf());
812846
}
813847

814848
// We need to do at least one, possibly two hardbreaks.
@@ -820,10 +854,10 @@ impl<'sess> State<'sess, '_> {
820854
};
821855
if twice {
822856
config.hardbreak(&mut self.s);
823-
self.cursor.advance(1);
857+
self.cursor.next_line(self.is_at_crlf());
824858
}
825859
config.hardbreak(&mut self.s);
826-
self.cursor.advance(1);
860+
self.cursor.next_line(self.is_at_crlf());
827861
}
828862
}
829863
}

crates/fmt/src/state/sol.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ macro_rules! get_span {
2525
/// Language-specific pretty printing: Solidity.
2626
impl<'ast> State<'_, 'ast> {
2727
pub(crate) fn print_source_unit(&mut self, source_unit: &'ast ast::SourceUnit<'ast>) {
28+
// Figure out if the cursor needs to check for CR (`\r`).
29+
if let Some(item) = source_unit.items.first() {
30+
self.check_crlf(item.span.to(source_unit.items.last().unwrap().span));
31+
}
32+
2833
let mut items = source_unit.items.iter().peekable();
2934
let mut is_first = true;
3035
while let Some(item) = items.next() {
@@ -150,7 +155,7 @@ impl<'ast> State<'_, 'ast> {
150155
self.print_comments(span.hi(), CommentConfig::default());
151156
self.print_trailing_comment(span.hi(), None);
152157
self.hardbreak_if_not_bol();
153-
self.cursor.advance(1);
158+
self.cursor.next_line(self.is_at_crlf());
154159
}
155160

156161
fn print_pragma(&mut self, pragma: &'ast ast::PragmaDirective<'ast>) {
@@ -821,7 +826,7 @@ impl<'ast> State<'_, 'ast> {
821826
self.end();
822827
} else if is_binary_expr(&init.kind) {
823828
if !self.is_bol_or_only_ind() {
824-
Separator::Space.print(&mut self.s, &mut self.cursor);
829+
self.print_sep_unhandled(Separator::Space);
825830
}
826831
if matches!(ty.kind, ast::TypeKind::Elementary(..) | ast::TypeKind::Mapping(..)) {
827832
self.s.offset(self.ind);
@@ -864,7 +869,7 @@ impl<'ast> State<'_, 'ast> {
864869
}
865870
} else {
866871
if !self.is_bol_or_only_ind() {
867-
Separator::Space.print(&mut self.s, &mut self.cursor);
872+
self.print_sep_unhandled(Separator::Space);
868873
}
869874
if matches!(ty.kind, ast::TypeKind::Elementary(..) | ast::TypeKind::Mapping(..))
870875
{
@@ -2149,7 +2154,7 @@ impl<'ast> State<'_, 'ast> {
21492154

21502155
fn print_if_cond(&mut self, kw: &'static str, cond: &'ast ast::Expr<'ast>, pos_hi: BytePos) {
21512156
self.print_word(kw);
2152-
Separator::Nbsp.print(&mut self.s, &mut self.cursor);
2157+
self.print_sep_unhandled(Separator::Nbsp);
21532158
self.print_tuple(
21542159
std::slice::from_ref(cond),
21552160
cond.span.lo(),

crates/forge/tests/cli/fmt_integration.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,4 @@ fmt_test!(
2525
"e41f2b9b7ed677ca03ff7bd7221a4e2fdd55504f"
2626
);
2727

28-
#[cfg(not(windows))]
2928
fmt_test!(fmt_0x_settler, "0xProject", "0x-settler", "a388c8251ab6c4bedce1641b31027d7b1136daef");

0 commit comments

Comments
 (0)