diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..8febba96 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +*No changes yet!* diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..5c60afea --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright 2014, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..48637f21 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +The dart_style package defines an automatic, opinionated formatter for Dart +code. It replaces the whitespace in your program with what it deems to be the +best formatting for it. Resulting code should following the [Dart style guide][] +but, moreso, should look nice to most human readers, most of the time. + +It handles indentation, inline whitespace and (by far the most difficult), +intelligent line wrapping. It has no problems with nested collections, function +expressions, long argument lists, or otherwise tricky code. + +## Running it + +The package exposes a simple command-line wrapper around the core formatting +library. The easiest way to invoke it is to [globally activate][] the package +and let pub put its executable on your path: + + $ pub global activate dart_style + $ dartfmt ... + +If you don't want `dartfmt` on your path, you can run it explicitly: + + $ pub global activate dart_style --no-executables + $ pub global run dart_style:format ... + +The formatter takes a list of paths, which can point to directories or files. +If the path is a directory, it processes every `.dart` file in that directory +or any of its subdirectories. + +By default, it formats each file and just prints the resulting code to stdout. +If you pass `-w`, it will instead overwrite your existing files with the +formatted results. + +You may pass an `--line-length` option to control the width of the page that it +wraps lines to fit within, but you're strongly encouraged to keep the default +line length of 80 columns. + +## Using it programmatically + +The package also exposes a single dart_style library containing a programmatic +API for formatting code. Simple usage looks like this: + + import 'package:dart_style/dart_style.dart'; + + main() { + var formatter = new DartFormatter(); + + try { + formatter.format(""" + library an_entire_compilation_unit; + + class SomeClass {} + """); + + formatter.formatStatement("aSingle(statement);"); + } on FormatterException catch (ex) { + print(ex); + } + } + +[dart style guide]: https://www.dartlang.org/articles/style-guide/ +[globally activate]: https://www.dartlang.org/tools/pub/cmd/pub-global.html + +## Stability + +You can rely on the formatter to not break your code or change its semantics. +If it does do so, this is a critical bug and we'll fix it quickly. + +The heuristics the formatter uses to determine the "best" way to split a line +are still being developed and may change over time. The ones today cover most +common uses, but there's room for more refinement. We don't promise that code +produced by the formatter today will be identical to the same code run through +a later version of the formatter. We do hope that you'll like the output of the +later version more. \ No newline at end of file diff --git a/bin/fmt.dart b/bin/fmt.dart deleted file mode 100644 index bf21ac0c..00000000 --- a/bin/fmt.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'dart:io'; - -import 'package:path/path.dart' as p; - -import 'package:dart_style/dart_style.dart'; - -main(List args) { - if (args.length == 1) { - if (new Directory(args[0]).existsSync()) { - reformatDirectory(args[0]); - } else if (new File(args[0]).existsSync()) { - reformatFile(args[0]); - } else { - // TODO(rnystrom): Report error. - } - - return; - } - - // This code is just for testing right now. - - /* -1234567890123456789012345678901234567890 - */ - formatStmt("sendPort.send({'type': 'error', 'error': 'oops'});"); -} - -void formatStmt(String source, [int pageWidth = 40]) { - var result = new DartFormatter(pageWidth: pageWidth).formatStatement(source); - - drawRuler("before", pageWidth); - print(source); - drawRuler("after", pageWidth); - print(result); -} - -void formatUnit(String source, [int pageWidth = 40]) { - var result = new DartFormatter(pageWidth: pageWidth).format(source); - - drawRuler("before", pageWidth); - print(source); - drawRuler("after", pageWidth); - print(result); -} - -void drawRuler(String label, int width) { - var padding = " " * (width - label.length - 1); - print("$label:$padding|"); -} - -/// Runs the formatter on every .dart file in [path] (and its subdirectories), -/// and replaces them with their formatted output. -void reformatDirectory(String path) { - for (var entry in new Directory(path).listSync(recursive: true)) { - if (!entry.path.endsWith(".dart")) continue; - - var relative = p.relative(entry.path, from: path); - print(relative); - - var formatter = new DartFormatter(); - try { - var file = entry as File; - var source = file.readAsStringSync(); - var formatted = formatter.format(source); - file.writeAsStringSync(formatted); - print("$relative: done"); - } on FormatterException catch(err) { - print("$relative: failed with\n$err"); - } - } -} - -void reformatFile(String path) { - var formatter = new DartFormatter(); - try { - var file = new File(path); - var source = file.readAsStringSync(); - var formatted = formatter.format(source); - file.writeAsStringSync(formatted); - print("$path: done"); - } on FormatterException catch(err) { - print("$path: failed with\n$err"); - } -} diff --git a/bin/format.dart b/bin/format.dart new file mode 100644 index 00000000..5dc4a4b1 --- /dev/null +++ b/bin/format.dart @@ -0,0 +1,108 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:path/path.dart' as p; + +import 'package:dart_style/dart_style.dart'; + +bool overwrite = false; +int lineLength = 80; + +void main(List args) { + var parser = new ArgParser(); + parser.addFlag("help", abbr: "h", negatable: false, + help: "Shows usage information."); + parser.addOption("line-length", abbr: "l", + help: "Wrap lines longer than this.", + defaultsTo: "80"); + parser.addFlag("overwrite", abbr: "w", negatable: false, + help: "Overwrite input files with formatted output.\n" + "If unset, prints results to standard output."); + + var options = parser.parse(args); + + if (options["help"]) { + printUsage(parser); + return; + } + + overwrite = options["overwrite"]; + + try { + lineLength = int.parse(options["line-length"]); + } on FormatException catch (_) { + printUsage(parser, '--line-length must be an integer, was ' + '"${options['line-length']}".'); + exitCode = 64; + return; + } + + if (options.rest.isEmpty) { + printUsage(parser, + "Please provide at least one directory or file to format."); + exitCode = 64; + return; + } + + for (var path in options.rest) { + var directory = new Directory(path); + if (directory.existsSync()) { + processDirectory(directory); + continue; + } + + var file = new File(path); + if (file.existsSync()) { + processFile(file); + } else { + stderr.writeln('No file or directory found at "$path".'); + } + } +} + +void printUsage(ArgParser parser, [String error]) { + var output = stdout; + + var message = "Reformats whitespace in Dart source files."; + if (error != null) { + message = error; + output = stdout; + } + + output.write("""$message + +Usage: dartfmt [-l ] + +${parser.usage} +"""); +} + +/// Runs the formatter on every .dart file in [path] (and its subdirectories), +/// and replaces them with their formatted output. +void processDirectory(Directory directory) { + print("Formatting directory ${directory.path}:"); + for (var entry in directory.listSync(recursive: true)) { + if (!entry.path.endsWith(".dart")) continue; + + var relative = p.relative(entry.path, from: directory.path); + processFile(entry, relative); + } +} + +/// Runs the formatter on [file]. +void processFile(File file, [String label]) { + if (label == null) label = file.path; + + var formatter = new DartFormatter(pageWidth: lineLength); + try { + var output = formatter.format(file.readAsStringSync()); + if (overwrite) { + file.writeAsStringSync(output); + print("Formatted $label"); + } else { + print(output); + } + } on FormatterException catch (err) { + stderr.writeln("Failed $label:\n$err"); + } +} diff --git a/example/format.dart b/example/format.dart new file mode 100644 index 00000000..9dca9f65 --- /dev/null +++ b/example/format.dart @@ -0,0 +1,29 @@ +import 'package:dart_style/dart_style.dart'; + +void main(List args) { + formatStmt("sendPort.send({'type': 'error', 'error': 'oops'});"); + formatUnit("class Foo{}"); +} + +void formatStmt(String source, [int pageWidth = 40]) { + var result = new DartFormatter(pageWidth: pageWidth).formatStatement(source); + + drawRuler("before", pageWidth); + print(source); + drawRuler("after", pageWidth); + print(result); +} + +void formatUnit(String source, [int pageWidth = 40]) { + var result = new DartFormatter(pageWidth: pageWidth).format(source); + + drawRuler("before", pageWidth); + print(source); + drawRuler("after", pageWidth); + print(result); +} + +void drawRuler(String label, int width) { + var padding = " " * (width - label.length - 1); + print("$label:$padding|"); +} \ No newline at end of file diff --git a/lib/dart_style.dart b/lib/dart_style.dart index fd90b671..494ab7e3 100644 --- a/lib/dart_style.dart +++ b/lib/dart_style.dart @@ -5,3 +5,4 @@ library dart_style; export 'src/dart_formatter.dart'; +export 'src/formatter_exception.dart'; diff --git a/lib/src/dart_formatter.dart b/lib/src/dart_formatter.dart index c9ca31f9..b77e110f 100644 --- a/lib/src/dart_formatter.dart +++ b/lib/src/dart_formatter.dart @@ -9,48 +9,11 @@ import 'package:analyzer/src/generated/parser.dart'; import 'package:analyzer/src/generated/scanner.dart'; import 'package:analyzer/src/generated/source.dart'; +import 'error_listener.dart'; import 'source_visitor.dart'; -/// Thrown when an error occurs in formatting. -class FormatterException implements Exception { - /// A message describing the error. - final String message; - - /// Creates a new FormatterException with an optional error [message]. - const FormatterException(this.message); - - factory FormatterException.forErrors(List errors, - [LineInfo lines]) { - var buffer = new StringBuffer(); - - for (var error in errors) { - // Show position information if we have it. - var pos; - if (lines != null) { - var start = lines.getLocation(error.offset); - var end = lines.getLocation(error.offset + error.length); - pos = "${start.lineNumber}:${start.columnNumber}-"; - if (start.lineNumber == end.lineNumber) { - pos += "${end.columnNumber}"; - } else { - pos += "${end.lineNumber}:${end.columnNumber}"; - } - } else { - pos = "${error.offset}...${error.offset + error.length}"; - } - buffer.writeln("$pos: ${error.message}"); - } - - return new FormatterException(buffer.toString()); - } - - String toString() => message; -} - -// TODO(rnystrom): We may not want to export this class publicly. At the very -// least, making things like lineInfo public are weird. /// Dart source code formatter. -class DartFormatter implements AnalysisErrorListener { +class DartFormatter { /// The string that newlines should use. /// /// If not explicitly provided, this is inferred from the source text. If the @@ -64,10 +27,6 @@ class DartFormatter implements AnalysisErrorListener { /// The number of levels of indentation to prefix the output lines with. final int indent; - final errors = []; - - LineInfo lineInfo; - /// Creates a new formatter for Dart code. /// /// If [lineEnding] is given, that will be used for any newlines in the @@ -94,39 +53,30 @@ class DartFormatter implements AnalysisErrorListener { } String _format(String source, parseFn(Parser parser, Token start)) { - var startToken = _tokenize(source); - _checkForErrors(); + var errorListener = new ErrorListener(); + var startToken = _tokenize(source, errorListener); + errorListener.throwIfErrors(); - var parser = new Parser(null, this); + var parser = new Parser(null, errorListener); parser.parseAsync = true; var node = parseFn(parser, startToken); - _checkForErrors(); + errorListener.throwIfErrors(); var buffer = new StringBuffer(); - var visitor = new SourceVisitor(this, lineInfo, source, buffer); + var visitor = new SourceVisitor(this, errorListener.lineInfo, source, + buffer); visitor.run(node); return buffer.toString(); } - void onError(AnalysisError error) { - errors.add(error); - } - - /// Throws a [FormatterException] if any errors have been reported. - void _checkForErrors() { - if (errors.length > 0) { - throw new FormatterException.forErrors(errors, lineInfo); - } - } - - Token _tokenize(String source) { + Token _tokenize(String source, ErrorListener errorListener) { var reader = new CharSequenceReader(source); - var scanner = new Scanner(null, reader, this); + var scanner = new Scanner(null, reader, errorListener); var token = scanner.tokenize(); - lineInfo = new LineInfo(scanner.lineStarts); + errorListener.lineInfo = new LineInfo(scanner.lineStarts); // Infer the line ending if not given one. Do it here since now we know // where the lines start. diff --git a/lib/src/error_listener.dart b/lib/src/error_listener.dart new file mode 100644 index 00000000..9c5c0025 --- /dev/null +++ b/lib/src/error_listener.dart @@ -0,0 +1,28 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library dart_style.src.error_listener; + +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/src/generated/source.dart'; + +import 'formatter_exception.dart'; + +/// A simple [AnalysisErrorListener] that just collects the reported errors. +class ErrorListener implements AnalysisErrorListener { + LineInfo lineInfo; + + final _errors = []; + + void onError(AnalysisError error) { + _errors.add(error); + } + + /// Throws a [FormatterException] if any errors have been reported. + void throwIfErrors() { + if (_errors.isEmpty) return; + + throw new FormatterException.forErrors(_errors, lineInfo); + } +} \ No newline at end of file diff --git a/lib/src/formatter_exception.dart b/lib/src/formatter_exception.dart new file mode 100644 index 00000000..bb4e1995 --- /dev/null +++ b/lib/src/formatter_exception.dart @@ -0,0 +1,44 @@ +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library dart_style.src.formatter_exception; + +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/src/generated/source.dart'; + +/// Thrown when an error occurs in formatting. +class FormatterException implements Exception { + /// A message describing the error. + final String message; + + /// Creates a new FormatterException with an optional error [message]. + const FormatterException(this.message); + + factory FormatterException.forErrors(List errors, + [LineInfo lines]) { + var buffer = new StringBuffer(); + + for (var error in errors) { + // Show position information if we have it. + var pos; + if (lines != null) { + var start = lines.getLocation(error.offset); + var end = lines.getLocation(error.offset + error.length); + pos = "${start.lineNumber}:${start.columnNumber}-"; + if (start.lineNumber == end.lineNumber) { + pos += "${end.columnNumber}"; + } else { + pos += "${end.lineNumber}:${end.columnNumber}"; + } + } else { + pos = "${error.offset}...${error.offset + error.length}"; + } + buffer.writeln("$pos: ${error.message}"); + } + + return new FormatterException(buffer.toString()); + } + + String toString() => message; +} diff --git a/pubspec.lock b/pubspec.lock index 11ff513b..4cd017b5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -16,7 +16,7 @@ packages: collection: description: collection source: hosted - version: "1.0.0" + version: "1.1.0" logging: description: logging source: hosted diff --git a/pubspec.yaml b/pubspec.yaml index ffa6748e..840d13b8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,15 @@ name: dart_style +version: 0.1.0-dev +description: Opinionated, automatic Dart source code formatter. +author: "Dart Team " +homepage: https://github.com/dart-lang/dart_style dependencies: analyzer: ">=0.22.0 <0.23.0" + args: ">=0.12.1 <0.13.0" + path: ">=1.0.0 <2.0.0" dev_dependencies: - unittest: ">=0.11.0 <0.12.0" browser: ">=0.10.0 <0.11.0" - path: ">=1.0.0 <2.0.0" \ No newline at end of file + unittest: ">=0.11.0 <0.12.0" +executables: + dartfmt: format + \ No newline at end of file