Chromium Code Reviews| Index: pkg/analyzer_experimental/lib/src/services/formatter_impl.dart |
| =================================================================== |
| --- pkg/analyzer_experimental/lib/src/services/formatter_impl.dart (revision 24755) |
| +++ pkg/analyzer_experimental/lib/src/services/formatter_impl.dart (working copy) |
| @@ -11,8 +11,8 @@ |
| import 'package:analyzer_experimental/src/generated/parser.dart'; |
| import 'package:analyzer_experimental/src/generated/scanner.dart'; |
| import 'package:analyzer_experimental/src/generated/source.dart'; |
| +import 'package:analyzer_experimental/src/services/writer.dart'; |
| - |
| /// OS line separator. --- TODO(pquitslund): may not be necessary |
| const NEW_LINE = '\n' ; //Platform.pathSeparator; |
| @@ -22,15 +22,17 @@ |
| /// Create formatter options with defaults derived (where defined) from |
| /// the style guide: <http://www.dartlang.org/articles/style-guide/>. |
| const FormatterOptions({this.initialIndentationLevel: 0, |
| - this.indentPerLevel: 2, |
| + this.spacesPerIndent: 2, |
| this.lineSeparator: NEW_LINE, |
| this.pageWidth: 80, |
| + this.tabsForIndent: false, |
| this.tabSize: 2}); |
| final String lineSeparator; |
| final int initialIndentationLevel; |
| - final int indentPerLevel; |
| + final int spacesPerIndent; |
| final int tabSize; |
| + final bool tabsForIndent; |
| final int pageWidth; |
| } |
| @@ -83,11 +85,9 @@ |
| class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener { |
| final FormatterOptions options; |
| - final EditRecorder recorder; |
| final errors = <AnalysisError>[]; |
| - CodeFormatterImpl(FormatterOptions options) : this.options = options, |
| - recorder = new EditRecorder(options); |
| + CodeFormatterImpl(this.options); |
| String format(CodeKind kind, String source, {int offset, int end, |
| int indentationLevel:0}) { |
| @@ -98,8 +98,10 @@ |
| var node = parse(kind, start); |
| checkForErrors(); |
| - var formatter = new FormattingEngine(options); |
| - return formatter.format(source, node, start, kind, recorder); |
| + var formatter = new SourceVisitor(options); |
| + node.accept(formatter); |
| + |
| + return formatter.writer.toString(); |
| } |
| ASTNode parse(CodeKind kind, Token start) { |
| @@ -134,454 +136,810 @@ |
| } |
| -/// Records a sequence of edits to a source string that will cause the string |
| -/// to be formatted when applied. |
| -class EditRecorder { |
| - final FormatterOptions options; |
| - final EditStore editStore; |
| +/// An AST visitor that drives formatting heuristics. |
| +class SourceVisitor implements ASTVisitor { |
| - int column = 0; |
| + /// The writer to which the source is to be written. |
| + SourceWriter writer; |
| - int sourceIndex = 0; |
| - String source = ''; |
| + /// Initialize a newly created visitor to write source code representing |
| + /// the visited nodes to the given [writer]. |
| + SourceVisitor(FormatterOptions options) : |
| + writer = new SourceWriter(initialIndent: options.initialIndentationLevel, |
|
Brian Wilkerson
2013/07/03 22:03:02
This seems weird. Why not just pass in the options
pquitslund
2013/07/03 22:21:39
I was on the fence. Leaning towards doing just th
|
| + lineSeparator: options.lineSeparator); |
| - Token currentToken; |
| + visitAdjacentStrings(AdjacentStrings node) { |
| + visitList(node.strings, ' '); |
| + } |
| - int numberOfIndentations = 0; |
| + visitAnnotation(Annotation node) { |
| + writer.print('@'); |
| + visit(node.name); |
| + visitPrefixed('.', node.constructorName); |
| + visit(node.arguments); |
| + } |
| - bool needsIndent = false; |
| + visitArgumentDefinitionTest(ArgumentDefinitionTest node) { |
| + writer.print('?'); |
| + visit(node.identifier); |
| + } |
| - EditRecorder(this.options): editStore = new EditStore(); |
| + visitArgumentList(ArgumentList node) { |
| + writer.print('('); |
| + visitList(node.arguments, ', '); |
| + writer.print(')'); |
| + } |
| - /// 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) { |
| - editStore.addEdit(offset, length, replacement); |
| + visitAsExpression(AsExpression node) { |
| + visit(node.expression); |
| + writer.print(' as '); |
| + visit(node.type); |
| } |
| - /// Advance past the given expected [token] (or fail if not matched). |
| - void advance(Token token) { |
| - if (currentToken.lexeme == token.lexeme) { |
| + visitAssertStatement(AssertStatement node) { |
| + writer.print('assert ('); |
| + visit(node.condition); |
| + writer.print(');'); |
| + } |
| - // TODO(pquitslund) emit comments |
| -// if (needsIndent) { |
| -// advanceIndent(); |
| -// needsIndent = false; |
| -// } |
| - // Record writing a token at the current edit location |
| - advanceChars(token.length); |
| - currentToken = currentToken.next; |
| - } else { |
| - wrongToken(token.lexeme); |
| - } |
| + visitAssignmentExpression(AssignmentExpression node) { |
| + visit(node.leftHandSide); |
| + writer.print(' '); |
| + writer.print(node.operator.lexeme); |
| + writer.print(' '); |
| + visit(node.rightHandSide); |
| } |
| - /// Move indices past indent, adding an edit if needed to adjust indentation |
| - void advanceIndent() { |
| -// var indentWidth = options.indentPerLevel * indentationLevel; |
| -// var indentString = getIndentString(indentWidth); |
| -// var sourceIndentWidth = 0; |
| -// for (var i = 0; i < source.length; i++) { |
| -// if (isIndentChar(source[sourceIndex + i])) { |
| -// sourceIndentWidth += 1; |
| -// } else { |
| -// break; |
| -// } |
| -// } |
| -// var hasSameIndent = sourceIndentWidth == indentWidth; |
| -// if (hasSameIndent) { |
| -// for (var i = 0; i < indentWidth; i++) { |
| -// if (source[sourceIndex + i] != indentString[i]) { |
| -// hasSameIndent = false; |
| -// break; |
| -// } |
| -// } |
| -// if (hasSameIndent) { |
| -// advanceChars(indentWidth); |
| -// return; |
| -// } |
| -// } |
| -// addEdit(sourceIndex, sourceIndentWidth, indentString); |
| -// column += indentWidth; |
| -// sourceIndex += sourceIndentWidth; |
| - |
| - var indent = options.indentPerLevel * numberOfIndentations; |
| - |
| - spaces(indent); |
| + visitBinaryExpression(BinaryExpression node) { |
| + visit(node.leftOperand); |
| + writer.print(' '); |
| + writer.print(node.operator.lexeme); |
| + writer.print(' '); |
| + visit(node.rightOperand); |
| } |
| - String getIndentString(int indentWidth) { |
| + visitBlock(Block node) { |
| + writer.print('{'); |
| + writer.indent(); |
| - // TODO(pquitslund) a temporary workaround |
| - if (indentWidth < 0) { |
| - return ''; |
| + for (var stmt in node.statements) { |
| + writer.newline(); |
| + visit(stmt); |
| } |
| - // TODO(pquitslund) allow indent with tab chars |
| + writer.unindent(); |
| + writer.newline(); |
| + writer.print('}'); |
| + } |
| - // Fetch a precomputed indent string |
| - if (indentWidth < SPACES.length) { |
| - return SPACES[indentWidth]; |
| - } |
| + visitBlockFunctionBody(BlockFunctionBody node) { |
| + visit(node.block); |
| + } |
| - // Build un-precomputed strings dynamically |
| - var sb = new StringBuffer(); |
| - for (var i = 0; i < indentWidth; ++i) { |
| - sb.write(' '); |
| - } |
| - return sb.toString(); |
| + visitBooleanLiteral(BooleanLiteral node) { |
| + writer.print(node.literal.lexeme); |
| } |
| - /// Advance past the given expected [token] (or fail if not matched). |
| - void advanceToken(String token) { |
| - if (currentToken.lexeme == token) { |
| - advance(currentToken); |
| - } else { |
| - wrongToken(token); |
| - } |
| + visitBreakStatement(BreakStatement node) { |
| + writer.print('break'); |
| + visitPrefixed(' ', node.label); |
| + writer.print(';'); |
| } |
| - /// Advance [column] and [sourceIndex] indices by [len] characters. |
| - void advanceChars(int len) { |
| - column += len; |
| - sourceIndex += len; |
| + visitCascadeExpression(CascadeExpression node) { |
| + visit(node.target); |
| + visitList(node.cascadeSections); |
| } |
| - /// 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; |
| + visitCatchClause(CatchClause node) { |
| + visitPrefixed('on ', node.exceptionType); |
| + if (node.catchKeyword != null) { |
| + if (node.exceptionType != null) { |
| + writer.print(' '); |
| } |
| + writer.print('catch ('); |
| + visit(node.exceptionParameter); |
| + visitPrefixed(', ', node.stackTraceParameter); |
| + writer.print(') '); |
| + } else { |
| + writer.print(' '); |
| } |
| - return count; |
| + visit(node.body); |
| } |
| - /// Update indent indices. |
| - void indent() { |
| - numberOfIndentations++; |
| + visitClassDeclaration(ClassDeclaration node) { |
| + visitToken(node.abstractKeyword, ' '); |
| + writer.print('class '); |
| + visit(node.name); |
| + visit(node.typeParameters); |
| + visitPrefixed(' ', node.extendsClause); |
| + visitPrefixed(' ', node.withClause); |
| + visitPrefixed(' ', node.implementsClause); |
| + writer.print(' {'); |
| + writer.indent(); |
| + for (var member in node.members) { |
| + writer.newline(); |
| + visit(member); |
| + } |
| + |
| + writer.unindent(); |
| + writer.newline(); |
| + writer.print('}'); |
| } |
| - /// 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; |
| + visitClassTypeAlias(ClassTypeAlias node) { |
| + writer.print('typedef '); |
| + visit(node.name); |
| + visit(node.typeParameters); |
| + writer.print(' = '); |
| + if (node.abstractKeyword != null) { |
| + writer.print('abstract '); |
| } |
| - for (var i = 0; i < NEW_LINE.length; i++) { |
| - if (source[index] != NEW_LINE[i]) { |
| - return false; |
| - } |
| - } |
| - return true; |
| + visit(node.superclass); |
| + visitPrefixed(' ', node.withClause); |
| + visitPrefixed(' ', node.implementsClause); |
| + writer.print(';'); |
| } |
| - /// Newline. |
| - void newline() { |
| - // TODO(pquitslund) emit comments |
| - needsIndent = true; |
| - // If there is a newline before the edit location, do nothing. |
| - if (isNewlineAt(sourceIndex - NEW_LINE.length)) { |
| - return; |
| - } |
| - // If there is a newline after the edit location, advance over it. |
| - if (isNewlineAt(sourceIndex)) { |
| - advanceChars(NEW_LINE.length); |
| - return; |
| - } |
| - // Otherwise, replace whitespace with a newline. |
| - var charsToReplace = countWhitespace(); |
| - if (isNewlineAt(sourceIndex + charsToReplace)) { |
| - charsToReplace += NEW_LINE.length; |
| - } |
| - addEdit(sourceIndex, charsToReplace, NEW_LINE); |
| - advanceChars(charsToReplace); |
| + visitComment(Comment node) => null; |
| + |
| + visitCommentReference(CommentReference node) => null; |
| + |
| + visitCompilationUnit(CompilationUnit node) { |
| + var scriptTag = node.scriptTag; |
| + var directives = node.directives; |
| + visit(scriptTag); |
| + var prefix = scriptTag == null ? '' : ' '; |
| + visitPrefixedList(prefix, directives, ' '); |
| + prefix = scriptTag == null && directives.isEmpty ? '' : ' '; |
| + visitPrefixedList(prefix, node.declarations, ' '); |
| } |
| + visitConditionalExpression(ConditionalExpression node) { |
| + visit(node.condition); |
| + writer.print(' ? '); |
| + visit(node.thenExpression); |
| + writer.print(' : '); |
| + visit(node.elseExpression); |
| + } |
| - /// Un-indent. |
| - void unindent() { |
| - numberOfIndentations--; |
| + visitConstructorDeclaration(ConstructorDeclaration node) { |
| + visitToken(node.externalKeyword, ' '); |
| + visitToken(node.constKeyword, ' '); |
| + visitToken(node.factoryKeyword, ' '); |
| + visit(node.returnType); |
| + visitPrefixed('.', node.name); |
| + visit(node.parameters); |
| + visitPrefixedList(' : ', node.initializers, ', '); |
| + visitPrefixed(' = ', node.redirectedConstructor); |
| + visitPrefixedBody(' ', node.body); |
| } |
| - /// Space. |
| - void space() { |
| - // TODO(pquitslund) emit comments |
| -// // If there is a space before the edit location, do nothing. |
| -// if (isSpaceAt(sourceIndex - 1)) { |
| -// return; |
| -// } |
| -// // If there is a space after the edit location, advance over it. |
| -// if (isSpaceAt(sourceIndex)) { |
| -// advance(1); |
| -// return; |
| -// } |
| - // Otherwise, replace spaces with a single space. |
| - spaces(1); |
| + visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
| + visitToken(node.keyword, '.'); |
| + visit(node.fieldName); |
| + writer.print(' = '); |
| + visit(node.expression); |
| } |
| - /// Spaces. |
| - void spaces(int num) { |
| - var charsToReplace = countWhitespace(); |
| - addEdit(sourceIndex, charsToReplace, SPACES[num]); |
| - advanceChars(charsToReplace); |
| + visitConstructorName(ConstructorName node) { |
| + visit(node.type); |
| + visitPrefixed('.', node.name); |
| } |
| - wrongToken(String token) { |
| - throw new FormatterException('expected token: "${token}", ' |
| - 'actual: "${currentToken}"'); |
| + visitContinueStatement(ContinueStatement node) { |
| + writer.print('continue'); |
| + visitPrefixed(' ', node.label); |
| + writer.print(';'); |
| } |
| - String toString() => |
| - new EditOperation().apply(editStore.edits, |
| - source.substring(0, sourceIndex)); |
| + visitDeclaredIdentifier(DeclaredIdentifier node) { |
| + visitToken(node.keyword, ' '); |
| + visitSuffixed(node.type, ' '); |
| + visit(node.identifier); |
| + } |
| -} |
| + visitDefaultFormalParameter(DefaultFormalParameter node) { |
| + visit(node.parameter); |
| + if (node.separator != null) { |
| + writer.print(' '); |
| + writer.print(node.separator.lexeme); |
| + visitPrefixed(' ', node.defaultValue); |
| + } |
| + } |
| -const SPACE = ' '; |
| -final SPACES = [ |
| - '', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| - ' ', |
| -]; |
| + visitDoStatement(DoStatement node) { |
| + writer.print('do '); |
| + visit(node.body); |
| + writer.print(' while ('); |
| + visit(node.condition); |
| + writer.print(');'); |
| + } |
| + visitDoubleLiteral(DoubleLiteral node) { |
| + writer.print(node.literal.lexeme); |
| + } |
| -bool isIndentChar(String ch) => ch == SPACE; // TODO(pquitslund) also check tab |
| + visitEmptyFunctionBody(EmptyFunctionBody node) { |
| + writer.print(';'); |
| + } |
| + visitEmptyStatement(EmptyStatement node) { |
| + writer.print(';'); |
| + } |
| -/// Manages stored [Edit]s. |
| -class EditStore { |
| + visitExportDirective(ExportDirective node) { |
| + writer.print('export '); |
| + visit(node.uri); |
| + visitPrefixedList(' ', node.combinators, ' '); |
| + writer.print(';'); |
| + } |
| - const EditStore(); |
| + visitExpressionFunctionBody(ExpressionFunctionBody node) { |
| + writer.print('=> '); |
| + visit(node.expression); |
| + if (node.semicolon != null) { |
| + writer.print(';'); |
| + } |
| + } |
| - /// The underlying sequence of [Edit]s. |
| - final edits = <Edit>[]; |
| + visitExpressionStatement(ExpressionStatement node) { |
| + visit(node.expression); |
| + writer.print(';'); |
| + } |
| - /// Add the given [Edit] to the end of the edit sequence. |
| - void add(Edit edit) { |
| - edits.add(edit); |
| + visitExtendsClause(ExtendsClause node) { |
| + writer.print('extends '); |
| + visit(node.superclass); |
| } |
| - /// 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) { |
| - add(new Edit(offset, length, replacement)); |
| + visitFieldDeclaration(FieldDeclaration node) { |
| + visitToken(node.keyword, ' '); |
| + visit(node.fields); |
| + writer.print(';'); |
| } |
| - /// Get the index of the current edit (for use in caching location |
| - /// information). |
| - int getCurrentEditIndex() => edits.length - 1; |
| + visitFieldFormalParameter(FieldFormalParameter node) { |
| + visitToken(node.keyword, ' '); |
| + visitSuffixed(node.type, ' '); |
| + writer.print('this.'); |
| + visit(node.identifier); |
| + visit(node.parameters); |
| + } |
| - /// Get the last edit. |
| - Edit getLastEdit() => edits.isEmpty ? null : edits.last; |
| + visitForEachStatement(ForEachStatement node) { |
| + writer.print('for ('); |
| + visit(node.loopVariable); |
| + writer.print(' in '); |
| + visit(node.iterator); |
| + writer.print(') '); |
| + visit(node.body); |
| + } |
| - /// Add an [Edit] that describes an insertion of text starting at the given |
| - /// [offset]. |
| - void insert(int offset, String insertedString) { |
| - addEdit(offset, 0, insertedString); |
| + visitFormalParameterList(FormalParameterList node) { |
| + var groupEnd = null; |
| + writer.print('('); |
| + var parameters = node.parameters; |
| + var size = parameters.length; |
| + for (var i = 0; i < size; i++) { |
| + var parameter = parameters[i]; |
| + if (i > 0) { |
| + writer.print(', '); |
| + } |
| + if (groupEnd == null && parameter is DefaultFormalParameter) { |
| + if (identical(parameter.kind, ParameterKind.NAMED)) { |
| + groupEnd = '}'; |
| + writer.print('{'); |
| + } else { |
| + groupEnd = ']'; |
| + writer.print('['); |
| + } |
| + } |
| + parameter.accept(this); |
| + } |
| + if (groupEnd != null) { |
| + writer.print(groupEnd); |
| + } |
| + writer.print(')'); |
| } |
| - /// Reset cached state. |
| - void reset() { |
| - edits.clear(); |
| + visitForStatement(ForStatement node) { |
| + var initialization = node.initialization; |
| + writer.print('for ('); |
| + if (initialization != null) { |
| + visit(initialization); |
| + } else { |
| + visit(node.variables); |
| + } |
| + writer.print(';'); |
| + visitPrefixed(' ', node.condition); |
| + writer.print(';'); |
| + visitPrefixedList(' ', node.updaters, ', '); |
| + writer.print(') '); |
| + visit(node.body); |
| } |
| - String toString() => 'EditStore( ${edits.toString()} )'; |
| + visitFunctionDeclaration(FunctionDeclaration node) { |
| + visitSuffixed(node.returnType, ' '); |
| + visitToken(node.propertyKeyword, ' '); |
| + visit(node.name); |
| + visit(node.functionExpression); |
| + } |
| -} |
| + visitFunctionDeclarationStatement(FunctionDeclarationStatement node) { |
| + visit(node.functionDeclaration); |
| + writer.print(';'); |
| + } |
| + visitFunctionExpression(FunctionExpression node) { |
| + visit(node.parameters); |
| + writer.print(' '); |
| + visit(node.body); |
| + } |
| -/// Describes a text edit. |
| -class Edit { |
| + visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
| + visit(node.function); |
| + visit(node.argumentList); |
| + } |
| - /// The offset at which to apply the edit. |
| - final int offset; |
| + visitFunctionTypeAlias(FunctionTypeAlias node) { |
| + writer.print('typedef '); |
| + visitSuffixed(node.returnType, ' '); |
| + visit(node.name); |
| + visit(node.typeParameters); |
| + visit(node.parameters); |
| + writer.print(';'); |
| + } |
| - /// The length of the text interval to replace. |
| - final int length; |
| + visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { |
| + visitSuffixed(node.returnType, ' '); |
| + visit(node.identifier); |
| + visit(node.parameters); |
| + } |
| - /// The replacement text. |
| - final String replacement; |
| + visitHideCombinator(HideCombinator node) { |
| + writer.print('hide '); |
| + visitList(node.hiddenNames, ', '); |
| + } |
| - /// Create an edit. |
| - const Edit(this.offset, this.length, this.replacement); |
| + visitIfStatement(IfStatement node) { |
| + writer.print('if ('); |
| + visit(node.condition); |
| + writer.print(') '); |
| + visit(node.thenStatement); |
| + visitPrefixed(' else ', node.elseStatement); |
| + } |
| - /// Create an edit for the given [range]. |
| - Edit.forRange(SourceRange range, String replacement): |
| - this(range.offset, range.length, replacement); |
| + visitImplementsClause(ImplementsClause node) { |
| + writer.print('implements '); |
| + visitList(node.interfaces, ', '); |
| + } |
| - String toString() => '${offset < 0 ? '(' : 'X('} offset: ${offset} , ' |
| - 'length ${length}, replacement :> ${replacement} <:)'; |
| + visitImportDirective(ImportDirective node) { |
| + writer.print('import '); |
| + visit(node.uri); |
| + visitPrefixed(' as ', node.prefix); |
| + visitPrefixedList(' ', node.combinators, ' '); |
| + writer.print(';'); |
| + } |
| -} |
| + visitIndexExpression(IndexExpression node) { |
| + if (node.isCascaded) { |
| + writer.print('..'); |
| + } else { |
| + visit(node.array); |
| + } |
| + writer.print('['); |
| + visit(node.index); |
| + writer.print(']'); |
| + } |
| -/// Applies a sequence of [edits] to a [document]. |
| -class EditOperation { |
| + visitInstanceCreationExpression(InstanceCreationExpression node) { |
| + visitToken(node.keyword, ' '); |
| + visit(node.constructorName); |
| + visit(node.argumentList); |
| + } |
| - String apply(List<Edit> edits, String document) { |
| + visitIntegerLiteral(IntegerLiteral node) { |
| + writer.print(node.literal.lexeme); |
| + } |
| - var edit; |
| - for (var i = edits.length - 1; i >= 0; --i) { |
| - edit = edits[i]; |
| - document = replace(document, edit.offset, |
| - edit.offset + edit.length, edit.replacement); |
| + visitInterpolationExpression(InterpolationExpression node) { |
| + if (node.rightBracket != null) { |
| + writer.print('\${'); |
| + visit(node.expression); |
| + writer.print('}'); |
| + } else { |
| + writer.print('\$'); |
| + visit(node.expression); |
| } |
| + } |
| - return document; |
| + visitInterpolationString(InterpolationString node) { |
| + writer.print(node.contents.lexeme); |
| } |
| -} |
| + visitIsExpression(IsExpression node) { |
| + visit(node.expression); |
| + if (node.notOperator == null) { |
| + writer.print(' is '); |
| + } else { |
| + writer.print(' is! '); |
| + } |
| + visit(node.type); |
| + } |
| + visitLabel(Label node) { |
| + visit(node.label); |
| + writer.print(':'); |
| + } |
| -String replace(String str, int start, int end, String replacement) => |
| - str.substring(0, start) + replacement + str.substring(end); |
| + visitLabeledStatement(LabeledStatement node) { |
| + visitSuffixedList(node.labels, ' ', ' '); |
| + visit(node.statement); |
| + } |
| + visitLibraryDirective(LibraryDirective node) { |
| + writer.print('library '); |
| + visit(node.name); |
| + writer.print(';'); |
| + } |
| -/// An AST visitor that drives formatting heuristics. |
| -class FormattingEngine extends RecursiveASTVisitor { |
| + visitLibraryIdentifier(LibraryIdentifier node) { |
| + writer.print(node.name); |
| + } |
| - final FormatterOptions options; |
| + visitListLiteral(ListLiteral node) { |
| + if (node.modifier != null) { |
| + writer.print(node.modifier.lexeme); |
| + writer.print(' '); |
| + } |
| + visitSuffixed(node.typeArguments, ' '); |
| + writer.print('['); |
| + visitList(node.elements, ', '); |
| + writer.print(']'); |
| + } |
| - CodeKind kind; |
| - EditRecorder recorder; |
| + visitMapLiteral(MapLiteral node) { |
| + if (node.modifier != null) { |
| + writer.print(node.modifier.lexeme); |
| + writer.print(' '); |
| + } |
| + visitSuffixed(node.typeArguments, ' '); |
| + writer.print('{'); |
| + visitList(node.entries, ', '); |
| + writer.print('}'); |
| + } |
| - FormattingEngine(this.options); |
| + visitMapLiteralEntry(MapLiteralEntry node) { |
| + visit(node.key); |
| + writer.print(' : '); |
| + visit(node.value); |
| + } |
| - String format(String source, ASTNode node, Token start, CodeKind kind, |
| - EditRecorder recorder) { |
| + visitMethodDeclaration(MethodDeclaration node) { |
| + visitToken(node.externalKeyword, ' '); |
| + visitToken(node.modifierKeyword, ' '); |
| + visitSuffixed(node.returnType, ' '); |
| + visitToken(node.propertyKeyword, ' '); |
| + visitToken(node.operatorKeyword, ' '); |
| + visit(node.name); |
| + if (!node.isGetter) { |
| + visit(node.parameters); |
| + } |
| + visitPrefixedBody(' ', node.body); |
| + } |
| - this.kind = kind; |
| - this.recorder = recorder; |
| + visitMethodInvocation(MethodInvocation node) { |
| + if (node.isCascaded) { |
| + writer.print('..'); |
| + } else { |
| + visitSuffixed(node.target, '.'); |
| + } |
| + visit(node.methodName); |
| + visit(node.argumentList); |
| + } |
| - recorder..source = source |
| - ..currentToken = start; |
| + visitNamedExpression(NamedExpression node) { |
| + visit(node.name); |
| + visitPrefixed(' ', node.expression); |
| + } |
| - node.accept(this); |
| + visitNativeFunctionBody(NativeFunctionBody node) { |
| + writer.print('native '); |
| + visit(node.stringLiteral); |
| + writer.print(';'); |
| + } |
| - var editor = new EditOperation(); |
| - return editor.apply(recorder.editStore.edits, source); |
| + visitNullLiteral(NullLiteral node) { |
| + writer.print('null'); |
| } |
| + visitParenthesizedExpression(ParenthesizedExpression node) { |
| + writer.print('('); |
| + visit(node.expression); |
| + writer.print(')'); |
| + } |
| - visitClassDeclaration(ClassDeclaration node) { |
| + visitPartDirective(PartDirective node) { |
| + writer.print('part '); |
| + visit(node.uri); |
| + writer.print(';'); |
| + } |
| - recorder.advanceIndent(); |
| + visitPartOfDirective(PartOfDirective node) { |
| + writer.print('part of '); |
| + visit(node.libraryName); |
| + writer.print(';'); |
| + } |
| - if (node.documentationComment != null) { |
| - node.documentationComment.accept(this); |
| - } |
| + visitPostfixExpression(PostfixExpression node) { |
| + visit(node.operand); |
| + writer.print(node.operator.lexeme); |
| + } |
| - recorder..advance(node.classKeyword)..space(); |
| + visitPrefixedIdentifier(PrefixedIdentifier node) { |
| + visit(node.prefix); |
| + writer.print('.'); |
| + visit(node.identifier); |
| + } |
| - node.name.accept(this); |
| + visitPrefixExpression(PrefixExpression node) { |
| + writer.print(node.operator.lexeme); |
| + visit(node.operand); |
| + } |
| - if (node.typeParameters != null) { |
| - node.typeParameters.accept(this); |
| + visitPropertyAccess(PropertyAccess node) { |
| + if (node.isCascaded) { |
| + writer.print('..'); |
| + } else { |
| + visit(node.target); |
| + writer.print('.'); |
| } |
| - recorder.space(); |
| + visit(node.propertyName); |
| + } |
| - if (node.extendsClause != null) { |
| - node.extendsClause.accept(this); |
| - recorder.space(); |
| - } |
| + visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) { |
| + writer.print('this'); |
| + visitPrefixed('.', node.constructorName); |
| + visit(node.argumentList); |
| + } |
| - if (node.implementsClause != null) { |
| - node.implementsClause.accept(this); |
| - recorder.space(); |
| + visitRethrowExpression(RethrowExpression node) { |
| + writer.print('rethrow'); |
| + } |
| + |
| + visitReturnStatement(ReturnStatement node) { |
| + var expression = node.expression; |
| + if (expression == null) { |
| + writer.print('return;'); |
| + } else { |
| + writer.print('return '); |
| + expression.accept(this); |
| + writer.print(';'); |
| } |
| + } |
| - recorder..advance(node.leftBracket) |
| - ..indent(); |
| + visitScriptTag(ScriptTag node) { |
| + writer.print(node.scriptTag.lexeme); |
| + } |
| - for (var member in node.members) { |
| - recorder..newline() |
| - ..advanceIndent(); |
| - member.accept(this); |
| - } |
| + visitShowCombinator(ShowCombinator node) { |
| + writer.print('show '); |
| + visitList(node.shownNames, ', '); |
| + } |
| - recorder..unindent() |
| - ..newline() |
| - ..advanceIndent() |
| - ..advance(node.rightBracket); |
| + visitSimpleFormalParameter(SimpleFormalParameter node) { |
| + visitToken(node.keyword, ' '); |
| + visitSuffixed(node.type, ' '); |
| + visit(node.identifier); |
| } |
| + visitSimpleIdentifier(SimpleIdentifier node) { |
| + writer.print(node.token.lexeme); |
| + } |
| - visitBlockFunctionBody(BlockFunctionBody node) { |
| - node.block.accept(this); |
| + visitSimpleStringLiteral(SimpleStringLiteral node) { |
| + writer.print(node.literal.lexeme); |
| } |
| + visitStringInterpolation(StringInterpolation node) { |
| + visitList(node.elements); |
| + } |
| - visitBlock(Block block) { |
| - recorder..advance(block.leftBracket) |
| - ..indent() |
| - ..newline(); |
| - // ... |
| - recorder..unindent() |
| - ..advanceIndent() |
| - ..advance(block.rightBracket); |
| + visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
| + writer.print('super'); |
| + visitPrefixed('.', node.constructorName); |
| + visit(node.argumentList); |
| } |
| + visitSuperExpression(SuperExpression node) { |
| + writer.print('super'); |
| + } |
| - visitExpressionFunctionBody(ExpressionFunctionBody node) { |
| - recorder..advance(node.functionDefinition) |
| - ..indent() |
| - ..newline(); |
| - node.expression.accept(this); |
| - recorder..unindent() |
| - ..advanceIndent() |
| - ..advance(node.semicolon); |
| + visitSwitchCase(SwitchCase node) { |
| + visitSuffixedList(node.labels, ' ', ' '); |
| + writer.print('case '); |
| + visit(node.expression); |
| + writer.print(': '); |
| + visitList(node.statements, ' '); |
| } |
| + visitSwitchDefault(SwitchDefault node) { |
| + visitSuffixedList(node.labels, ' ', ' '); |
| + writer.print('default: '); |
| + visitList(node.statements, ' '); |
| + } |
| - visitMethodDeclaration(MethodDeclaration node) { |
| + visitSwitchStatement(SwitchStatement node) { |
| + writer.print('switch ('); |
| + visit(node.expression); |
| + writer.print(') {'); |
| + visitList(node.members, ' '); |
| + writer.print('}'); |
| + } |
| - if (node.modifierKeyword != null) { |
| - recorder.advance(node.modifierKeyword); |
| - recorder.space(); |
| - } |
| + visitSymbolLiteral(SymbolLiteral node) { |
| + // No-op ? |
| + } |
| - if (node.returnType != null) { |
| - node.returnType.accept(this); |
| - recorder.space(); |
| - } |
| + visitThisExpression(ThisExpression node) { |
| + writer.print('this'); |
| + } |
| - recorder.advance(node.name.beginToken); |
| + visitThrowExpression(ThrowExpression node) { |
| + writer.print('throw '); |
| + visit(node.expression); |
| + } |
| - node.parameters.accept(this); |
| + visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
| + visitSuffixed(node.variables, ';'); |
| + } |
| - recorder.space(); |
| + visitTryStatement(TryStatement node) { |
| + writer.print('try '); |
| + visit(node.body); |
| + visitPrefixedList(' ', node.catchClauses, ' '); |
| + visitPrefixed(' finally ', node.finallyClause); |
| + } |
| - node.body.accept(this); |
| + visitTypeArgumentList(TypeArgumentList node) { |
| + writer.print('<'); |
| + visitList(node.arguments, ', '); |
| + writer.print('>'); |
| } |
| + visitTypeName(TypeName node) { |
| + visit(node.name); |
| + visit(node.typeArguments); |
| + } |
| - visitFormalParameterList(FormalParameterList node) { |
| - recorder.advance(node.beginToken); |
| - //... |
| - recorder.advance(node.endToken); |
| + visitTypeParameter(TypeParameter node) { |
| + visit(node.name); |
| + visitPrefixed(' extends ', node.bound); |
| } |
| + visitTypeParameterList(TypeParameterList node) { |
| + writer.print('<'); |
| + visitList(node.typeParameters, ', '); |
| + writer.print('>'); |
| + } |
| - visitSimpleIdentifier(SimpleIdentifier node) { |
| - recorder.advance(node.token); |
| + visitVariableDeclaration(VariableDeclaration node) { |
| + visit(node.name); |
| + visitPrefixed(' = ', node.initializer); |
| } |
| -} |
| + visitVariableDeclarationList(VariableDeclarationList node) { |
| + visitToken(node.keyword, ' '); |
| + visitSuffixed(node.type, ' '); |
| + visitList(node.variables, ', '); |
| + } |
| + |
| + visitVariableDeclarationStatement(VariableDeclarationStatement node) { |
| + visit(node.variables); |
| + writer.print(';'); |
| + } |
| + |
| + visitWhileStatement(WhileStatement node) { |
| + writer.print('while ('); |
| + visit(node.condition); |
| + writer.print(') '); |
| + visit(node.body); |
| + } |
| + |
| + visitWithClause(WithClause node) { |
| + writer.print('with '); |
| + visitList(node.mixinTypes, ', '); |
| + } |
| + |
| + /// Safely visit the given [node]. |
| + visit(ASTNode node) { |
| + if (node != null) { |
| + node.accept(this); |
| + } |
| + } |
| + |
| + /// Safely visit the given [node], printing the [suffix] after the node if it |
| + /// is non-null. |
| + visitSuffixed(ASTNode node, String suffix) { |
| + if (node != null) { |
| + node.accept(this); |
| + writer.print(suffix); |
| + } |
| + } |
| + |
| + /// Safely visit the given [node], printing the [prefix] before the node if |
| + /// it is non-null. |
| + visitPrefixed(String prefix, ASTNode node) { |
| + if (node != null) { |
| + writer.print(prefix); |
| + node.accept(this); |
| + } |
| + } |
| + |
| + /// Visit the given function [body], printing the [prefix] before if given |
| + /// body is not empty. |
| + visitPrefixedBody(String prefix, FunctionBody body) { |
| + if (body is! EmptyFunctionBody) { |
| + writer.print(prefix); |
| + } |
| + visit(body); |
| + } |
| + |
| + /// Safely visit the given [token], printing the suffix after the [token] |
| + /// node if it is non-null. |
| + visitToken(Token token, String suffix) { |
| + if (token != null) { |
| + writer.print(token.lexeme); |
| + writer.print(suffix); |
| + } |
| + } |
| + |
| + /// Print a list of [nodes], separated by the given [separator]. |
| + visitList(NodeList<ASTNode> nodes, [String separator = '']) { |
| + if (nodes != null) { |
| + var size = nodes.length; |
| + for (var i = 0; i < size; i++) { |
| + if (i > 0) { |
| + writer.print(separator); |
| + } |
| + nodes[i].accept(this); |
| + } |
| + } |
| + } |
| + |
| + /// Print a list of [nodes], separated by the given [separator]. |
| + visitSuffixedList(NodeList<ASTNode> nodes, String separator, String suffix) { |
| + if (nodes != null) { |
| + var size = nodes.length; |
| + if (size > 0) { |
| + for (var i = 0; i < size; i++) { |
| + if (i > 0) { |
| + writer.print(separator); |
| + } |
| + nodes[i].accept(this); |
| + } |
| + writer.print(suffix); |
| + } |
| + } |
| + } |
| + |
| + /// Print a list of [nodes], separated by the given [separator]. |
| + visitPrefixedList(String prefix, NodeList<ASTNode> nodes, String separator) { |
| + if (nodes != null) { |
| + var size = nodes.length; |
| + if (size > 0) { |
| + writer.print(prefix); |
| + for (var i = 0; i < size; i++) { |
| + if (i > 0) { |
| + writer.print(separator); |
| + } |
| + nodes[i].accept(this); |
| + } |
| + } |
| + } |
| + } |
| + |
| +} |