···7272 needlessly indented if preceded by a comment.
7373- Line endings other than `\n` are now handled by the formatter, preserving
7474 blank lines and converting them to `\n`.
7575+- The formatter can now format groups of imports alphabetically.
75767677### Build tool
7778···151152152153- The format used by the formatter has been improved in some niche cases.
153154- Improved the formatting of long case guards.
154154-- The formatter can now format groups of imports alphabetically.
155155156156## v1.0.0-rc1 - 2024-02-10
157157
···219219 /// Separates the imports in groups delimited by comments or empty lines and
220220 /// sorts each group alphabetically.
221221 ///
222222+ /// The formatter needs to play nicely with import groups defined by the
223223+ /// programmer. If one puts a comment before an import then that's a clue
224224+ /// for the formatter that it has run into a gorup of related imports.
225225+ ///
226226+ /// So we can't just sort `imports` and format each one, we have to be a
227227+ /// bit smarter and see if each import is preceded by a comment.
228228+ /// Once we find a comment we know we're done with the current import
229229+ /// group and a new one has started.
230230+ ///
231231+ /// ```gleam
232232+ /// // This is an import group.
233233+ /// import gleam/int
234234+ /// import gleam/string
235235+ ///
236236+ /// // This marks the beginning of a new import group that can't
237237+ /// // be mushed together with the previous one!
238238+ /// import wibble
239239+ /// import wobble
240240+ /// ```
222241 fn imports<'a>(&mut self, imports: Vec<&'a TargetedDefinition>) -> Vec<Document<'a>> {
223223- // The formatter needs to play nicely with import groups defined by the
224224- // programmer. If one puts a comment before an import then that's a clue
225225- // for the formatter that it has run into a gorup of related imports.
226226- //
227227- // So we can't just sort `imports` and format each one, we have to be a
228228- // bit smarter and see if each import is preceded by a comment.
229229- // Once we find a comment we know we're done with the current import
230230- // group and a new one has started.
231231- //
232232- // ```gleam
233233- // // This is an import group.
234234- // import gleam/int
235235- // import gleam/string
236236- //
237237- // // This marks the beginning of a new import group that can't
238238- // // be mushed together with the previous one!
239239- // import wibble
240240- // import wobble
241241- // ```
242242- let mut documents = vec![];
242242+ let mut import_groups_docs = vec![];
243243 let mut current_group = vec![];
244244- let mut group_comments = None;
244244+ let mut current_group_delimiter = docvec!();
245245246246 for import in imports {
247247 let start = import.definition.location().start;
248248- // If the import is preceded by a comment then we want to put it
249249- // into a new group and we can print the current one.
250250- if !current_group.is_empty()
251251- && (self.any_comments(start) || self.any_empty_lines(start))
252252- {
253253- documents.append(&mut self.sorted_import_group(&mut current_group, group_comments));
254254- documents.push(lines(2));
255255- // We pop the comment introducing the group and save it for
256256- // later for when the group is over and we can actually print
257257- // it.
258258- // We have to immediately pop the comment as soon as we start
259259- // with a new group or it would still be present in
260260- // `self.comments` messing up `self.any_comments` for the next
261261- // imports in the group.
262262- group_comments = printed_comments(self.pop_comments(start), false);
248248+249249+ // We need to start a new group if the `import` is preceded by one or
250250+ // more empty lines or a `//` comment.
251251+ let start_new_group = self.any_comments(start) || self.any_empty_lines(start);
252252+ if start_new_group {
253253+ // First we print the previous group and clear it out to start a
254254+ // new empty group containing the import we've just ran into.
255255+ if !current_group.is_empty() {
256256+ import_groups_docs.push(docvec![
257257+ current_group_delimiter,
258258+ self.sorted_import_group(¤t_group)
259259+ ]);
260260+ current_group.clear();
261261+ }
262262+263263+ // Now that we've taken care of the previous group we can start
264264+ // the new one. We know it's preceded either by an empty line or
265265+ // some comments se we have to be a bit more precise and save the
266266+ // actual delimiter that we're going to put at the top of this
267267+ // group.
268268+269269+ let comments = self.pop_comments(start);
270270+ let _ = self.pop_empty_lines(start);
271271+ current_group_delimiter = printed_comments(comments, true).unwrap_or(docvec!());
263272 }
264264- let _ = self.pop_empty_lines(start);
273273+ // Lastly we add the import to the group.
265274 current_group.push(import);
266275 }
267276268277 // Let's not forget about the last import group!
269278 if !current_group.is_empty() {
270270- documents.append(&mut self.sorted_import_group(&mut current_group, group_comments));
279279+ import_groups_docs.push(docvec![
280280+ current_group_delimiter,
281281+ self.sorted_import_group(¤t_group)
282282+ ]);
271283 }
272284273273- documents
285285+ // We want all consecutive import groups to be separated by an empty line.
286286+ // This should really be `.intersperse(line())` but I can't do that
287287+ // because of https://github.com/rust-lang/rust/issues/48919.
288288+ Itertools::intersperse(import_groups_docs.into_iter(), lines(2)).collect_vec()
274289 }
275290276276- /// Prints the imports as a single sorted group of import statements
277277- /// draining the given vector.
278278- ///
279279- /// `group_comment` is the comment preceding the current group.
280280- /// It might be missing since a group could also be defined by simply having
281281- /// an empty line between imports.
291291+ /// Prints the imports as a single sorted group of import statements.
282292 ///
283283- fn sorted_import_group<'a>(
284284- &mut self,
285285- imports: &mut Vec<&'a TargetedDefinition>,
286286- group_comment: Option<Document<'a>>,
287287- ) -> Vec<Document<'a>> {
288288- let mut documents = Vec::with_capacity(imports.len() * 2);
289289-290290- // If the group is defined with a single comment we print it as the
291291- // first thing.
292292- if let Some(comment) = group_comment {
293293- documents.push(comment)
294294- };
295295-296296- imports.sort_by(|one, other| match (&one.definition, &other.definition) {
297297- (Definition::Import(one), Definition::Import(other)) => one.module.cmp(&other.module),
298298- // It shouldn't really be possible for a non import to be here so
299299- // we just return a default value.
300300- _ => std::cmp::Ordering::Equal,
301301- });
293293+ fn sorted_import_group<'a>(&mut self, imports: &Vec<&'a TargetedDefinition>) -> Document<'a> {
294294+ let imports = imports
295295+ .iter()
296296+ .sorted_by(|one, other| match (&one.definition, &other.definition) {
297297+ (Definition::Import(one), Definition::Import(other)) => {
298298+ one.module.cmp(&other.module)
299299+ }
300300+ // It shouldn't really be possible for a non import to be here so
301301+ // we just return a default value.
302302+ _ => std::cmp::Ordering::Equal,
303303+ })
304304+ .map(|import| self.targeted_definition(import));
302305303303- for import in imports.iter() {
304304- if !documents.is_empty() {
305305- documents.push(lines(1))
306306- }
307307- documents.push(self.targeted_definition(import));
308308- }
309309- imports.clear();
310310- documents
306306+ // This should really be `.intersperse(line())` but I can't do that
307307+ // because of https://github.com/rust-lang/rust/issues/48919.
308308+ Itertools::intersperse(imports, line())
309309+ .collect_vec()
310310+ .to_doc()
311311 }
312312313313 fn definition<'a>(&mut self, statement: &'a UntypedDefinition) -> Document<'a> {