]> git.lizzy.rs Git - rust.git/blob - src/lib.rs
Format source codes
[rust.git] / src / lib.rs
1 // Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 #![feature(rustc_private)]
12
13 #[macro_use]
14 extern crate log;
15
16 extern crate serde;
17 #[macro_use]
18 extern crate serde_derive;
19 extern crate serde_json;
20
21 extern crate syntax;
22 extern crate rustc_errors as errors;
23
24 extern crate strings;
25
26 extern crate unicode_segmentation;
27 extern crate regex;
28 extern crate diff;
29 extern crate term;
30
31 use errors::{Handler, DiagnosticBuilder};
32 use errors::emitter::{ColorConfig, EmitterWriter};
33 use syntax::ast;
34 use syntax::codemap::{CodeMap, Span, FilePathMapping};
35 use syntax::parse::{self, ParseSess};
36
37 use strings::string_buffer::StringBuffer;
38
39 use std::io::{self, stdout, Write};
40 use std::ops::{Add, Sub};
41 use std::path::{Path, PathBuf};
42 use std::rc::Rc;
43 use std::collections::HashMap;
44 use std::fmt;
45
46 use issues::{BadIssueSeeker, Issue};
47 use filemap::FileMap;
48 use visitor::FmtVisitor;
49 use config::Config;
50 use checkstyle::{output_header, output_footer};
51 use utils::mk_sp;
52
53 pub use self::summary::Summary;
54
55 #[macro_use]
56 mod utils;
57 pub mod config;
58 pub mod codemap;
59 pub mod filemap;
60 pub mod file_lines;
61 pub mod visitor;
62 mod checkstyle;
63 mod items;
64 mod missed_spans;
65 mod lists;
66 mod types;
67 mod expr;
68 mod imports;
69 mod issues;
70 mod rewrite;
71 mod string;
72 mod comment;
73 pub mod modules;
74 pub mod rustfmt_diff;
75 mod chains;
76 mod macros;
77 mod patterns;
78 mod summary;
79 mod vertical;
80
81 const MIN_STRING: usize = 10;
82 // When we get scoped annotations, we should have rustfmt::skip.
83 const SKIP_ANNOTATION: &'static str = "rustfmt_skip";
84
85 pub trait Spanned {
86     fn span(&self) -> Span;
87 }
88
89 impl Spanned for ast::Expr {
90     fn span(&self) -> Span {
91         self.span
92     }
93 }
94
95 impl Spanned for ast::Pat {
96     fn span(&self) -> Span {
97         self.span
98     }
99 }
100
101 impl Spanned for ast::Ty {
102     fn span(&self) -> Span {
103         self.span
104     }
105 }
106
107 impl Spanned for ast::Arm {
108     fn span(&self) -> Span {
109         let hi = self.body.span.hi;
110         if self.attrs.is_empty() {
111             mk_sp(self.pats[0].span.lo, hi)
112         } else {
113             mk_sp(self.attrs[0].span.lo, hi)
114         }
115     }
116 }
117
118 impl Spanned for ast::Arg {
119     fn span(&self) -> Span {
120         if items::is_named_arg(self) {
121             utils::mk_sp(self.pat.span.lo, self.ty.span.hi)
122         } else {
123             self.ty.span
124         }
125     }
126 }
127
128 impl Spanned for ast::StructField {
129     fn span(&self) -> Span {
130         if self.attrs.is_empty() {
131             mk_sp(self.span.lo, self.ty.span.hi)
132         } else {
133             // Include attributes and doc comments, if present
134             mk_sp(self.attrs[0].span.lo, self.ty.span.hi)
135         }
136     }
137 }
138
139 impl Spanned for ast::Field {
140     fn span(&self) -> Span {
141         let lo = if self.attrs.is_empty() {
142             self.span.lo
143         } else {
144             self.attrs[0].span.lo
145         };
146         mk_sp(lo, self.span.hi)
147     }
148 }
149
150 impl Spanned for ast::WherePredicate {
151     fn span(&self) -> Span {
152         match *self {
153             ast::WherePredicate::BoundPredicate(ref p) => p.span,
154             ast::WherePredicate::RegionPredicate(ref p) => p.span,
155             ast::WherePredicate::EqPredicate(ref p) => p.span,
156         }
157     }
158 }
159
160 impl Spanned for ast::FunctionRetTy {
161     fn span(&self) -> Span {
162         match *self {
163             ast::FunctionRetTy::Default(span) => span,
164             ast::FunctionRetTy::Ty(ref ty) => ty.span,
165         }
166     }
167 }
168
169 impl Spanned for ast::TyParam {
170     fn span(&self) -> Span {
171         // Note that ty.span is the span for ty.ident, not the whole item.
172         let lo = if self.attrs.is_empty() {
173             self.span.lo
174         } else {
175             self.attrs[0].span.lo
176         };
177         if let Some(ref def) = self.default {
178             return mk_sp(lo, def.span.hi);
179         }
180         if self.bounds.is_empty() {
181             return mk_sp(lo, self.span.hi);
182         }
183         let hi = self.bounds[self.bounds.len() - 1].span().hi;
184         mk_sp(lo, hi)
185     }
186 }
187
188 impl Spanned for ast::TyParamBound {
189     fn span(&self) -> Span {
190         match *self {
191             ast::TyParamBound::TraitTyParamBound(ref ptr, _) => ptr.span,
192             ast::TyParamBound::RegionTyParamBound(ref l) => l.span,
193         }
194     }
195 }
196
197 #[derive(Copy, Clone, Debug)]
198 pub struct Indent {
199     // Width of the block indent, in characters. Must be a multiple of
200     // Config::tab_spaces.
201     pub block_indent: usize,
202     // Alignment in characters.
203     pub alignment: usize,
204 }
205
206 impl Indent {
207     pub fn new(block_indent: usize, alignment: usize) -> Indent {
208         Indent {
209             block_indent: block_indent,
210             alignment: alignment,
211         }
212     }
213
214     pub fn empty() -> Indent {
215         Indent::new(0, 0)
216     }
217
218     pub fn block_only(&self) -> Indent {
219         Indent {
220             block_indent: self.block_indent,
221             alignment: 0,
222         }
223     }
224
225     pub fn block_indent(mut self, config: &Config) -> Indent {
226         self.block_indent += config.tab_spaces();
227         self
228     }
229
230     pub fn block_unindent(mut self, config: &Config) -> Indent {
231         if self.block_indent < config.tab_spaces() {
232             Indent::new(self.block_indent, 0)
233         } else {
234             self.block_indent -= config.tab_spaces();
235             self
236         }
237     }
238
239     pub fn width(&self) -> usize {
240         self.block_indent + self.alignment
241     }
242
243     pub fn to_string(&self, config: &Config) -> String {
244         let (num_tabs, num_spaces) = if config.hard_tabs() {
245             (self.block_indent / config.tab_spaces(), self.alignment)
246         } else {
247             (0, self.width())
248         };
249         let num_chars = num_tabs + num_spaces;
250         let mut indent = String::with_capacity(num_chars);
251         for _ in 0..num_tabs {
252             indent.push('\t')
253         }
254         for _ in 0..num_spaces {
255             indent.push(' ')
256         }
257         indent
258     }
259 }
260
261 impl Add for Indent {
262     type Output = Indent;
263
264     fn add(self, rhs: Indent) -> Indent {
265         Indent {
266             block_indent: self.block_indent + rhs.block_indent,
267             alignment: self.alignment + rhs.alignment,
268         }
269     }
270 }
271
272 impl Sub for Indent {
273     type Output = Indent;
274
275     fn sub(self, rhs: Indent) -> Indent {
276         Indent::new(
277             self.block_indent - rhs.block_indent,
278             self.alignment - rhs.alignment,
279         )
280     }
281 }
282
283 impl Add<usize> for Indent {
284     type Output = Indent;
285
286     fn add(self, rhs: usize) -> Indent {
287         Indent::new(self.block_indent, self.alignment + rhs)
288     }
289 }
290
291 impl Sub<usize> for Indent {
292     type Output = Indent;
293
294     fn sub(self, rhs: usize) -> Indent {
295         Indent::new(self.block_indent, self.alignment - rhs)
296     }
297 }
298
299 #[derive(Copy, Clone, Debug)]
300 pub struct Shape {
301     pub width: usize,
302     // The current indentation of code.
303     pub indent: Indent,
304     // Indentation + any already emitted text on the first line of the current
305     // statement.
306     pub offset: usize,
307 }
308
309 impl Shape {
310     /// `indent` is the indentation of the first line. The next lines
311     /// should begin with at least `indent` spaces (except backwards
312     /// indentation). The first line should not begin with indentation.
313     /// `width` is the maximum number of characters on the last line
314     /// (excluding `indent`). The width of other lines is not limited by
315     /// `width`.
316     /// Note that in reality, we sometimes use width for lines other than the
317     /// last (i.e., we are conservative).
318     // .......*-------*
319     //        |       |
320     //        |     *-*
321     //        *-----|
322     // |<------------>|  max width
323     // |<---->|          indent
324     //        |<--->|    width
325     pub fn legacy(width: usize, indent: Indent) -> Shape {
326         Shape {
327             width: width,
328             indent: indent,
329             offset: indent.alignment,
330         }
331     }
332
333     pub fn indented(indent: Indent, config: &Config) -> Shape {
334         Shape {
335             width: config.max_width().checked_sub(indent.width()).unwrap_or(0),
336             indent: indent,
337             offset: indent.alignment,
338         }
339     }
340
341     pub fn with_max_width(&self, config: &Config) -> Shape {
342         Shape {
343             width: config
344                 .max_width()
345                 .checked_sub(self.indent.width())
346                 .unwrap_or(0),
347             ..*self
348         }
349     }
350
351     pub fn offset(width: usize, indent: Indent, offset: usize) -> Shape {
352         Shape {
353             width: width,
354             indent: indent,
355             offset: offset,
356         }
357     }
358
359     pub fn visual_indent(&self, extra_width: usize) -> Shape {
360         let alignment = self.offset + extra_width;
361         Shape {
362             width: self.width,
363             indent: Indent::new(self.indent.block_indent, alignment),
364             offset: alignment,
365         }
366     }
367
368     pub fn block_indent(&self, extra_width: usize) -> Shape {
369         if self.indent.alignment == 0 {
370             Shape {
371                 width: self.width,
372                 indent: Indent::new(self.indent.block_indent + extra_width, 0),
373                 offset: 0,
374             }
375         } else {
376             Shape {
377                 width: self.width,
378                 indent: self.indent + extra_width,
379                 offset: self.indent.alignment + extra_width,
380             }
381         }
382     }
383
384     pub fn block_left(&self, width: usize) -> Option<Shape> {
385         self.block_indent(width).sub_width(width)
386     }
387
388     pub fn add_offset(&self, extra_width: usize) -> Shape {
389         Shape {
390             offset: self.offset + extra_width,
391             ..*self
392         }
393     }
394
395     pub fn block(&self) -> Shape {
396         Shape {
397             indent: self.indent.block_only(),
398             ..*self
399         }
400     }
401
402     pub fn sub_width(&self, width: usize) -> Option<Shape> {
403         Some(Shape {
404             width: try_opt!(self.width.checked_sub(width)),
405             ..*self
406         })
407     }
408
409     pub fn shrink_left(&self, width: usize) -> Option<Shape> {
410         Some(Shape {
411             width: try_opt!(self.width.checked_sub(width)),
412             indent: self.indent + width,
413             offset: self.offset + width,
414         })
415     }
416
417     pub fn offset_left(&self, width: usize) -> Option<Shape> {
418         self.add_offset(width).sub_width(width)
419     }
420
421     pub fn used_width(&self) -> usize {
422         self.indent.block_indent + self.offset
423     }
424
425     pub fn rhs_overhead(&self, config: &Config) -> usize {
426         config
427             .max_width()
428             .checked_sub(self.used_width() + self.width)
429             .unwrap_or(0)
430     }
431 }
432
433 pub enum ErrorKind {
434     // Line has exceeded character limit (found, maximum)
435     LineOverflow(usize, usize),
436     // Line ends in whitespace
437     TrailingWhitespace,
438     // TO-DO or FIX-ME item without an issue number
439     BadIssue(Issue),
440 }
441
442 impl fmt::Display for ErrorKind {
443     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
444         match *self {
445             ErrorKind::LineOverflow(found, maximum) => write!(
446                 fmt,
447                 "line exceeded maximum length (maximum: {}, found: {})",
448                 maximum,
449                 found
450             ),
451             ErrorKind::TrailingWhitespace => write!(fmt, "left behind trailing whitespace"),
452             ErrorKind::BadIssue(issue) => write!(fmt, "found {}", issue),
453         }
454     }
455 }
456
457 // Formatting errors that are identified *after* rustfmt has run.
458 pub struct FormattingError {
459     line: u32,
460     kind: ErrorKind,
461 }
462
463 impl FormattingError {
464     fn msg_prefix(&self) -> &str {
465         match self.kind {
466             ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "Rustfmt failed at",
467             ErrorKind::BadIssue(_) => "WARNING:",
468         }
469     }
470
471     fn msg_suffix(&self) -> &str {
472         match self.kind {
473             ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "(sorry)",
474             ErrorKind::BadIssue(_) => "",
475         }
476     }
477 }
478
479 pub struct FormatReport {
480     // Maps stringified file paths to their associated formatting errors.
481     file_error_map: HashMap<String, Vec<FormattingError>>,
482 }
483
484 impl FormatReport {
485     fn new() -> FormatReport {
486         FormatReport {
487             file_error_map: HashMap::new(),
488         }
489     }
490
491     pub fn warning_count(&self) -> usize {
492         self.file_error_map
493             .iter()
494             .map(|(_, errors)| errors.len())
495             .fold(0, |acc, x| acc + x)
496     }
497
498     pub fn has_warnings(&self) -> bool {
499         self.warning_count() > 0
500     }
501 }
502
503 impl fmt::Display for FormatReport {
504     // Prints all the formatting errors.
505     fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
506         for (file, errors) in &self.file_error_map {
507             for error in errors {
508                 write!(
509                     fmt,
510                     "{} {}:{}: {} {}\n",
511                     error.msg_prefix(),
512                     file,
513                     error.line,
514                     error.kind,
515                     error.msg_suffix()
516                 )?;
517             }
518         }
519         Ok(())
520     }
521 }
522
523 // Formatting which depends on the AST.
524 fn format_ast<F>(
525     krate: &ast::Crate,
526     mut parse_session: &mut ParseSess,
527     main_file: &Path,
528     config: &Config,
529     codemap: &Rc<CodeMap>,
530     mut after_file: F,
531 ) -> Result<(FileMap, bool), io::Error>
532 where
533     F: FnMut(&str, &mut StringBuffer) -> Result<bool, io::Error>,
534 {
535     let mut result = FileMap::new();
536     // diff mode: check if any files are differing
537     let mut has_diff = false;
538
539     // We always skip children for the "Plain" write mode, since there is
540     // nothing to distinguish the nested module contents.
541     let skip_children = config.skip_children() || config.write_mode() == config::WriteMode::Plain;
542     for (path, module) in modules::list_files(krate, parse_session.codemap()) {
543         if skip_children && path.as_path() != main_file {
544             continue;
545         }
546         let path = path.to_str().unwrap();
547         if config.verbose() {
548             println!("Formatting {}", path);
549         }
550         {
551             let mut visitor = FmtVisitor::from_codemap(parse_session, config);
552             visitor.format_separate_mod(module);
553
554             has_diff |= after_file(path, &mut visitor.buffer)?;
555
556             result.push((path.to_owned(), visitor.buffer));
557         }
558         // Reset the error count.
559         if parse_session.span_diagnostic.has_errors() {
560             let silent_emitter = Box::new(EmitterWriter::new(
561                 Box::new(Vec::new()),
562                 Some(codemap.clone()),
563             ));
564             parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
565         }
566     }
567
568     Ok((result, has_diff))
569 }
570
571 // Formatting done on a char by char or line by line basis.
572 // FIXME(#209) warn on bad license
573 // FIXME(#20) other stuff for parity with make tidy
574 fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &mut FormatReport) {
575     // Iterate over the chars in the file map.
576     let mut trims = vec![];
577     let mut last_wspace: Option<usize> = None;
578     let mut line_len = 0;
579     let mut cur_line = 1;
580     let mut newline_count = 0;
581     let mut errors = vec![];
582     let mut issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme());
583
584     for (c, b) in text.chars() {
585         if c == '\r' {
586             continue;
587         }
588
589         let format_line = config.file_lines().contains_line(name, cur_line as usize);
590
591         if format_line {
592             // Add warnings for bad todos/ fixmes
593             if let Some(issue) = issue_seeker.inspect(c) {
594                 errors.push(FormattingError {
595                     line: cur_line,
596                     kind: ErrorKind::BadIssue(issue),
597                 });
598             }
599         }
600
601         if c == '\n' {
602             if format_line {
603                 // Check for (and record) trailing whitespace.
604                 if let Some(lw) = last_wspace {
605                     trims.push((cur_line, lw, b));
606                     line_len -= 1;
607                 }
608
609                 // Check for any line width errors we couldn't correct.
610                 if config.error_on_line_overflow() && line_len > config.max_width() {
611                     errors.push(FormattingError {
612                         line: cur_line,
613                         kind: ErrorKind::LineOverflow(line_len, config.max_width()),
614                     });
615                 }
616             }
617
618             line_len = 0;
619             cur_line += 1;
620             newline_count += 1;
621             last_wspace = None;
622         } else {
623             newline_count = 0;
624             line_len += 1;
625             if c.is_whitespace() {
626                 if last_wspace.is_none() {
627                     last_wspace = Some(b);
628                 }
629             } else {
630                 last_wspace = None;
631             }
632         }
633     }
634
635     if newline_count > 1 {
636         debug!("track truncate: {} {}", text.len, newline_count);
637         let line = text.len - newline_count + 1;
638         text.truncate(line);
639     }
640
641     for &(l, _, _) in &trims {
642         errors.push(FormattingError {
643             line: l,
644             kind: ErrorKind::TrailingWhitespace,
645         });
646     }
647
648     report.file_error_map.insert(name.to_owned(), errors);
649 }
650
651 fn parse_input(
652     input: Input,
653     parse_session: &ParseSess,
654 ) -> Result<ast::Crate, Option<DiagnosticBuilder>> {
655     let result = match input {
656         Input::File(file) => {
657             let mut parser = parse::new_parser_from_file(parse_session, &file);
658             parser.cfg_mods = false;
659             parser.parse_crate_mod()
660         }
661         Input::Text(text) => {
662             let mut parser =
663                 parse::new_parser_from_source_str(parse_session, "stdin".to_owned(), text);
664             parser.cfg_mods = false;
665             parser.parse_crate_mod()
666         }
667     };
668
669     match result {
670         Ok(c) => {
671             if parse_session.span_diagnostic.has_errors() {
672                 // Bail out if the parser recovered from an error.
673                 Err(None)
674             } else {
675                 Ok(c)
676             }
677         }
678         Err(e) => Err(Some(e)),
679     }
680 }
681
682 pub fn format_input<T: Write>(
683     input: Input,
684     config: &Config,
685     mut out: Option<&mut T>,
686 ) -> Result<(Summary, FileMap, FormatReport), (io::Error, Summary)> {
687     let mut summary = Summary::new();
688     if config.disable_all_formatting() {
689         return Ok((summary, FileMap::new(), FormatReport::new()));
690     }
691     let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
692
693     let tty_handler =
694         Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(codemap.clone()));
695     let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone());
696
697     let main_file = match input {
698         Input::File(ref file) => file.clone(),
699         Input::Text(..) => PathBuf::from("stdin"),
700     };
701
702     let krate = match parse_input(input, &parse_session) {
703         Ok(krate) => krate,
704         Err(diagnostic) => {
705             if let Some(mut diagnostic) = diagnostic {
706                 diagnostic.emit();
707             }
708             summary.add_parsing_error();
709             return Ok((summary, FileMap::new(), FormatReport::new()));
710         }
711     };
712
713     if parse_session.span_diagnostic.has_errors() {
714         summary.add_parsing_error();
715     }
716
717     // Suppress error output after parsing.
718     let silent_emitter = Box::new(EmitterWriter::new(
719         Box::new(Vec::new()),
720         Some(codemap.clone()),
721     ));
722     parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
723
724     let mut report = FormatReport::new();
725
726     match format_ast(
727         &krate,
728         &mut parse_session,
729         &main_file,
730         config,
731         &codemap,
732         |file_name, file| {
733             // For some reason, the codemap does not include terminating
734             // newlines so we must add one on for each file. This is sad.
735             filemap::append_newline(file);
736
737             format_lines(file, file_name, config, &mut report);
738
739             if let Some(ref mut out) = out {
740                 return filemap::write_file(file, file_name, out, config);
741             }
742             Ok(false)
743         },
744     ) {
745         Ok((file_map, has_diff)) => {
746             if report.has_warnings() {
747                 summary.add_formatting_error();
748             }
749
750             if has_diff {
751                 summary.add_diff();
752             }
753
754             Ok((summary, file_map, report))
755         }
756         Err(e) => Err((e, summary)),
757     }
758 }
759
760 #[derive(Debug)]
761 pub enum Input {
762     File(PathBuf),
763     Text(String),
764 }
765
766 pub fn run(input: Input, config: &Config) -> Summary {
767     let mut out = &mut stdout();
768     output_header(out, config.write_mode()).ok();
769     match format_input(input, config, Some(out)) {
770         Ok((summary, _, report)) => {
771             output_footer(out, config.write_mode()).ok();
772
773             if report.has_warnings() {
774                 msg!("{}", report);
775             }
776
777             summary
778         }
779         Err((msg, mut summary)) => {
780             msg!("Error writing files: {}", msg);
781             summary.add_operational_error();
782             summary
783         }
784     }
785 }
786
787 #[cfg(test)]
788 mod test {
789     use super::*;
790
791     #[test]
792     fn indent_add_sub() {
793         let indent = Indent::new(4, 8) + Indent::new(8, 12);
794         assert_eq!(12, indent.block_indent);
795         assert_eq!(20, indent.alignment);
796
797         let indent = indent - Indent::new(4, 4);
798         assert_eq!(8, indent.block_indent);
799         assert_eq!(16, indent.alignment);
800     }
801
802     #[test]
803     fn indent_add_sub_alignment() {
804         let indent = Indent::new(4, 8) + 4;
805         assert_eq!(4, indent.block_indent);
806         assert_eq!(12, indent.alignment);
807
808         let indent = indent - 4;
809         assert_eq!(4, indent.block_indent);
810         assert_eq!(8, indent.alignment);
811     }
812
813     #[test]
814     fn indent_to_string_spaces() {
815         let config = Config::default();
816         let indent = Indent::new(4, 8);
817
818         // 12 spaces
819         assert_eq!("            ", indent.to_string(&config));
820     }
821
822     #[test]
823     fn indent_to_string_hard_tabs() {
824         let mut config = Config::default();
825         config.set().hard_tabs(true);
826         let indent = Indent::new(8, 4);
827
828         // 2 tabs + 4 spaces
829         assert_eq!("\t\t    ", indent.to_string(&config));
830     }
831
832     #[test]
833     fn shape_visual_indent() {
834         let config = Config::default();
835         let indent = Indent::new(4, 8);
836         let shape = Shape::legacy(config.max_width(), indent);
837         let shape = shape.visual_indent(20);
838
839         assert_eq!(config.max_width(), shape.width);
840         assert_eq!(4, shape.indent.block_indent);
841         assert_eq!(28, shape.indent.alignment);
842         assert_eq!(28, shape.offset);
843     }
844
845     #[test]
846     fn shape_block_indent_without_alignment() {
847         let config = Config::default();
848         let indent = Indent::new(4, 0);
849         let shape = Shape::legacy(config.max_width(), indent);
850         let shape = shape.block_indent(20);
851
852         assert_eq!(config.max_width(), shape.width);
853         assert_eq!(24, shape.indent.block_indent);
854         assert_eq!(0, shape.indent.alignment);
855         assert_eq!(0, shape.offset);
856     }
857
858     #[test]
859     fn shape_block_indent_with_alignment() {
860         let config = Config::default();
861         let indent = Indent::new(4, 8);
862         let shape = Shape::legacy(config.max_width(), indent);
863         let shape = shape.block_indent(20);
864
865         assert_eq!(config.max_width(), shape.width);
866         assert_eq!(4, shape.indent.block_indent);
867         assert_eq!(28, shape.indent.alignment);
868         assert_eq!(28, shape.offset);
869     }
870 }