Index: pkg/analyzer_experimental/lib/src/services/formatter_impl.dart |
diff --git a/pkg/analyzer_experimental/lib/src/services/formatter_impl.dart b/pkg/analyzer_experimental/lib/src/services/formatter_impl.dart |
deleted file mode 100644 |
index cb568aca8b60887269ea53cbfd03d5aee8786934..0000000000000000000000000000000000000000 |
--- a/pkg/analyzer_experimental/lib/src/services/formatter_impl.dart |
+++ /dev/null |
@@ -1,1535 +0,0 @@ |
-// 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:math'; |
- |
-import 'package:analyzer_experimental/analyzer.dart'; |
-import 'package:analyzer_experimental/src/generated/java_core.dart' show CharSequence; |
-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'; |
- |
-/// 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, |
- this.spacesPerIndent: 2, |
- this.lineSeparator: NEW_LINE, |
- this.pageWidth: 80, |
- this.tabsForIndent: false, |
- this.tabSize: 2, |
- this.codeTransforms: false}); |
- |
- final String lineSeparator; |
- final int initialIndentationLevel; |
- final int spacesPerIndent; |
- final int tabSize; |
- final bool tabsForIndent; |
- final int pageWidth; |
- final bool codeTransforms; |
-} |
- |
- |
-/// 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']); |
- |
- FormatterException.forError(List<AnalysisError> errors, [LineInfo line]) : |
- message = _createMessage(errors); |
- |
- static String _createMessage(errors) { |
- //TODO(pquitslund): consider a verbosity flag to add/suppress details |
- var errorCode = errors[0].errorCode; |
- var phase = errorCode is ParserErrorCode ? 'parsing' : 'scanning'; |
- return 'An error occured while ${phase} (${errorCode.name}).'; |
- } |
- |
- String toString() => '$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 { |
- |
- 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]. |
- FormattedSource format(CodeKind kind, String source, {int offset, int end, |
- int indentationLevel: 0, Selection selection: null}); |
- |
-} |
- |
-/// Source selection state information. |
-class Selection { |
- |
- /// The offset of the source selection. |
- final int offset; |
- |
- /// The length of the selection. |
- final int length; |
- |
- Selection(this.offset, this.length); |
- |
- String toString() => 'Selection (offset: $offset, length: $length)'; |
-} |
- |
-/// Formatted source. |
-class FormattedSource { |
- |
- /// Selection state or null if unspecified. |
- Selection selection; |
- |
- /// Formatted source string. |
- final String source; |
- |
- /// Create a formatted [source] result, with optional [selection] information. |
- FormattedSource(this.source, [this.selection = null]); |
-} |
- |
- |
-class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener { |
- |
- final FormatterOptions options; |
- final errors = <AnalysisError>[]; |
- final whitespace = new RegExp(r'[\s]+'); |
- |
- LineInfo lineInfo; |
- |
- CodeFormatterImpl(this.options); |
- |
- FormattedSource format(CodeKind kind, String source, {int offset, int end, |
- int indentationLevel: 0, Selection selection: null}) { |
- |
- var startToken = tokenize(source); |
- checkForErrors(); |
- |
- var node = parse(kind, startToken); |
- checkForErrors(); |
- |
- var formatter = new SourceVisitor(options, lineInfo, selection); |
- node.accept(formatter); |
- |
- var formattedSource = formatter.writer.toString(); |
- |
- checkTokenStreams(startToken, tokenize(formattedSource)); |
- |
- return new FormattedSource(formattedSource, formatter.selection); |
- } |
- |
- checkTokenStreams(Token t1, Token t2) => |
- new TokenStreamComparator(lineInfo, t1, t2).verifyEquals(); |
- |
- ASTNode parse(CodeKind kind, Token start) { |
- |
- var parser = new Parser(null, this); |
- |
- switch (kind) { |
- case CodeKind.COMPILATION_UNIT: |
- return parser.parseCompilationUnit(start); |
- case CodeKind.STATEMENT: |
- return parser.parseStatement(start); |
- } |
- |
- throw new FormatterException('Unsupported format kind: $kind'); |
- } |
- |
- checkForErrors() { |
- if (errors.length > 0) { |
- throw new FormatterException.forError(errors); |
- } |
- } |
- |
- onError(AnalysisError error) { |
- errors.add(error); |
- } |
- |
- Token tokenize(String source) { |
- var reader = new CharSequenceReader(new CharSequence(source)); |
- var scanner = new Scanner(null, reader, this); |
- var token = scanner.tokenize(); |
- lineInfo = new LineInfo(scanner.lineStarts); |
- return token; |
- } |
- |
-} |
- |
- |
-// Compares two token streams. Used for sanity checking formatted results. |
-class TokenStreamComparator { |
- |
- final LineInfo lineInfo; |
- Token token1, token2; |
- |
- TokenStreamComparator(this.lineInfo, this.token1, this.token2); |
- |
- /// Verify that these two token streams are equal. |
- verifyEquals() { |
- while (!isEOF(token1)) { |
- checkPrecedingComments(); |
- if (!checkTokens()) { |
- throwNotEqualException(token1, token2); |
- } |
- advance(); |
- |
- } |
- // TODO(pquitslund): consider a better way to notice trailing synthetics |
- if (!isEOF(token2) && |
- !(isCLOSE_CURLY_BRACKET(token2) && isEOF(token2.next))) { |
- throw new FormatterException( |
- 'Expected "EOF" but got "${token2}".'); |
- } |
- } |
- |
- checkPrecedingComments() { |
- var comment1 = token1.precedingComments; |
- var comment2 = token2.precedingComments; |
- while (comment1 != null) { |
- if (comment2 == null) { |
- throw new FormatterException( |
- 'Expected comment, "${comment1}", at ${describeLocation(token1)}, ' |
- 'but got none.'); |
- } |
- if (!equivalentComments(comment1, comment2)) { |
- throwNotEqualException(comment1, comment2); |
- } |
- comment1 = comment1.next; |
- comment2 = comment2.next; |
- } |
- if (comment2 != null) { |
- throw new FormatterException( |
- 'Unexpected comment, "${comment2}", at ${describeLocation(token2)}.'); |
- } |
- } |
- |
- bool equivalentComments(Token comment1, Token comment2) => |
- comment1.lexeme.trim() == comment2.lexeme.trim(); |
- |
- throwNotEqualException(t1, t2) { |
- throw new FormatterException( |
- 'Expected "${t1}" but got "${t2}", at ${describeLocation(t1)}.'); |
- } |
- |
- String describeLocation(Token token) => lineInfo == null ? '<unknown>' : |
- 'Line: ${lineInfo.getLocation(token.offset).lineNumber}, ' |
- 'Column: ${lineInfo.getLocation(token.offset).columnNumber}'; |
- |
- advance() { |
- token1 = token1.next; |
- token2 = token2.next; |
- } |
- |
- bool checkTokens() { |
- if (token1 == null || token2 == null) { |
- return false; |
- } |
- if (token1 == token2 || token1.lexeme == token2.lexeme) { |
- return true; |
- } |
- |
- // '[' ']' => '[]' |
- if (isOPEN_SQ_BRACKET(token1) && isCLOSE_SQUARE_BRACKET(token1.next)) { |
- if (isINDEX(token2)) { |
- token1 = token1.next; |
- return true; |
- } |
- } |
- // '>' '>' => '>>' |
- if (isGT(token1) && isGT(token1.next)) { |
- if (isGT_GT(token2)) { |
- token1 = token1.next; |
- return true; |
- } |
- } |
- // Cons(){} => Cons(); |
- if (isOPEN_CURLY_BRACKET(token1) && isCLOSE_CURLY_BRACKET(token1.next)) { |
- if (isSEMICOLON(token2)) { |
- token1 = token1.next; |
- advance(); |
- return true; |
- } |
- } |
- // Advance past synthetic { } tokens |
- if (isOPEN_CURLY_BRACKET(token2) || isCLOSE_CURLY_BRACKET(token2)) { |
- token2 = token2.next; |
- return checkTokens(); |
- } |
- |
- return false; |
- } |
- |
-} |
- |
-// Cached parser for testing token types. |
-final tokenTester = new Parser(null,null); |
- |
-/// Test if this token is an EOF token. |
-bool isEOF(Token token) => tokenIs(token, TokenType.EOF); |
- |
-/// Test for token type. |
-bool tokenIs(Token token, TokenType type) => |
- token != null && tokenTester.matches4(token, type); |
- |
-/// Test if this token is a GT token. |
-bool isGT(Token token) => tokenIs(token, TokenType.GT); |
- |
-/// Test if this token is a GT_GT token. |
-bool isGT_GT(Token token) => tokenIs(token, TokenType.GT_GT); |
- |
-/// Test if this token is an INDEX token. |
-bool isINDEX(Token token) => tokenIs(token, TokenType.INDEX); |
- |
-/// Test if this token is a OPEN_CURLY_BRACKET token. |
-bool isOPEN_CURLY_BRACKET(Token token) => |
- tokenIs(token, TokenType.OPEN_CURLY_BRACKET); |
- |
-/// Test if this token is a CLOSE_CURLY_BRACKET token. |
-bool isCLOSE_CURLY_BRACKET(Token token) => |
- tokenIs(token, TokenType.CLOSE_CURLY_BRACKET); |
- |
-/// Test if this token is a OPEN_SQUARE_BRACKET token. |
-bool isOPEN_SQ_BRACKET(Token token) => |
- tokenIs(token, TokenType.OPEN_SQUARE_BRACKET); |
- |
-/// Test if this token is a CLOSE_SQUARE_BRACKET token. |
-bool isCLOSE_SQUARE_BRACKET(Token token) => |
- tokenIs(token, TokenType.CLOSE_SQUARE_BRACKET); |
- |
-/// Test if this token is a SEMICOLON token. |
-bool isSEMICOLON(Token token) => |
- tokenIs(token, TokenType.SEMICOLON); |
- |
- |
-/// An AST visitor that drives formatting heuristics. |
-class SourceVisitor implements ASTVisitor { |
- |
- static final OPEN_CURLY = syntheticToken(TokenType.OPEN_CURLY_BRACKET, '{'); |
- static final CLOSE_CURLY = syntheticToken(TokenType.CLOSE_CURLY_BRACKET, '}'); |
- static final SEMI_COLON = syntheticToken(TokenType.SEMICOLON, ';'); |
- |
- static const SYNTH_OFFSET = -13; |
- |
- static StringToken syntheticToken(TokenType type, String value) => |
- new StringToken(type, value, SYNTH_OFFSET); |
- |
- static bool isSynthetic(Token token) => token.offset == SYNTH_OFFSET; |
- |
- /// The writer to which the source is to be written. |
- final SourceWriter writer; |
- |
- /// Cached line info for calculating blank lines. |
- LineInfo lineInfo; |
- |
- /// Cached previous token for calculating preceding whitespace. |
- Token previousToken; |
- |
- /// A flag to indicate that a newline should be emitted before the next token. |
- bool needsNewline = false; |
- |
- /// A counter for spaces that should be emitted preceding the next token. |
- int leadingSpaces = 0; |
- |
- /// Used for matching EOL comments |
- final twoSlashes = new RegExp(r'//[^/]'); |
- |
- /// Original pre-format selection information (may be null). |
- final Selection preSelection; |
- |
- final bool codeTransforms; |
- |
- /// Post format selection information. |
- Selection selection; |
- |
- |
- /// Initialize a newly created visitor to write source code representing |
- /// the visited nodes to the given [writer]. |
- SourceVisitor(FormatterOptions options, this.lineInfo, this.preSelection): |
- writer = new SourceWriter(indentCount: options.initialIndentationLevel, |
- lineSeparator: options.lineSeparator), |
- codeTransforms = options.codeTransforms; |
- |
- visitAdjacentStrings(AdjacentStrings node) { |
- visitNodes(node.strings, separatedBy: space); |
- } |
- |
- visitAnnotation(Annotation node) { |
- token(node.atSign); |
- visit(node.name); |
- token(node.period); |
- visit(node.constructorName); |
- visit(node.arguments); |
- } |
- |
- visitArgumentDefinitionTest(ArgumentDefinitionTest node) { |
- token(node.question); |
- visit(node.identifier); |
- } |
- |
- visitArgumentList(ArgumentList node) { |
- token(node.leftParenthesis); |
- visitNodes(node.arguments, separatedBy: commaSeperator); |
- token(node.rightParenthesis); |
- } |
- |
- visitAsExpression(AsExpression node) { |
- visit(node.expression); |
- space(); |
- token(node.asOperator); |
- space(); |
- visit(node.type); |
- } |
- |
- visitAssertStatement(AssertStatement node) { |
- token(node.keyword); |
- space(); |
- token(node.leftParenthesis); |
- visit(node.condition); |
- token(node.rightParenthesis); |
- token(node.semicolon); |
- } |
- |
- visitAssignmentExpression(AssignmentExpression node) { |
- visit(node.leftHandSide); |
- space(); |
- token(node.operator); |
- space(); |
- visit(node.rightHandSide); |
- } |
- |
- visitBinaryExpression(BinaryExpression node) { |
- visit(node.leftOperand); |
- space(); |
- token(node.operator); |
- space(); |
- visit(node.rightOperand); |
- } |
- |
- visitBlock(Block node) { |
- token(node.leftBracket); |
- indent(); |
- visitNodes(node.statements, precededBy: newlines, separatedBy: newlines); |
- unindent(); |
- newlines(); |
- token(node.rightBracket); |
- } |
- |
- visitBlockFunctionBody(BlockFunctionBody node) { |
- visit(node.block); |
- } |
- |
- visitBooleanLiteral(BooleanLiteral node) { |
- token(node.literal); |
- } |
- |
- visitBreakStatement(BreakStatement node) { |
- token(node.keyword); |
- visitNode(node.label, precededBy: space); |
- token(node.semicolon); |
- } |
- |
- visitCascadeExpression(CascadeExpression node) { |
- visit(node.target); |
- indent(2); |
- visitNodes(node.cascadeSections); |
- unindent(2); |
- } |
- |
- visitCatchClause(CatchClause node) { |
- |
- token(node.onKeyword, precededBy: space, followedBy: space); |
- visit(node.exceptionType); |
- |
- if (node.catchKeyword != null) { |
- if (node.exceptionType != null) { |
- space(); |
- } |
- token(node.catchKeyword); |
- space(); |
- token(node.leftParenthesis); |
- visit(node.exceptionParameter); |
- token(node.comma, followedBy: space); |
- visit(node.stackTraceParameter); |
- token(node.rightParenthesis); |
- space(); |
- } else { |
- space(); |
- } |
- visit(node.body); |
- } |
- |
- visitClassDeclaration(ClassDeclaration node) { |
- modifier(node.abstractKeyword); |
- token(node.classKeyword); |
- space(); |
- visit(node.name); |
- visit(node.typeParameters); |
- visitNode(node.extendsClause, precededBy: space); |
- visitNode(node.withClause, precededBy: space); |
- visitNode(node.implementsClause, precededBy: space); |
- space(); |
- token(node.leftBracket); |
- indent(); |
- visitNodes(node.members, precededBy: newlines, separatedBy: newlines); |
- unindent(); |
- newlines(); |
- token(node.rightBracket); |
- } |
- |
- visitClassTypeAlias(ClassTypeAlias node) { |
- token(node.keyword); |
- space(); |
- visit(node.name); |
- visit(node.typeParameters); |
- space(); |
- token(node.equals); |
- space(); |
- if (node.abstractKeyword != null) { |
- token(node.abstractKeyword); |
- space(); |
- } |
- visit(node.superclass); |
- visitNode(node.withClause, precededBy: space); |
- visitNode(node.implementsClause, precededBy: space); |
- token(node.semicolon); |
- } |
- |
- visitComment(Comment node) => null; |
- |
- visitCommentReference(CommentReference node) => null; |
- |
- visitCompilationUnit(CompilationUnit node) { |
- |
- // Cache EOF for leading whitespace calculation |
- var start = node.beginToken.previous; |
- if (start != null && start.type is TokenType_EOF) { |
- previousToken = start; |
- } |
- |
- var scriptTag = node.scriptTag; |
- var directives = node.directives; |
- visit(scriptTag); |
- |
- visitNodes(directives, separatedBy: newlines, followedBy: newlines); |
- |
- visitNodes(node.declarations, separatedBy: newlines); |
- |
- // Handle trailing whitespace |
- token(node.endToken /* EOF */); |
- |
- // Be a good citizen, end with a NL |
- ensureTrailingNewline(); |
- } |
- |
- visitConditionalExpression(ConditionalExpression node) { |
- visit(node.condition); |
- space(); |
- token(node.question); |
- space(); |
- visit(node.thenExpression); |
- space(); |
- token(node.colon); |
- space(); |
- visit(node.elseExpression); |
- } |
- |
- visitConstructorDeclaration(ConstructorDeclaration node) { |
- modifier(node.externalKeyword); |
- modifier(node.constKeyword); |
- modifier(node.factoryKeyword); |
- visit(node.returnType); |
- token(node.period); |
- visit(node.name); |
- visit(node.parameters); |
- |
- // Check for redirects or initializer lists |
- if (node.separator != null) { |
- if (node.redirectedConstructor != null) { |
- visitConstructorRedirects(node); |
- } else { |
- visitConstructorInitializers(node); |
- } |
- } |
- |
- var body = node.body; |
- if (codeTransforms && body is BlockFunctionBody) { |
- if (body.block.statements.isEmpty) { |
- token(SEMI_COLON); |
- newlines(); |
- return; |
- } |
- } |
- |
- visitPrefixedBody(space, body); |
- } |
- |
- visitConstructorInitializers(ConstructorDeclaration node) { |
- newlines(); |
- indent(2); |
- token(node.separator /* : */); |
- space(); |
- for (var i = 0; i < node.initializers.length; i++) { |
- if (i > 0) { |
- comma(); |
- newlines(); |
- space(2); |
- } |
- node.initializers[i].accept(this); |
- } |
- unindent(2); |
- } |
- |
- visitConstructorRedirects(ConstructorDeclaration node) { |
- token(node.separator /* = */, precededBy: space, followedBy: space); |
- visitNodes(node.initializers, separatedBy: commaSeperator); |
- visit(node.redirectedConstructor); |
- } |
- |
- visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
- token(node.keyword); |
- token(node.period); |
- visit(node.fieldName); |
- space(); |
- token(node.equals); |
- space(); |
- visit(node.expression); |
- } |
- |
- visitConstructorName(ConstructorName node) { |
- visit(node.type); |
- token(node.period); |
- visit(node.name); |
- } |
- |
- visitContinueStatement(ContinueStatement node) { |
- token(node.keyword); |
- visitNode(node.label, precededBy: space); |
- token(node.semicolon); |
- } |
- |
- visitDeclaredIdentifier(DeclaredIdentifier node) { |
- modifier(node.keyword); |
- visitNode(node.type, followedBy: space); |
- visit(node.identifier); |
- } |
- |
- visitDefaultFormalParameter(DefaultFormalParameter node) { |
- visit(node.parameter); |
- if (node.separator != null) { |
- // The '=' separator is preceded by a space |
- if (node.separator.type == TokenType.EQ) { |
- space(); |
- } |
- token(node.separator); |
- visitNode(node.defaultValue, precededBy: space); |
- } |
- } |
- |
- visitDoStatement(DoStatement node) { |
- token(node.doKeyword); |
- space(); |
- visit(node.body); |
- space(); |
- token(node.whileKeyword); |
- space(); |
- token(node.leftParenthesis); |
- visit(node.condition); |
- token(node.rightParenthesis); |
- token(node.semicolon); |
- } |
- |
- visitDoubleLiteral(DoubleLiteral node) { |
- token(node.literal); |
- } |
- |
- visitEmptyFunctionBody(EmptyFunctionBody node) { |
- token(node.semicolon); |
- } |
- |
- visitEmptyStatement(EmptyStatement node) { |
- token(node.semicolon); |
- } |
- |
- visitExportDirective(ExportDirective node) { |
- token(node.keyword); |
- space(); |
- visit(node.uri); |
- visitNodes(node.combinators, precededBy: space, separatedBy: space); |
- token(node.semicolon); |
- } |
- |
- visitExpressionFunctionBody(ExpressionFunctionBody node) { |
- token(node.functionDefinition); |
- space(); |
- visit(node.expression); |
- token(node.semicolon); |
- } |
- |
- visitExpressionStatement(ExpressionStatement node) { |
- visit(node.expression); |
- token(node.semicolon); |
- } |
- |
- visitExtendsClause(ExtendsClause node) { |
- token(node.keyword); |
- space(); |
- visit(node.superclass); |
- } |
- |
- visitFieldDeclaration(FieldDeclaration node) { |
- modifier(node.staticKeyword); |
- visit(node.fields); |
- token(node.semicolon); |
- } |
- |
- visitFieldFormalParameter(FieldFormalParameter node) { |
- token(node.keyword, followedBy: space); |
- visitNode(node.type, followedBy: space); |
- token(node.thisToken); |
- token(node.period); |
- visit(node.identifier); |
- visit(node.parameters); |
- } |
- |
- visitForEachStatement(ForEachStatement node) { |
- token(node.forKeyword); |
- space(); |
- token(node.leftParenthesis); |
- visit(node.loopVariable); |
- space(); |
- token(node.inKeyword); |
- space(); |
- visit(node.iterator); |
- token(node.rightParenthesis); |
- space(); |
- visit(node.body); |
- } |
- |
- visitFormalParameterList(FormalParameterList node) { |
- var groupEnd = null; |
- token(node.leftParenthesis); |
- var parameters = node.parameters; |
- var size = parameters.length; |
- for (var i = 0; i < size; i++) { |
- var parameter = parameters[i]; |
- if (i > 0) { |
- append(', '); |
- } |
- if (groupEnd == null && parameter is DefaultFormalParameter) { |
- if (identical(parameter.kind, ParameterKind.NAMED)) { |
- groupEnd = '}'; |
- append('{'); |
- } else { |
- groupEnd = ']'; |
- append('['); |
- } |
- } |
- parameter.accept(this); |
- } |
- if (groupEnd != null) { |
- append(groupEnd); |
- } |
- token(node.rightParenthesis); |
- } |
- |
- visitForStatement(ForStatement node) { |
- token(node.forKeyword); |
- space(); |
- token(node.leftParenthesis); |
- if (node.initialization != null) { |
- visit(node.initialization); |
- } else { |
- visit(node.variables); |
- } |
- token(node.leftSeparator); |
- space(); |
- visit(node.condition); |
- token(node.rightSeparator); |
- visitNodes(node.updaters, precededBy: space, separatedBy: space); |
- token(node.rightParenthesis); |
- space(); |
- visit(node.body); |
- } |
- |
- visitFunctionDeclaration(FunctionDeclaration node) { |
- visitNode(node.returnType, followedBy: space); |
- token(node.propertyKeyword, followedBy: space); |
- visit(node.name); |
- visit(node.functionExpression); |
- } |
- |
- visitFunctionDeclarationStatement(FunctionDeclarationStatement node) { |
- visit(node.functionDeclaration); |
- } |
- |
- visitFunctionExpression(FunctionExpression node) { |
- visit(node.parameters); |
- space(); |
- visit(node.body); |
- } |
- |
- visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
- visit(node.function); |
- visit(node.argumentList); |
- } |
- |
- visitFunctionTypeAlias(FunctionTypeAlias node) { |
- token(node.keyword); |
- space(); |
- visitNode(node.returnType, followedBy: space); |
- visit(node.name); |
- visit(node.typeParameters); |
- visit(node.parameters); |
- token(node.semicolon); |
- } |
- |
- visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { |
- visitNode(node.returnType, followedBy: space); |
- visit(node.identifier); |
- visit(node.parameters); |
- } |
- |
- visitHideCombinator(HideCombinator node) { |
- token(node.keyword); |
- space(); |
- visitNodes(node.hiddenNames, separatedBy: commaSeperator); |
- } |
- |
- visitIfStatement(IfStatement node) { |
- var hasElse = node.elseStatement != null; |
- token(node.ifKeyword); |
- space(); |
- token(node.leftParenthesis); |
- visit(node.condition); |
- token(node.rightParenthesis); |
- space(); |
- if (hasElse) { |
- printAsBlock(node.thenStatement); |
- space(); |
- token(node.elseKeyword); |
- space(); |
- printAsBlock(node.elseStatement); |
- } else { |
- visit(node.thenStatement); |
- } |
- } |
- |
- visitImplementsClause(ImplementsClause node) { |
- token(node.keyword); |
- space(); |
- visitNodes(node.interfaces, separatedBy: commaSeperator); |
- } |
- |
- visitImportDirective(ImportDirective node) { |
- token(node.keyword); |
- space(); |
- visit(node.uri); |
- token(node.asToken, precededBy: space, followedBy: space); |
- visit(node.prefix); |
- visitNodes(node.combinators, precededBy: space, separatedBy: space); |
- token(node.semicolon); |
- } |
- |
- visitIndexExpression(IndexExpression node) { |
- if (node.isCascaded) { |
- token(node.period); |
- } else { |
- visit(node.target); |
- } |
- token(node.leftBracket); |
- visit(node.index); |
- token(node.rightBracket); |
- } |
- |
- visitInstanceCreationExpression(InstanceCreationExpression node) { |
- token(node.keyword); |
- space(); |
- visit(node.constructorName); |
- visit(node.argumentList); |
- } |
- |
- visitIntegerLiteral(IntegerLiteral node) { |
- token(node.literal); |
- } |
- |
- visitInterpolationExpression(InterpolationExpression node) { |
- if (node.rightBracket != null) { |
- token(node.leftBracket); |
- visit(node.expression); |
- token(node.rightBracket); |
- } else { |
- token(node.leftBracket); |
- visit(node.expression); |
- } |
- } |
- |
- visitInterpolationString(InterpolationString node) { |
- token(node.contents); |
- } |
- |
- visitIsExpression(IsExpression node) { |
- visit(node.expression); |
- space(); |
- token(node.isOperator); |
- token(node.notOperator); |
- space(); |
- visit(node.type); |
- } |
- |
- visitLabel(Label node) { |
- visit(node.label); |
- token(node.colon); |
- } |
- |
- visitLabeledStatement(LabeledStatement node) { |
- visitNodes(node.labels, separatedBy: space, followedBy: space); |
- visit(node.statement); |
- } |
- |
- visitLibraryDirective(LibraryDirective node) { |
- token(node.keyword); |
- space(); |
- visit(node.name); |
- token(node.semicolon); |
- } |
- |
- visitLibraryIdentifier(LibraryIdentifier node) { |
- append(node.name); |
- } |
- |
- visitListLiteral(ListLiteral node) { |
- modifier(node.constKeyword); |
- visit(node.typeArguments); |
- token(node.leftBracket); |
- visitNodes(node.elements, separatedBy: commaSeperator); |
- optionalTrailingComma(node.rightBracket); |
- token(node.rightBracket); |
- } |
- |
- visitMapLiteral(MapLiteral node) { |
- modifier(node.constKeyword); |
- visitNode(node.typeArguments, followedBy: space); |
- token(node.leftBracket); |
- visitNodes(node.entries, separatedBy: commaSeperator); |
- optionalTrailingComma(node.rightBracket); |
- token(node.rightBracket); |
- } |
- |
- visitMapLiteralEntry(MapLiteralEntry node) { |
- visit(node.key); |
- token(node.separator); |
- space(); |
- visit(node.value); |
- } |
- |
- visitMethodDeclaration(MethodDeclaration node) { |
- modifier(node.externalKeyword); |
- modifier(node.modifierKeyword); |
- visitNode(node.returnType, followedBy: space); |
- modifier(node.propertyKeyword); |
- modifier(node.operatorKeyword); |
- visit(node.name); |
- if (!node.isGetter) { |
- visit(node.parameters); |
- } |
- visitPrefixedBody(space, node.body); |
- } |
- |
- visitMethodInvocation(MethodInvocation node) { |
- visit(node.target); |
- token(node.period); |
- visit(node.methodName); |
- visit(node.argumentList); |
- } |
- |
- visitNamedExpression(NamedExpression node) { |
- visit(node.name); |
- visitNode(node.expression, precededBy: space); |
- } |
- |
- visitNativeClause(NativeClause node) { |
- token(node.keyword); |
- space(); |
- visit(node.name); |
- } |
- |
- visitNativeFunctionBody(NativeFunctionBody node) { |
- token(node.nativeToken); |
- space(); |
- visit(node.stringLiteral); |
- token(node.semicolon); |
- } |
- |
- visitNullLiteral(NullLiteral node) { |
- token(node.literal); |
- } |
- |
- visitParenthesizedExpression(ParenthesizedExpression node) { |
- token(node.leftParenthesis); |
- visit(node.expression); |
- token(node.rightParenthesis); |
- } |
- |
- visitPartDirective(PartDirective node) { |
- token(node.keyword); |
- space(); |
- visit(node.uri); |
- token(node.semicolon); |
- } |
- |
- visitPartOfDirective(PartOfDirective node) { |
- token(node.keyword); |
- space(); |
- token(node.ofToken); |
- space(); |
- visit(node.libraryName); |
- token(node.semicolon); |
- } |
- |
- visitPostfixExpression(PostfixExpression node) { |
- visit(node.operand); |
- token(node.operator); |
- } |
- |
- visitPrefixedIdentifier(PrefixedIdentifier node) { |
- visit(node.prefix); |
- token(node.period); |
- visit(node.identifier); |
- } |
- |
- visitPrefixExpression(PrefixExpression node) { |
- token(node.operator); |
- visit(node.operand); |
- } |
- |
- visitPropertyAccess(PropertyAccess node) { |
- if (node.isCascaded) { |
- token(node.operator); |
- } else { |
- visit(node.target); |
- token(node.operator); |
- } |
- visit(node.propertyName); |
- } |
- |
- visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) { |
- token(node.keyword); |
- token(node.period); |
- visit(node.constructorName); |
- visit(node.argumentList); |
- } |
- |
- visitRethrowExpression(RethrowExpression node) { |
- token(node.keyword); |
- } |
- |
- visitReturnStatement(ReturnStatement node) { |
- var expression = node.expression; |
- if (expression == null) { |
- token(node.keyword); |
- token(node.semicolon); |
- } else { |
- token(node.keyword); |
- space(); |
- expression.accept(this); |
- token(node.semicolon); |
- } |
- } |
- |
- visitScriptTag(ScriptTag node) { |
- token(node.scriptTag); |
- } |
- |
- visitShowCombinator(ShowCombinator node) { |
- token(node.keyword); |
- space(); |
- visitNodes(node.shownNames, separatedBy: commaSeperator); |
- } |
- |
- visitSimpleFormalParameter(SimpleFormalParameter node) { |
- modifier(node.keyword); |
- visitNode(node.type, followedBy: space); |
- visit(node.identifier); |
- } |
- |
- visitSimpleIdentifier(SimpleIdentifier node) { |
- token(node.token); |
- } |
- |
- visitSimpleStringLiteral(SimpleStringLiteral node) { |
- token(node.literal); |
- } |
- |
- visitStringInterpolation(StringInterpolation node) { |
- visitNodes(node.elements); |
- } |
- |
- visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
- token(node.keyword); |
- token(node.period); |
- visit(node.constructorName); |
- visit(node.argumentList); |
- } |
- |
- visitSuperExpression(SuperExpression node) { |
- token(node.keyword); |
- } |
- |
- visitSwitchCase(SwitchCase node) { |
- visitNodes(node.labels, separatedBy: space, followedBy: space); |
- token(node.keyword); |
- space(); |
- visit(node.expression); |
- token(node.colon); |
- newlines(); |
- indent(); |
- visitNodes(node.statements, separatedBy: newlines); |
- unindent(); |
- } |
- |
- visitSwitchDefault(SwitchDefault node) { |
- visitNodes(node.labels, separatedBy: space, followedBy: space); |
- token(node.keyword); |
- token(node.colon); |
- space(); |
- visitNodes(node.statements, separatedBy: space); |
- } |
- |
- visitSwitchStatement(SwitchStatement node) { |
- token(node.keyword); |
- space(); |
- token(node.leftParenthesis); |
- visit(node.expression); |
- token(node.rightParenthesis); |
- space(); |
- token(node.leftBracket); |
- indent(); |
- newlines(); |
- visitNodes(node.members, separatedBy: newlines, followedBy: newlines); |
- unindent(); |
- token(node.rightBracket); |
- } |
- |
- visitSymbolLiteral(SymbolLiteral node) { |
- // No-op ? |
- } |
- |
- visitThisExpression(ThisExpression node) { |
- token(node.keyword); |
- } |
- |
- visitThrowExpression(ThrowExpression node) { |
- token(node.keyword); |
- space(); |
- visit(node.expression); |
- } |
- |
- visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
- visit(node.variables); |
- token(node.semicolon); |
- } |
- |
- visitTryStatement(TryStatement node) { |
- token(node.tryKeyword); |
- space(); |
- visit(node.body); |
- visitNodes(node.catchClauses, precededBy: space, separatedBy: space); |
- token(node.finallyKeyword, precededBy: space, followedBy: space); |
- visit(node.finallyBlock); |
- } |
- |
- visitTypeArgumentList(TypeArgumentList node) { |
- token(node.leftBracket); |
- visitNodes(node.arguments, separatedBy: commaSeperator); |
- token(node.rightBracket); |
- } |
- |
- visitTypeName(TypeName node) { |
- visit(node.name); |
- visit(node.typeArguments); |
- } |
- |
- visitTypeParameter(TypeParameter node) { |
- visit(node.name); |
- token(node.keyword /* extends */, precededBy: space, followedBy: space); |
- visit(node.bound); |
- } |
- |
- visitTypeParameterList(TypeParameterList node) { |
- token(node.leftBracket); |
- visitNodes(node.typeParameters, separatedBy: commaSeperator); |
- token(node.rightBracket); |
- } |
- |
- visitVariableDeclaration(VariableDeclaration node) { |
- visit(node.name); |
- if (node.initializer != null) { |
- space(); |
- token(node.equals); |
- space(); |
- visit(node.initializer); |
- } |
- } |
- |
- visitVariableDeclarationList(VariableDeclarationList node) { |
- modifier(node.keyword); |
- visitNode(node.type, followedBy: space); |
- visitNodes(node.variables, separatedBy: commaSeperator); |
- } |
- |
- visitVariableDeclarationStatement(VariableDeclarationStatement node) { |
- visit(node.variables); |
- token(node.semicolon); |
- } |
- |
- visitWhileStatement(WhileStatement node) { |
- token(node.keyword); |
- space(); |
- token(node.leftParenthesis); |
- visit(node.condition); |
- token(node.rightParenthesis); |
- space(); |
- visit(node.body); |
- } |
- |
- visitWithClause(WithClause node) { |
- token(node.withKeyword); |
- space(); |
- visitNodes(node.mixinTypes, separatedBy: commaSeperator); |
- } |
- |
- /// Safely visit the given [node]. |
- visit(ASTNode node) { |
- if (node != null) { |
- node.accept(this); |
- } |
- } |
- |
- /// Visit the given function [body], printing the [prefix] before if given |
- /// body is not empty. |
- visitPrefixedBody(prefix(), FunctionBody body) { |
- if (body is! EmptyFunctionBody) { |
- prefix(); |
- } |
- visit(body); |
- } |
- |
- /// Visit a list of [nodes] if not null, optionally separated and/or preceded |
- /// and followed by the given functions. |
- visitNodes(NodeList<ASTNode> nodes, {precededBy(): null, |
- separatedBy() : null, followedBy(): null}) { |
- if (nodes != null) { |
- var size = nodes.length; |
- if (size > 0) { |
- if (precededBy != null) { |
- precededBy(); |
- } |
- for (var i = 0; i < size; i++) { |
- if (i > 0 && separatedBy != null) { |
- separatedBy(); |
- } |
- nodes[i].accept(this); |
- } |
- if (followedBy != null) { |
- followedBy(); |
- } |
- } |
- } |
- } |
- |
- /// Visit a [node], and if not null, optionally preceded or followed by the |
- /// specified functions. |
- visitNode(ASTNode node, {precededBy(): null, followedBy(): null}) { |
- if (node != null) { |
- if (precededBy != null) { |
- precededBy(); |
- } |
- node.accept(this); |
- if (followedBy != null) { |
- followedBy(); |
- } |
- } |
- } |
- |
- |
- /// Emit the given [modifier] if it's non null, followed by non-breaking |
- /// whitespace. |
- modifier(Token modifier) { |
- token(modifier, followedBy: space); |
- } |
- |
- /// Indicate that at least one newline should be emitted and possibly more |
- /// if the source has them. |
- newlines() { |
- needsNewline = true; |
- } |
- |
- /// Optionally emit a trailing comma. |
- optionalTrailingComma(Token rightBracket) { |
- if (rightBracket.previous.lexeme == ',') { |
- comma(); |
- } |
- } |
- |
- token(Token token, {precededBy(), followedBy(), int minNewlines: 0}) { |
- if (token != null) { |
- if (needsNewline) { |
- minNewlines = max(1, minNewlines); |
- } |
- var emitted = emitPrecedingCommentsAndNewlines(token, min: minNewlines); |
- if (emitted > 0) { |
- needsNewline = false; |
- } |
- if (precededBy != null) { |
- precededBy(); |
- } |
- checkForSelectionUpdate(token); |
- append(token.lexeme); |
- if (followedBy != null) { |
- followedBy(); |
- } |
- previousToken = token; |
- } |
- } |
- |
- emitSpaces() { |
- while (leadingSpaces > 0) { |
- writer.print(' '); |
- leadingSpaces--; |
- } |
- } |
- |
- checkForSelectionUpdate(Token token) { |
- // Cache the first token on or AFTER the selection offset |
- if (preSelection != null && selection == null) { |
- // Check for overshots |
- var overshot = token.offset - preSelection.offset; |
- if (overshot >= 0) { |
- //TODO(pquitslund): update length (may need truncating) |
- selection = new Selection( |
- writer.toString().length + leadingSpaces - overshot, |
- preSelection.length); |
- } |
- } |
- } |
- |
- commaSeperator() { |
- comma(); |
- space(); |
- } |
- |
- comma() { |
- writer.print(','); |
- } |
- |
- |
- /// Emit a non-breakable space. |
- space([n = 1]) { |
- //TODO(pquitslund): replace with a proper space token |
- leadingSpaces+=n; |
- } |
- |
- /// Emit a breakable space |
- breakableSpace() { |
- //Implement |
- } |
- |
- /// Append the given [string] to the source writer if it's non-null. |
- append(String string) { |
- if (string != null && !string.isEmpty) { |
- emitSpaces(); |
- writer.print(string); |
- } |
- } |
- |
- /// Indent. |
- indent([n = 1]) { |
- while (n-- > 0) { |
- writer.indent(); |
- } |
- } |
- |
- /// Unindent |
- unindent([n = 1]) { |
- while (n-- > 0) { |
- writer.unindent(); |
- } |
- } |
- |
- /// Print this statement as if it were a block (e.g., surrounded by braces). |
- printAsBlock(Statement statement) { |
- if (codeTransforms && statement is! Block) { |
- token(OPEN_CURLY); |
- indent(); |
- newlines(); |
- visit(statement); |
- newlines(); |
- unindent(); |
- token(CLOSE_CURLY); |
- } else { |
- visit(statement); |
- } |
- } |
- |
- /// Emit any detected comments and newlines or a minimum as specified |
- /// by [min]. |
- int emitPrecedingCommentsAndNewlines(Token token, {min: 0}) { |
- |
- var comment = token.precedingComments; |
- var currentToken = comment != null ? comment : token; |
- |
- //Handle EOLs before newlines |
- if (isAtEOL(comment)) { |
- emitComment(comment, previousToken); |
- comment = comment.next; |
- currentToken = comment != null ? comment : token; |
- } |
- |
- var lines = max(min, countNewlinesBetween(previousToken, currentToken)); |
- writer.newlines(lines); |
- |
- previousToken = currentToken.previous; |
- |
- while (comment != null) { |
- |
- emitComment(comment, previousToken); |
- |
- var nextToken = comment.next != null ? comment.next : token; |
- var newlines = calculateNewlinesBetweenComments(comment, nextToken); |
- if (newlines > 0) { |
- writer.newlines(newlines); |
- lines += newlines; |
- } else if (!isEOF(token)) { |
- append(' '); |
- } |
- |
- previousToken = comment; |
- comment = comment.next; |
- } |
- |
- previousToken = token; |
- return lines; |
- } |
- |
- |
- ensureTrailingNewline() { |
- if (writer.lastToken is! NewlineToken) { |
- writer.newline(); |
- } |
- } |
- |
- |
- /// Test if this [comment] is at the end of a line. |
- bool isAtEOL(Token comment) => |
- comment != null && comment.toString().trim().startsWith(twoSlashes) && |
- sameLine(comment, previousToken); |
- |
- /// Emit this [comment], inserting leading whitespace if appropriate. |
- emitComment(Token comment, Token previousToken) { |
- if (!writer.currentLine.isWhitespace() && !isBlock(comment)) { |
- var ws = countSpacesBetween(previousToken, comment); |
- // Preserve one space but no more |
- if (ws > 0) { |
- append(' '); |
- } |
- } |
- |
- append(comment.toString().trim()); |
- } |
- |
- /// Count spaces between these tokens. Tokens on different lines return 0. |
- int countSpacesBetween(Token last, Token current) => isEOF(last) || |
- countNewlinesBetween(last, current) > 0 ? 0 : current.offset - last.end; |
- |
- /// Count the blanks between these two nodes. |
- int countBlankLinesBetween(ASTNode lastNode, ASTNode currentNode) => |
- countNewlinesBetween(lastNode.endToken, currentNode.beginToken); |
- |
- /// Count newlines preceeding this [node]. |
- int countPrecedingNewlines(ASTNode node) => |
- countNewlinesBetween(node.beginToken.previous, node.beginToken); |
- |
- /// Count newlines succeeding this [node]. |
- int countSucceedingNewlines(ASTNode node) => node == null ? 0 : |
- countNewlinesBetween(node.endToken, node.endToken.next); |
- |
- /// Count the blanks between these two tokens. |
- int countNewlinesBetween(Token last, Token current) { |
- if (last == null || current == null || isSynthetic(last)) { |
- return 0; |
- } |
- |
- return linesBetween(last.end - 1, current.offset); |
- } |
- |
- /// Calculate the newlines that should separate these comments. |
- int calculateNewlinesBetweenComments(Token last, Token current) { |
- // Insist on a newline after doc comments or single line comments |
- // (NOTE that EOL comments have already been processed). |
- if (isOldSingleLineDocComment(last) || isSingleLineComment(last)) { |
- return max(1, countNewlinesBetween(last, current)); |
- } else { |
- return countNewlinesBetween(last, current); |
- } |
- } |
- |
- /// Single line multi-line comments (e.g., '/** like this */'). |
- bool isOldSingleLineDocComment(Token comment) => |
- comment.lexeme.startsWith(r'/**') && singleLine(comment); |
- |
- /// Test if this [token] spans just one line. |
- bool singleLine(Token token) => linesBetween(token.offset, token.end) < 1; |
- |
- /// Test if token [first] is on the same line as [second]. |
- bool sameLine(Token first, Token second) => |
- countNewlinesBetween(first, second) == 0; |
- |
- /// Test if this is a multi-line [comment] (e.g., '/* ...' or '/** ...') |
- bool isMultiLineComment(Token comment) => |
- comment.type == TokenType.MULTI_LINE_COMMENT; |
- |
- /// Test if this is a single-line [comment] (e.g., '// ...') |
- bool isSingleLineComment(Token comment) => |
- comment.type == TokenType.SINGLE_LINE_COMMENT; |
- |
- /// Test if this [comment] is a block comment (e.g., '/* like this */').. |
- bool isBlock(Token comment) => |
- isMultiLineComment(comment) && singleLine(comment); |
- |
- /// Count the lines between two offsets. |
- int linesBetween(int lastOffset, int currentOffset) { |
- var lastLine = |
- lineInfo.getLocation(lastOffset).lineNumber; |
- var currentLine = |
- lineInfo.getLocation(currentOffset).lineNumber; |
- return currentLine - lastLine; |
- } |
- |
- String toString() => writer.toString(); |
- |
-} |