⭐️ A friendly language for building type-safe, scalable systems!

allow empty lines inside lists

authored by giacomocavalieri.me and committed by Louis Pilfold d4cf96ab e9402f20

Changed files
+178 -14
compiler-core
src
format
tests
+27
CHANGELOG.md
··· 77 77 78 78 ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 79 79 80 + - The formatter no longer removes empty lines between list items. In case an 81 + empty line is added between list items they will all be split on multiple 82 + lines. 83 + 84 + ```gleam 85 + pub fn main() { 86 + [ 87 + "natu", "xatu", 88 + 89 + "chimeco" 90 + ] 91 + } 92 + ``` 93 + 94 + Is formatted as: 95 + 96 + ```gleam 97 + pub fn main() { 98 + [ 99 + "natu", 100 + "xatu", 101 + 102 + "chimeco", 103 + ] 104 + } 105 + ``` 106 + 80 107 ### Compiler 81 108 82 109 - Generated JavaScript functions, constants, and custom type constructors now
+61 -14
compiler-core/src/format.rs
··· 587 587 } 588 588 }; 589 589 590 - let elements = join( 591 - elements.iter().map(|element| self.const_expr(element)), 592 - comma, 593 - ); 590 + let mut elements_doc = nil(); 591 + for element in elements.iter() { 592 + let empty_lines = self.pop_empty_lines(element.location().start); 593 + let element_doc = self.const_expr(element); 594 + 595 + elements_doc = if elements_doc.is_empty() { 596 + element_doc 597 + } else if empty_lines { 598 + // If there's empty lines before the list item we want to add an 599 + // empty line here. Notice how we're making sure no nesting is 600 + // added after the comma, otherwise we would be adding needless 601 + // whitespace in the empty line! 602 + docvec![ 603 + elements_doc, 604 + comma.clone().set_nesting(0), 605 + line(), 606 + element_doc 607 + ] 608 + } else { 609 + docvec![elements_doc, comma.clone(), element_doc] 610 + }; 611 + } 612 + elements_doc = elements_doc.next_break_fits(NextBreakFitsMode::Disabled); 594 613 595 - let doc = break_("[", "[").append(elements).nest(INDENT); 614 + let doc = break_("[", "[").append(elements_doc).nest(INDENT); 596 615 597 616 // We get all remaining comments that come before the list's closing 598 617 // square bracket. ··· 1999 2018 None => 0, 2000 2019 }; 2001 2020 2002 - let elements = join( 2003 - elements 2004 - .iter() 2005 - .map(|e| self.comma_separated_item(e, list_size)), 2006 - comma, 2007 - ) 2008 - .next_break_fits(NextBreakFitsMode::Disabled); 2021 + let mut elements_doc = nil(); 2022 + for element in elements.iter() { 2023 + let empty_lines = self.pop_empty_lines(element.location().start); 2024 + let element_doc = self.comma_separated_item(element, list_size); 2009 2025 2010 - let doc = break_("[", "[").append(elements); 2026 + elements_doc = if elements_doc.is_empty() { 2027 + element_doc 2028 + } else if empty_lines { 2029 + // If there's empty lines before the list item we want to add an 2030 + // empty line here. Notice how we're making sure no nesting is 2031 + // added after the comma, otherwise we would be adding needless 2032 + // whitespace in the empty line! 2033 + docvec![ 2034 + elements_doc, 2035 + comma.clone().set_nesting(0), 2036 + line(), 2037 + element_doc 2038 + ] 2039 + } else { 2040 + docvec![elements_doc, comma.clone(), element_doc] 2041 + }; 2042 + } 2043 + elements_doc = elements_doc.next_break_fits(NextBreakFitsMode::Disabled); 2044 + 2045 + let doc = break_("[", "[").append(elements_doc); 2011 2046 // We need to keep the last break aside and do not add it immediately 2012 2047 // because in case there's a final comment before the closing square 2013 2048 // bracket we want to add indentation (to just that break). Otherwise, ··· 2068 2103 let has_multiple_elements_per_line = 2069 2104 self.has_items_on_the_same_line(items.iter().chain(tail)); 2070 2105 2071 - if !ends_with_trailing_comma { 2106 + let has_empty_lines_between_elements = match (items.first(), items.last().or(tail)) { 2107 + (Some(first), Some(last)) => self.empty_lines.first().is_some_and(|empty_line| { 2108 + *empty_line >= first.location().end && *empty_line < last.location().start 2109 + }), 2110 + _ => false, 2111 + }; 2112 + 2113 + if has_empty_lines_between_elements { 2114 + // If there's any empty line between elements we want to force each 2115 + // item onto its own line to preserve the empty lines that were 2116 + // intentionally added. 2117 + ListItemsPacking::BreakOnePerLine 2118 + } else if !ends_with_trailing_comma { 2072 2119 // If the list doesn't end with a trailing comma we try and pack it in 2073 2120 // a single line; if we can't we'll put one item per line, no matter 2074 2121 // the content of the list.
+90
compiler-core/src/format/tests/lists.rs
··· 273 273 "# 274 274 ); 275 275 } 276 + 277 + #[test] 278 + fn empty_lines_in_list_are_not_ignored() { 279 + assert_format_rewrite!( 280 + "pub fn main() { 281 + [1, 2, 282 + 283 + 3 284 + ] 285 + } 286 + ", 287 + "pub fn main() { 288 + [ 289 + 1, 290 + 2, 291 + 292 + 3, 293 + ] 294 + } 295 + " 296 + ); 297 + } 298 + 299 + #[test] 300 + fn empty_lines_in_const_list_are_not_ignored() { 301 + assert_format_rewrite!( 302 + "const list = 303 + [1, 2, 304 + 305 + 3 306 + ] 307 + ", 308 + "const list = [ 309 + 1, 310 + 2, 311 + 312 + 3, 313 + ] 314 + " 315 + ); 316 + } 317 + 318 + #[test] 319 + fn lists_with_empty_lines_are_always_broken() { 320 + assert_format_rewrite!( 321 + "pub fn main() { 322 + [ 323 + 1, 324 + 2, 325 + 326 + 3, 4, 5 327 + ] 328 + } 329 + ", 330 + "pub fn main() { 331 + [ 332 + 1, 333 + 2, 334 + 335 + 3, 336 + 4, 337 + 5, 338 + ] 339 + } 340 + " 341 + ); 342 + } 343 + 344 + #[test] 345 + fn const_lists_with_empty_lines_are_always_broken() { 346 + assert_format_rewrite!( 347 + "const list = 348 + [ 349 + 1, 350 + 2, 351 + 352 + 3, 4, 5 353 + ] 354 + ", 355 + "const list = [ 356 + 1, 357 + 2, 358 + 359 + 3, 360 + 4, 361 + 5, 362 + ] 363 + " 364 + ); 365 + }