Chromium Code Reviews| Index: pkg/analyzer_experimental/lib/src/services/formatter_impl.dart |
| =================================================================== |
| --- pkg/analyzer_experimental/lib/src/services/formatter_impl.dart (revision 0) |
| +++ pkg/analyzer_experimental/lib/src/services/formatter_impl.dart (revision 0) |
| @@ -0,0 +1,291 @@ |
| +// Copyright (c) 2013, 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 formatter_impl; |
| + |
| + |
| +import 'dart:io'; |
|
Bob Nystrom
2013/06/06 22:23:06
Nit, but Nathan and I put organize our imports lik
pquitslund
2013/06/07 19:45:01
Done.
|
| +import 'package:analyzer_experimental/src/generated/scanner.dart'; |
| +import 'package:analyzer_experimental/analyzer.dart'; |
| +import 'package:analyzer_experimental/src/generated/parser.dart'; |
| +import 'package:analyzer_experimental/src/generated/source.dart'; |
|
Bob Nystrom
2013/06/06 22:23:06
These are fine, but you can also use relative impo
pquitslund
2013/06/07 19:45:01
I thought this would lead to woes if I ever wanted
Bob Nystrom
2013/06/10 21:15:46
It's very confusing, unfortunately. The basic rule
|
| + |
| + |
| +/// OS line separator. (TODO: may not be necessary) |
|
messick
2013/06/07 13:51:36
TODO(ldap) everywhere? It probably seems redundant
pquitslund
2013/06/07 19:45:01
Done.
|
| +const String NEW_LINE = '\n' ; //Platform.pathSeparator; |
|
Bob Nystrom
2013/06/06 22:23:06
Ditch the type annotation.
pquitslund
2013/06/07 19:45:01
Oh my. This is taking some getting used to. :) D
|
| + |
| +/// Formatter options. |
| +class FormatterOptions { |
| + |
| + /// Create formatter options with defaults derived (where defined) from |
| + /// the style guide: <http://www.dartlang.org/articles/style-guide/>. |
| + const FormatterOptions({this.initialIndentationLevel:0, |
|
Bob Nystrom
2013/06/06 22:23:06
Space after ":".
pquitslund
2013/06/07 19:45:01
Done. If only I had a formatter to do this for me
|
| + this.indentPerLevel:2, |
| + this.lineSeparator:NEW_LINE, |
| + this.pageWidth:80, |
| + this.tabSize:2}); |
| + |
| + final String lineSeparator; |
| + final int initialIndentationLevel; |
| + final int indentPerLevel; |
| + final int tabSize; |
| + final int pageWidth; |
| +} |
| + |
| + |
| +/// 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 = '']); |
| + |
| + FormatterException.forError(List<AnalysisError> errors) : |
| + // TODO: add descriptive message based on errors |
| + message = 'an analysis error occured during format'; |
| + |
| + String toString() => 'FormatterException: $message'; |
| + |
| +} |
| + |
| +/// Specifies the kind of code snippet to format. |
| +class CodeKind { |
| + |
| + final int index; |
| + |
| + const CodeKind(this.index); |
| + |
| + /// A compilation unit snippet. |
| + static const COMPILATION_UNIT = const CodeKind(0); |
| + |
| + /// A statement snippet. |
| + static const STATEMENT = const CodeKind(1); |
| + |
| +} |
| + |
| +/// Dart source code formatter. |
| +abstract class CodeFormatter { |
|
messick
2013/06/07 13:51:36
I'm not clear what your plan is but I wasn't expec
pquitslund
2013/06/07 19:45:01
See response to Bob below.
|
| + |
| + factory CodeFormatter([FormatterOptions options = const FormatterOptions()]) |
| + => new CodeFormatterImpl(options); |
| + |
| + /// Format the specified portion (from [offset] with [length]) of the given |
| + /// [source] string, optionally providing an [indentationLevel]. |
| + String format(CodeKind kind, String source, {int offset, int end, |
| + int indentationLevel:0}); |
|
Bob Nystrom
2013/06/06 22:23:06
Indent a line continuation +4, not +2.
pquitslund
2013/06/07 19:45:01
Done.
|
| + |
| +} |
| + |
| +class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener { |
|
Bob Nystrom
2013/06/06 22:23:06
Unify this with CodeFormatter. Simpler = better. :
pquitslund
2013/06/07 19:45:01
OK. I *might*. My thinking here is that I really
Brian Wilkerson
2013/06/07 19:55:11
Another alternative would be to not implement Anal
|
| + |
| + final FormatterOptions options; |
| + final List<AnalysisError> errors; |
| + |
| + CodeFormatterImpl(this.options) : errors = new List<AnalysisError>(); |
| + |
| + String format(CodeKind kind, String source, {int offset, int end, |
| + int indentationLevel:0}) { |
| + |
| + var start = tokenize(source); |
| + _checkForErrors(); |
| + |
| + var node = parse(kind, start); |
| + _checkForErrors(); |
| + |
| + // To be continued... |
| + |
| + return source; |
| + } |
| + |
| + ASTNode parse(CodeKind kind, Token start) { |
|
Bob Nystrom
2013/06/06 22:23:06
The style guide would name this "AstNode".
messick
2013/06/07 13:51:36
We should talk to Konstantin to see if we can get
pquitslund
2013/06/07 19:45:01
Aha. Something for Konstantin (or Brian).
Thanks
pquitslund
2013/06/07 19:45:01
Good point!
|
| + |
| + var parser = new Parser(null, this); |
| + |
| + switch (kind) { |
| + case CodeKind.COMPILATION_UNIT: |
| + return parser.parseCompilationUnit(start); |
| + case CodeKind.STATEMENT: |
| + return parser.parseStatement(start); |
|
messick
2013/06/07 13:51:36
At a guess, I'd expect refactoring to want a few m
pquitslund
2013/06/07 19:45:01
Absolutely. Need to crawl before I can walk thoug
|
| + } |
| + |
| + throw new FormatterException('Unsupported format kind: $kind'); |
| + } |
| + |
| + _checkForErrors() { |
| + if (errors.length > 0) { |
| + throw new FormatterException.forError(errors); |
| + } |
| + } |
| + |
| + void onError(AnalysisError error){ |
|
Bob Nystrom
2013/06/06 22:23:06
Space before "{".
pquitslund
2013/06/07 19:45:01
Eagle eye. Fixed!
|
| + errors.add(error); |
| + } |
| + |
| + Token tokenize(String source) { |
| + var scanner = new StringScanner(null, source, this); |
| + return scanner.tokenize(); |
|
Brian Wilkerson
2013/06/06 22:21:58
I noticed that you're testing for new-lines in the
pquitslund
2013/06/07 19:45:01
Cool. Great tip. I'll look into that. Thanks!
|
| + } |
| + |
| +} |
| + |
| +/// Placeholder class to hold a reference to the Class object representing |
| +/// the Dart keyword void. |
| +class Void extends Object { |
|
messick
2013/06/07 13:51:36
I hope you'll be able to use Keyword.VOID instead
pquitslund
2013/06/07 19:45:01
The issue is that I really like to use VOID as a t
|
| + |
| +} |
| + |
| + |
| +/// Records a sequence of edits to a source string that will cause the string |
| +/// to be formatted when applied. |
| +class EditRecorder { |
| + |
| + final FormatterOptions options; |
| + |
| + int column = 0; |
| + |
| + int sourceIndex = 0; |
| + String source = ''; |
| + |
| + Token currentToken; |
| + |
| + int indentationLevel = 0; |
| + int numberOfIndentations = 0; |
| + |
| + bool isIndentNeeded = false; |
| + |
| + EditRecorder(this.options); |
| + |
| + /// Count the number of whitespace chars beginning at the current |
| + /// [sourceIndex]. |
| + int countWhitespace() { |
| + var count = 0; |
| + for (var i = sourceIndex; i < source.length; ++i) { |
| + if (isIndentChar(source[i])) { |
| + ++count; |
| + } else { |
| + break; |
| + } |
| + } |
| + return count; |
| + } |
| + |
| + /// Indent. |
| + void indent() { |
| + indentationLevel += options.indentPerLevel; |
|
messick
2013/06/07 13:51:36
These names are confusing. What's a "level"?
inden
pquitslund
2013/06/07 19:45:01
Working on it. Thanks!
|
| + numberOfIndentations++; |
| + } |
| + |
| + /// Test if there is a newline at the given source [index]. |
| + bool isNewlineAt(int index) { |
| + if (index < 0 || index + NEW_LINE.length > source.length) { |
| + return false; |
| + } |
| + for (var i = 0; i < NEW_LINE.length; i++) { |
|
Bob Nystrom
2013/06/06 22:23:06
Do you need to increment index here too?
pquitslund
2013/06/07 19:45:01
Time for some more tests! :)
|
| + if (source[index] != NEW_LINE[i]) { |
| + return false; |
| + } |
| + } |
| + return true; |
| + } |
| + |
| +} |
| + |
| +final String SPACE = ' '; |
|
Bob Nystrom
2013/06/06 22:23:06
"final String" -> "const".
pquitslund
2013/06/07 19:45:01
Done.
|
| + |
| +bool isIndentChar(String ch) => ch == SPACE; // TODO(pquitslund) also check tab |
| + |
| + |
| +/// Manages stored [Edit]s. |
| +abstract class EditStore { |
|
Brian Wilkerson
2013/06/06 22:21:58
Edits and edit stores sound like a re-usable piece
messick
2013/06/07 13:51:36
I think so, too.
|
| + |
| + /// Add an [Edit] that describes a textual [replacement] of a text interval |
| + /// starting at the given [offset] spanning the given [length]. |
| + void addEdit(int offset, int length, String replacement); |
| + |
| + /// Get the index of the current edit (for use in caching location |
| + /// information). |
| + int getCurrentEditIndex(); |
| + |
| + /// Get the underlying sequence of [Edit]s. |
| + List<Edit> get edits; |
| + |
| + /// Get the last edit. |
| + Edit getLastEdit(); |
|
Bob Nystrom
2013/06/06 22:23:06
You'll already have edits.last, so I'd ditch this.
pquitslund
2013/06/07 19:45:01
Right but for some reason I convinced myself that
Bob Nystrom
2013/06/10 21:15:46
They could just do edits.isEmpty to check for that
|
| + |
| + /// Add an [Edit] that describes an insertion of text starting at the given |
| + /// [offset]. |
| + void insert(int offset, String insertedString); |
| + |
| + /// Reset cached state. |
| + void reset(); |
| + |
| +} |
| + |
| + /// Basic un-optimized [EditStore] suitable for subclassing. |
| + class BasicEditStore implements EditStore { |
|
Bob Nystrom
2013/06/06 22:23:06
You can unify this with EditStore. If users don't
pquitslund
2013/06/07 19:45:01
Ah yes. Good point!
Done.
|
| + |
| + final List<Edit> edits = new List<Edit>(); |
|
Bob Nystrom
2013/06/06 22:23:06
final edits = <Edit>[];
pquitslund
2013/06/07 19:45:01
Done.
|
| + |
| + /// Add the given [Edit] to the end of the edit sequence. |
| + void add(Edit edit) { |
| + edits.add(edit); |
| + } |
| + |
| + void addEdit(int offset, int length, String replacement) { |
| + add(new Edit(offset, length, replacement)); |
| + } |
| + |
| + //TODO(pquitslund): verify that this should be "last" vs. "next" |
|
messick
2013/06/07 13:51:36
I wonder if this comment is in the right place?
pquitslund
2013/06/07 19:45:01
Done.
|
| + int getCurrentEditIndex() => edits.length - 1; |
| + |
| + Edit getLastEdit() => edits.isEmpty ? null : edits.last; |
| + |
| + void insert(int offset, String insertedString) { |
| + addEdit(offset, 0, insertedString); |
| + } |
| + |
| + void reset() { |
| + edits.clear(); |
| + } |
| + |
| + String toString() => 'EditStore( ${edits.toString()} )'; |
| + |
| + } |
|
Bob Nystrom
2013/06/06 22:23:06
Unindent.
pquitslund
2013/06/07 19:45:01
Nothing escapes you!
|
| + |
| + |
| + |
| +/// Describes a text edit. |
| +class Edit { |
| + |
| + /// The offset at which to apply the edit. |
| + final int offset; |
| + |
| + /// The length of the text interval to replace. |
| + final int length; |
| + |
| + /// The replacement text. |
| + final String replacement; |
| + |
| + /// Create an edit. |
| + const Edit(this.offset, this.length, this.replacement); |
| + |
| + /// Create an edit for the given [range]. |
| + Edit.forRange(SourceRange range, String replacement): |
| + this(range.offset, range.length, replacement); |
| + |
| + String toString() => '${offset < 0 ? '(' : 'X('} offset: ${offset} , ' |
| + 'length ${length}, replacement :> ${replacement} <:)'; |
| + |
| +} |
| + |
| +/// An AST visitor that drives formatting heuristics. |
| +class FormattingEngine extends RecursiveASTVisitor<Void> { |
| + |
| + final FormatterOptions options; |
| + |
| + FormattingEngine(this.options); |
| + |
| +} |