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