Coverage report for lib/src/dart_formatter.dart

Line coverage: 43 / 45 (95.6%)

All files > lib/src/dart_formatter.dart

1
// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
2
// for details. All rights reserved. Use of this source code is governed by a
3
// BSD-style license that can be found in the LICENSE file.
4
5
library dart_style.src.dart_formatter;
6
7
import 'dart:math' as math;
8
9
import 'package:analyzer/dart/ast/ast.dart';
10
import 'package:analyzer/dart/ast/token.dart';
11
import 'package:analyzer/error/error.dart';
12
import 'package:analyzer/src/dart/scanner/reader.dart';
13
import 'package:analyzer/src/dart/scanner/scanner.dart';
14
import 'package:analyzer/src/generated/parser.dart';
15
import 'package:analyzer/src/generated/source.dart';
16
import 'package:analyzer/src/string_source.dart';
17
18
import 'error_listener.dart';
19
import 'exceptions.dart';
20
import 'source_code.dart';
21
import 'source_visitor.dart';
22
import 'string_compare.dart' as string_compare;
23
import 'style_fix.dart';
24
25
/// Dart source code formatter.
26
class DartFormatter {
27
  /// The string that newlines should use.
28
  ///
29
  /// If not explicitly provided, this is inferred from the source text. If the
30
  /// first newline is `\r\n` (Windows), it will use that. Otherwise, it uses
31
  /// Unix-style line endings (`\n`).
32
  String lineEnding;
33
34
  /// The number of characters allowed in a single line.
35
  final int pageWidth;
36
37
  /// The number of characters of indentation to prefix the output lines with.
38
  final int indent;
39
40
  final Set<StyleFix> fixes = new Set();
41
42
  /// Creates a new formatter for Dart code.
43
  ///
44
  /// If [lineEnding] is given, that will be used for any newlines in the
45
  /// output. Otherwise, the line separator will be inferred from the line
46
  /// endings in the source file.
47
  ///
48
  /// If [indent] is given, that many levels of indentation will be prefixed
49
  /// before each resulting line in the output.
50
  ///
51
  /// While formatting, also applies any of the given [fixes].
523
  DartFormatter(
53
      {this.lineEnding, int pageWidth, int indent, Iterable<StyleFix> fixes})
54
      : pageWidth = pageWidth ?? 80,
55
        indent = indent ?? 0 {
564
    if (fixes != null) this.fixes.addAll(fixes);
57
  }
58
59
  /// Formats the given [source] string containing an entire Dart compilation
60
  /// unit.
61
  ///
62
  /// If [uri] is given, it is a [String] or [Uri] used to identify the file
63
  /// being formatted in error messages.
641
  String format(String source, {uri}) {
65
    if (uri == null) {
66
      // Do nothing.
671
    } else if (uri is Uri) {
680
      uri = uri.toString();
691
    } else if (uri is String) {
70
      // Do nothing.
71
    } else {
720
      throw new ArgumentError("uri must be `null`, a Uri, or a String.");
73
    }
74
751
    return formatSource(
761
            new SourceCode(source, uri: uri, isCompilationUnit: true))
771
        .text;
78
  }
79
80
  /// Formats the given [source] string containing a single Dart statement.
811
  String formatStatement(String source) {
823
    return formatSource(new SourceCode(source, isCompilationUnit: false)).text;
83
  }
84
85
  /// Formats the given [source].
86
  ///
87
  /// Returns a new [SourceCode] containing the formatted code and the resulting
88
  /// selection, if any.
893
  SourceCode formatSource(SourceCode source) {
903
    var errorListener = new ErrorListener();
91
92
    // Tokenize the source.
936
    var reader = new CharSequenceReader(source.text);
949
    var stringSource = new StringSource(source.text, source.uri);
953
    var scanner = new Scanner(stringSource, reader, errorListener);
963
    var startToken = scanner.tokenize();
976
    var lineInfo = new LineInfo(scanner.lineStarts);
98
99
    // Infer the line ending if not given one. Do it here since now we know
100
    // where the lines start.
1013
    if (lineEnding == null) {
102
      // If the first newline is "\r\n", use that. Otherwise, use "\n".
1039
      if (scanner.lineStarts.length > 1 &&
1049
          scanner.lineStarts[1] >= 2 &&
10518
          source.text[scanner.lineStarts[1] - 2] == '\r') {
1061
        lineEnding = "\r\n";
107
      } else {
1083
        lineEnding = "\n";
109
      }
110
    }
111
1123
    errorListener.throwIfErrors();
113
114
    // Parse it.
1153
    var parser = new Parser(stringSource, errorListener);
1163
    parser.enableOptionalNewAndConst = true;
1173
    parser.enableSetLiterals = true;
118
119
    AstNode node;
1203
    if (source.isCompilationUnit) {
1213
      node = parser.parseCompilationUnit(startToken);
122
    } else {
1232
      node = parser.parseStatement(startToken);
124
125
      // Make sure we consumed all of the source.
1264
      var token = node.endToken.next;
1274
      if (token.type != TokenType.EOF) {
1281
        var error = new AnalysisError(
129
            stringSource,
1301
            token.offset,
1316
            math.max(token.length, 1),
132
            ParserErrorCode.UNEXPECTED_TOKEN,
1332
            [token.lexeme]);
134
1352
        throw new FormatterException([error]);
136
      }
137
    }
138
1393
    errorListener.throwIfErrors();
140
141
    // Format it.
1423
    var visitor = new SourceVisitor(this, lineInfo, source);
1433
    var output = visitor.run(node);
144
145
    // Sanity check that only whitespace was changed if that's all we expect.
1466
    if (fixes.isEmpty &&
1476
        !string_compare.equalIgnoringWhitespace(source.text, output.text)) {
1483
      throw new UnexpectedOutputException(source.text, output.text);
149
    }
150
151
    return output;
152
  }
153
}