| Index: dart_style/lib/src/source_visitor.dart
|
| diff --git a/dart_style/lib/src/source_visitor.dart b/dart_style/lib/src/source_visitor.dart
|
| deleted file mode 100644
|
| index 524db2ecf8fe427478953bda1181ddf9019ffd19..0000000000000000000000000000000000000000
|
| --- a/dart_style/lib/src/source_visitor.dart
|
| +++ /dev/null
|
| @@ -1,2168 +0,0 @@
|
| -// 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.source_visitor;
|
| -
|
| -import 'package:analyzer/analyzer.dart';
|
| -import 'package:analyzer/src/generated/scanner.dart';
|
| -import 'package:analyzer/src/generated/source.dart';
|
| -
|
| -import 'argument_list_visitor.dart';
|
| -import 'call_chain_visitor.dart';
|
| -import 'chunk.dart';
|
| -import 'chunk_builder.dart';
|
| -import 'dart_formatter.dart';
|
| -import 'rule/argument.dart';
|
| -import 'rule/combinator.dart';
|
| -import 'rule/rule.dart';
|
| -import 'rule/type_argument.dart';
|
| -import 'source_code.dart';
|
| -import 'whitespace.dart';
|
| -
|
| -/// Visits every token of the AST and passes all of the relevant bits to a
|
| -/// [ChunkBuilder].
|
| -class SourceVisitor implements AstVisitor {
|
| - /// The builder for the block that is currently being visited.
|
| - ChunkBuilder builder;
|
| -
|
| - final DartFormatter _formatter;
|
| -
|
| - /// Cached line info for calculating blank lines.
|
| - LineInfo _lineInfo;
|
| -
|
| - /// The source being formatted.
|
| - final SourceCode _source;
|
| -
|
| - /// `true` if the visitor has written past the beginning of the selection in
|
| - /// the original source text.
|
| - bool _passedSelectionStart = false;
|
| -
|
| - /// `true` if the visitor has written past the end of the selection in the
|
| - /// original source text.
|
| - bool _passedSelectionEnd = false;
|
| -
|
| - /// The character offset of the end of the selection, if there is a selection.
|
| - ///
|
| - /// This is calculated and cached by [_findSelectionEnd].
|
| - int _selectionEnd;
|
| -
|
| - /// The rule that should be used for the contents of a literal body that are
|
| - /// about to be written.
|
| - ///
|
| - /// This is set by [visitArgumentList] to ensure that all block arguments
|
| - /// share a rule.
|
| - ///
|
| - /// If `null`, a literal body creates its own rule.
|
| - Rule _nextLiteralBodyRule;
|
| -
|
| - /// A stack that tracks forcing nested collections to split.
|
| - ///
|
| - /// Each entry corresponds to a collection currently being visited and the
|
| - /// value is whether or not it should be forced to split. Every time a
|
| - /// collection is entered, it sets all of the existing elements to `true`
|
| - /// then it pushes `false` for itself.
|
| - ///
|
| - /// When done visiting the elements, it removes its value. If it was set to
|
| - /// `true`, we know we visited a nested collection so we force this one to
|
| - /// split.
|
| - final List<bool> _collectionSplits = [];
|
| -
|
| - /// Initialize a newly created visitor to write source code representing
|
| - /// the visited nodes to the given [writer].
|
| - SourceVisitor(this._formatter, this._lineInfo, this._source) {
|
| - builder = new ChunkBuilder(_formatter, _source);
|
| - }
|
| -
|
| - /// Runs the visitor on [node], formatting its contents.
|
| - ///
|
| - /// Returns a [SourceCode] containing the resulting formatted source and
|
| - /// updated selection, if any.
|
| - ///
|
| - /// This is the only method that should be called externally. Everything else
|
| - /// is effectively private.
|
| - SourceCode run(AstNode node) {
|
| - visit(node);
|
| -
|
| - // Output trailing comments.
|
| - writePrecedingCommentsAndNewlines(node.endToken.next);
|
| -
|
| - // Finish writing and return the complete result.
|
| - return builder.end();
|
| - }
|
| -
|
| - visitAdjacentStrings(AdjacentStrings node) {
|
| - builder.startSpan();
|
| - builder.startRule();
|
| - visitNodes(node.strings, between: splitOrNewline);
|
| - builder.endRule();
|
| - builder.endSpan();
|
| - }
|
| -
|
| - visitAnnotation(Annotation node) {
|
| - token(node.atSign);
|
| - visit(node.name);
|
| - token(node.period);
|
| - visit(node.constructorName);
|
| - visit(node.arguments);
|
| - }
|
| -
|
| - /// Visits an argument list.
|
| - ///
|
| - /// This is a bit complex to handle the rules for formatting positional and
|
| - /// named arguments. The goals, in rough order of descending priority are:
|
| - ///
|
| - /// 1. Keep everything on the first line.
|
| - /// 2. Keep the named arguments together on the next line.
|
| - /// 3. Keep everything together on the second line.
|
| - /// 4. Split between one or more positional arguments, trying to keep as many
|
| - /// on earlier lines as possible.
|
| - /// 5. Split the named arguments each onto their own line.
|
| - visitArgumentList(ArgumentList node) {
|
| - // Corner case: handle empty argument lists.
|
| - if (node.arguments.isEmpty) {
|
| - token(node.leftParenthesis);
|
| -
|
| - // If there is a comment inside the parens, do allow splitting before it.
|
| - if (node.rightParenthesis.precedingComments != null) soloZeroSplit();
|
| -
|
| - token(node.rightParenthesis);
|
| - return;
|
| - }
|
| -
|
| - new ArgumentListVisitor(this, node).visit();
|
| - }
|
| -
|
| - visitAsExpression(AsExpression node) {
|
| - builder.startSpan();
|
| - visit(node.expression);
|
| - soloSplit();
|
| - token(node.asOperator);
|
| - space();
|
| - visit(node.type);
|
| - builder.endSpan();
|
| - }
|
| -
|
| - visitAssertStatement(AssertStatement node) {
|
| - _simpleStatement(node, () {
|
| - token(node.assertKeyword);
|
| - token(node.leftParenthesis);
|
| - soloZeroSplit();
|
| - visit(node.condition);
|
| - token(node.rightParenthesis);
|
| - });
|
| - }
|
| -
|
| - visitAssignmentExpression(AssignmentExpression node) {
|
| - builder.nestExpression();
|
| -
|
| - visit(node.leftHandSide);
|
| - _visitAssignment(node.operator, node.rightHandSide);
|
| -
|
| - builder.unnest();
|
| - }
|
| -
|
| - visitAwaitExpression(AwaitExpression node) {
|
| - token(node.awaitKeyword);
|
| - space();
|
| - visit(node.expression);
|
| - }
|
| -
|
| - visitBinaryExpression(BinaryExpression node) {
|
| - builder.startSpan();
|
| - builder.nestExpression();
|
| -
|
| - // Start lazily so we don't force the operator to split if a line comment
|
| - // appears before the first operand.
|
| - builder.startLazyRule();
|
| -
|
| - // Flatten out a tree/chain of the same precedence. If we split on this
|
| - // precedence level, we will break all of them.
|
| - var precedence = node.operator.type.precedence;
|
| -
|
| - traverse(Expression e) {
|
| - if (e is BinaryExpression && e.operator.type.precedence == precedence) {
|
| - traverse(e.leftOperand);
|
| -
|
| - space();
|
| - token(e.operator);
|
| -
|
| - split();
|
| - traverse(e.rightOperand);
|
| - } else {
|
| - visit(e);
|
| - }
|
| - }
|
| -
|
| - // Blocks as operands to infix operators should always nest like regular
|
| - // operands. (Granted, this case is exceedingly rare in real code.)
|
| - builder.startBlockArgumentNesting();
|
| -
|
| - traverse(node);
|
| -
|
| - builder.endBlockArgumentNesting();
|
| -
|
| - builder.unnest();
|
| - builder.endSpan();
|
| - builder.endRule();
|
| - }
|
| -
|
| - visitBlock(Block node) {
|
| - // For a block that is not a function body, just bump the indentation and
|
| - // keep it in the current block.
|
| - if (node.parent is! BlockFunctionBody) {
|
| - _writeBody(node.leftBracket, node.rightBracket, body: () {
|
| - visitNodes(node.statements, between: oneOrTwoNewlines, after: newline);
|
| - });
|
| - return;
|
| - }
|
| -
|
| - _startLiteralBody(node.leftBracket);
|
| - visitNodes(node.statements, between: oneOrTwoNewlines, after: newline);
|
| - _endLiteralBody(node.rightBracket, forceSplit: node.statements.isNotEmpty);
|
| - }
|
| -
|
| - visitBlockFunctionBody(BlockFunctionBody node) {
|
| - // Space after the parameter list.
|
| - space();
|
| -
|
| - // The "async" or "sync" keyword.
|
| - token(node.keyword);
|
| -
|
| - // The "*" in "async*" or "sync*".
|
| - token(node.star);
|
| - if (node.keyword != null) space();
|
| -
|
| - visit(node.block);
|
| - }
|
| -
|
| - visitBooleanLiteral(BooleanLiteral node) {
|
| - token(node.literal);
|
| - }
|
| -
|
| - visitBreakStatement(BreakStatement node) {
|
| - _simpleStatement(node, () {
|
| - token(node.breakKeyword);
|
| - visit(node.label, before: space);
|
| - });
|
| - }
|
| -
|
| - visitCascadeExpression(CascadeExpression node) {
|
| - // If the target of the cascade is a method call (or chain of them), we
|
| - // treat the nesting specially. Normally, you would end up with:
|
| - //
|
| - // receiver
|
| - // .method()
|
| - // .method()
|
| - // ..cascade()
|
| - // ..cascade();
|
| - //
|
| - // This is logical, since the method chain is an operand of the cascade
|
| - // expression, so it's more deeply nested. But it looks wrong, so we leave
|
| - // the method chain's nesting active until after the cascade sections to
|
| - // force the *cascades* to be deeper because it looks better:
|
| - //
|
| - // receiver
|
| - // .method()
|
| - // .method()
|
| - // ..cascade()
|
| - // ..cascade();
|
| - if (node.target is MethodInvocation) {
|
| - new CallChainVisitor(this, node.target).visit(unnest: false);
|
| - } else {
|
| - visit(node.target);
|
| - }
|
| -
|
| - builder.nestExpression(indent: Indent.cascade, now: true);
|
| - builder.startBlockArgumentNesting();
|
| -
|
| - // If the cascade sections have consistent names they can be broken
|
| - // normally otherwise they always get their own line.
|
| - if (_allowInlineCascade(node.cascadeSections)) {
|
| - builder.startRule();
|
| - zeroSplit();
|
| - visitNodes(node.cascadeSections, between: zeroSplit);
|
| - builder.endRule();
|
| - } else {
|
| - builder.startRule(new HardSplitRule());
|
| - zeroSplit();
|
| - visitNodes(node.cascadeSections, between: zeroSplit);
|
| - builder.endRule();
|
| - }
|
| -
|
| - builder.endBlockArgumentNesting();
|
| - builder.unnest();
|
| -
|
| - if (node.target is MethodInvocation) builder.unnest();
|
| - }
|
| -
|
| - /// Whether a cascade should be allowed to be inline as opposed to one
|
| - /// expression per line.
|
| - bool _allowInlineCascade(List<Expression> sections) {
|
| - if (sections.length < 2) return true;
|
| -
|
| - var name;
|
| - // We could be more forgiving about what constitutes sections with
|
| - // consistent names but for now we require all sections to have the same
|
| - // method name.
|
| - for (var expression in sections) {
|
| - if (expression is! MethodInvocation) return false;
|
| - if (name == null) {
|
| - name = expression.methodName.name;
|
| - } else if (name != expression.methodName.name) {
|
| - return false;
|
| - }
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - visitCatchClause(CatchClause node) {
|
| - token(node.onKeyword, after: 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, after: space);
|
| - visit(node.stackTraceParameter);
|
| - token(node.rightParenthesis);
|
| - space();
|
| - } else {
|
| - space();
|
| - }
|
| - visit(node.body);
|
| - }
|
| -
|
| - visitClassDeclaration(ClassDeclaration node) {
|
| - visitDeclarationMetadata(node.metadata);
|
| -
|
| - builder.nestExpression();
|
| - modifier(node.abstractKeyword);
|
| - token(node.classKeyword);
|
| - space();
|
| - visit(node.name);
|
| - visit(node.typeParameters);
|
| - visit(node.extendsClause);
|
| -
|
| - builder.startRule(new CombinatorRule());
|
| - visit(node.withClause);
|
| - visit(node.implementsClause);
|
| - builder.endRule();
|
| -
|
| - visit(node.nativeClause, before: space);
|
| - space();
|
| -
|
| - builder.unnest();
|
| - _writeBody(node.leftBracket, node.rightBracket, body: () {
|
| - if (node.members.isNotEmpty) {
|
| - for (var member in node.members) {
|
| - visit(member);
|
| -
|
| - if (member == node.members.last) {
|
| - newline();
|
| - break;
|
| - }
|
| -
|
| - var needsDouble = false;
|
| - if (member is ClassDeclaration) {
|
| - // Add a blank line after classes.
|
| - twoNewlines();
|
| - } else if (member is MethodDeclaration) {
|
| - // Add a blank line after non-empty block methods.
|
| - var method = member as MethodDeclaration;
|
| - if (method.body is BlockFunctionBody) {
|
| - var body = method.body as BlockFunctionBody;
|
| - needsDouble = body.block.statements.isNotEmpty;
|
| - }
|
| - }
|
| -
|
| - if (needsDouble) {
|
| - twoNewlines();
|
| - } else {
|
| - // Variables and arrow-bodied members can be more tightly packed if
|
| - // the user wants to group things together.
|
| - oneOrTwoNewlines();
|
| - }
|
| - }
|
| - }
|
| - });
|
| - }
|
| -
|
| - visitClassTypeAlias(ClassTypeAlias node) {
|
| - visitDeclarationMetadata(node.metadata);
|
| -
|
| - _simpleStatement(node, () {
|
| - modifier(node.abstractKeyword);
|
| - token(node.typedefKeyword);
|
| - space();
|
| - visit(node.name);
|
| - visit(node.typeParameters);
|
| - space();
|
| - token(node.equals);
|
| - space();
|
| -
|
| - visit(node.superclass);
|
| -
|
| - builder.startRule(new CombinatorRule());
|
| - visit(node.withClause);
|
| - visit(node.implementsClause);
|
| - builder.endRule();
|
| - });
|
| - }
|
| -
|
| - visitComment(Comment node) => null;
|
| -
|
| - visitCommentReference(CommentReference node) => null;
|
| -
|
| - visitCompilationUnit(CompilationUnit node) {
|
| - visit(node.scriptTag);
|
| -
|
| - // Put a blank line between the library tag and the other directives.
|
| - var directives = node.directives;
|
| - if (directives.isNotEmpty && directives.first is LibraryDirective) {
|
| - visit(directives.first);
|
| - twoNewlines();
|
| -
|
| - directives = directives.skip(1);
|
| - }
|
| -
|
| - visitNodes(directives, between: oneOrTwoNewlines);
|
| -
|
| - if (node.declarations.isNotEmpty) {
|
| - var needsDouble = true;
|
| -
|
| - for (var declaration in node.declarations) {
|
| - // Add a blank line before classes.
|
| - if (declaration is ClassDeclaration) needsDouble = true;
|
| -
|
| - if (needsDouble) {
|
| - twoNewlines();
|
| - } else {
|
| - // Variables and arrow-bodied members can be more tightly packed if
|
| - // the user wants to group things together.
|
| - oneOrTwoNewlines();
|
| - }
|
| -
|
| - visit(declaration);
|
| -
|
| - needsDouble = false;
|
| - if (declaration is ClassDeclaration) {
|
| - // Add a blank line after classes.
|
| - needsDouble = true;
|
| - } else if (declaration is FunctionDeclaration) {
|
| - // Add a blank line after non-empty block functions.
|
| - var function = declaration as FunctionDeclaration;
|
| - if (function.functionExpression.body is BlockFunctionBody) {
|
| - var body = function.functionExpression.body as BlockFunctionBody;
|
| - needsDouble = body.block.statements.isNotEmpty;
|
| - }
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - visitConditionalExpression(ConditionalExpression node) {
|
| - builder.nestExpression();
|
| -
|
| - // Push any block arguments all the way past the leading "?" and ":".
|
| - builder.nestExpression(indent: Indent.block, now: true);
|
| - builder.startBlockArgumentNesting();
|
| - builder.unnest();
|
| -
|
| - visit(node.condition);
|
| -
|
| - builder.startSpan();
|
| -
|
| - // If we split after one clause in a conditional, always split after both.
|
| - builder.startRule();
|
| - split();
|
| - token(node.question);
|
| - space();
|
| -
|
| - builder.nestExpression();
|
| - visit(node.thenExpression);
|
| - builder.unnest();
|
| -
|
| - split();
|
| - token(node.colon);
|
| - space();
|
| -
|
| - visit(node.elseExpression);
|
| -
|
| - builder.endRule();
|
| - builder.endSpan();
|
| - builder.endBlockArgumentNesting();
|
| - builder.unnest();
|
| - }
|
| -
|
| - visitConstructorDeclaration(ConstructorDeclaration node) {
|
| - visitMemberMetadata(node.metadata);
|
| -
|
| - modifier(node.externalKeyword);
|
| - modifier(node.constKeyword);
|
| - modifier(node.factoryKeyword);
|
| - visit(node.returnType);
|
| - token(node.period);
|
| - visit(node.name);
|
| -
|
| - // Make the rule for the ":" span both the preceding parameter list and
|
| - // the entire initialization list. This ensures that we split before the
|
| - // ":" if the parameters and initialization list don't all fit on one line.
|
| - builder.startRule();
|
| -
|
| - _visitBody(node.parameters, node.body, () {
|
| - // Check for redirects or initializer lists.
|
| - if (node.redirectedConstructor != null) {
|
| - _visitConstructorRedirects(node);
|
| - } else if (node.initializers.isNotEmpty) {
|
| - _visitConstructorInitializers(node);
|
| - }
|
| - });
|
| - }
|
| -
|
| - void _visitConstructorRedirects(ConstructorDeclaration node) {
|
| - token(node.separator /* = */, before: space, after: space);
|
| - visitCommaSeparatedNodes(node.initializers);
|
| - visit(node.redirectedConstructor);
|
| - }
|
| -
|
| - void _visitConstructorInitializers(ConstructorDeclaration node) {
|
| - // Shift the ":" forward.
|
| - builder.indent(Indent.constructorInitializer);
|
| -
|
| - split();
|
| - token(node.separator); // ":".
|
| - space();
|
| -
|
| - // Shift everything past the ":".
|
| - builder.indent();
|
| -
|
| - for (var i = 0; i < node.initializers.length; i++) {
|
| - if (i > 0) {
|
| - // Preceding comma.
|
| - token(node.initializers[i].beginToken.previous);
|
| - newline();
|
| - }
|
| -
|
| - node.initializers[i].accept(this);
|
| - }
|
| -
|
| - builder.unindent();
|
| - builder.unindent();
|
| -
|
| - // End the rule for ":" after all of the initializers.
|
| - builder.endRule();
|
| - }
|
| -
|
| - visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
|
| - builder.nestExpression();
|
| -
|
| - token(node.thisKeyword);
|
| - token(node.period);
|
| - visit(node.fieldName);
|
| -
|
| - _visitAssignment(node.equals, node.expression);
|
| -
|
| - builder.unnest();
|
| - }
|
| -
|
| - visitConstructorName(ConstructorName node) {
|
| - visit(node.type);
|
| - token(node.period);
|
| - visit(node.name);
|
| - }
|
| -
|
| - visitContinueStatement(ContinueStatement node) {
|
| - _simpleStatement(node, () {
|
| - token(node.continueKeyword);
|
| - visit(node.label, before: space);
|
| - });
|
| - }
|
| -
|
| - visitDeclaredIdentifier(DeclaredIdentifier node) {
|
| - modifier(node.keyword);
|
| - visit(node.type, after: space);
|
| - visit(node.identifier);
|
| - }
|
| -
|
| - visitDefaultFormalParameter(DefaultFormalParameter node) {
|
| - visit(node.parameter);
|
| - if (node.separator != null) {
|
| - builder.startSpan();
|
| - builder.nestExpression();
|
| -
|
| - // The '=' separator is preceded by a space, ":" is not.
|
| - if (node.separator.type == TokenType.EQ) space();
|
| - token(node.separator);
|
| -
|
| - soloSplit(Cost.assignment);
|
| - visit(node.defaultValue);
|
| -
|
| - builder.unnest();
|
| - builder.endSpan();
|
| - }
|
| - }
|
| -
|
| - visitDoStatement(DoStatement node) {
|
| - _simpleStatement(node, () {
|
| - token(node.doKeyword);
|
| - space();
|
| - visit(node.body);
|
| - space();
|
| - token(node.whileKeyword);
|
| - space();
|
| - token(node.leftParenthesis);
|
| - soloZeroSplit();
|
| - visit(node.condition);
|
| - token(node.rightParenthesis);
|
| - });
|
| - }
|
| -
|
| - visitDoubleLiteral(DoubleLiteral node) {
|
| - token(node.literal);
|
| - }
|
| -
|
| - visitEmptyFunctionBody(EmptyFunctionBody node) {
|
| - token(node.semicolon);
|
| - }
|
| -
|
| - visitEmptyStatement(EmptyStatement node) {
|
| - token(node.semicolon);
|
| - }
|
| -
|
| - visitEnumConstantDeclaration(EnumConstantDeclaration node) {
|
| - visit(node.name);
|
| - }
|
| -
|
| - visitEnumDeclaration(EnumDeclaration node) {
|
| - visitDeclarationMetadata(node.metadata);
|
| -
|
| - token(node.enumKeyword);
|
| - space();
|
| - visit(node.name);
|
| - space();
|
| -
|
| - _writeBody(node.leftBracket, node.rightBracket, space: true, body: () {
|
| - visitCommaSeparatedNodes(node.constants, between: split);
|
| - });
|
| - }
|
| -
|
| - visitExportDirective(ExportDirective node) {
|
| - visitDeclarationMetadata(node.metadata);
|
| -
|
| - _simpleStatement(node, () {
|
| - token(node.keyword);
|
| - space();
|
| - visit(node.uri);
|
| -
|
| - builder.startRule(new CombinatorRule());
|
| - visitNodes(node.combinators);
|
| - builder.endRule();
|
| - });
|
| - }
|
| -
|
| - visitExpressionFunctionBody(ExpressionFunctionBody node) {
|
| - // Space after the parameter list.
|
| - space();
|
| -
|
| - // The "async" or "sync" keyword.
|
| - token(node.keyword, after: space);
|
| -
|
| - // Try to keep the "(...) => " with the start of the body for anonymous
|
| - // functions.
|
| - if (_isInLambda(node)) builder.startSpan();
|
| -
|
| - token(node.functionDefinition); // "=>".
|
| -
|
| - // Split after the "=>", using the rule created before the parameters
|
| - // by _visitBody().
|
| - split();
|
| - builder.endRule();
|
| -
|
| - if (_isInLambda(node)) builder.endSpan();
|
| -
|
| - builder.startBlockArgumentNesting();
|
| - builder.startSpan();
|
| - visit(node.expression);
|
| - builder.endSpan();
|
| - builder.endBlockArgumentNesting();
|
| -
|
| - token(node.semicolon);
|
| - }
|
| -
|
| - visitExpressionStatement(ExpressionStatement node) {
|
| - _simpleStatement(node, () {
|
| - visit(node.expression);
|
| - });
|
| - }
|
| -
|
| - visitExtendsClause(ExtendsClause node) {
|
| - soloSplit();
|
| - token(node.extendsKeyword);
|
| - space();
|
| - visit(node.superclass);
|
| - }
|
| -
|
| - visitFieldDeclaration(FieldDeclaration node) {
|
| - visitMemberMetadata(node.metadata);
|
| -
|
| - _simpleStatement(node, () {
|
| - modifier(node.staticKeyword);
|
| - visit(node.fields);
|
| - });
|
| - }
|
| -
|
| - visitFieldFormalParameter(FieldFormalParameter node) {
|
| - visitParameterMetadata(node.metadata, () {
|
| - token(node.keyword, after: space);
|
| - visit(node.type, after: space);
|
| - token(node.thisKeyword);
|
| - token(node.period);
|
| - visit(node.identifier);
|
| - visit(node.parameters);
|
| - });
|
| - }
|
| -
|
| - visitForEachStatement(ForEachStatement node) {
|
| - builder.nestExpression();
|
| - token(node.awaitKeyword, after: space);
|
| - token(node.forKeyword);
|
| - space();
|
| - token(node.leftParenthesis);
|
| - if (node.loopVariable != null) {
|
| - visit(node.loopVariable);
|
| - } else {
|
| - visit(node.identifier);
|
| - }
|
| - soloSplit();
|
| - token(node.inKeyword);
|
| - space();
|
| - visit(node.iterable);
|
| - token(node.rightParenthesis);
|
| - space();
|
| - visit(node.body);
|
| - builder.unnest();
|
| - }
|
| -
|
| - visitFormalParameterList(FormalParameterList node) {
|
| - // Corner case: empty parameter lists.
|
| - if (node.parameters.isEmpty) {
|
| - token(node.leftParenthesis);
|
| -
|
| - // If there is a comment, do allow splitting before it.
|
| - if (node.rightParenthesis.precedingComments != null) soloZeroSplit();
|
| -
|
| - token(node.rightParenthesis);
|
| - return;
|
| - }
|
| -
|
| - var requiredParams = node.parameters
|
| - .where((param) => param is! DefaultFormalParameter)
|
| - .toList();
|
| - var optionalParams = node.parameters
|
| - .where((param) => param is DefaultFormalParameter)
|
| - .toList();
|
| -
|
| - builder.nestExpression();
|
| - token(node.leftParenthesis);
|
| -
|
| - var rule;
|
| - if (requiredParams.isNotEmpty) {
|
| - if (requiredParams.length > 1) {
|
| - rule = new MultiplePositionalRule(null, 0, 0);
|
| - } else {
|
| - rule = new SinglePositionalRule(null);
|
| - }
|
| -
|
| - builder.startRule(rule);
|
| - if (_isInLambda(node)) {
|
| - // Don't allow splitting before the first argument (i.e. right after
|
| - // the bare "(" in a lambda. Instead, just stuff a null chunk in there
|
| - // to avoid confusing the arg rule.
|
| - rule.beforeArgument(null);
|
| - } else {
|
| - // Split before the first argument.
|
| - rule.beforeArgument(zeroSplit());
|
| - }
|
| -
|
| - builder.startSpan();
|
| -
|
| - for (var param in requiredParams) {
|
| - visit(param);
|
| -
|
| - // Write the trailing comma.
|
| - if (param != node.parameters.last) token(param.endToken.next);
|
| -
|
| - if (param != requiredParams.last) rule.beforeArgument(split());
|
| - }
|
| -
|
| - builder.endSpan();
|
| - builder.endRule();
|
| - }
|
| -
|
| - if (optionalParams.isNotEmpty) {
|
| - var namedRule = new NamedRule(null);
|
| - if (rule != null) rule.setNamedArgsRule(namedRule);
|
| -
|
| - builder.startRule(namedRule);
|
| -
|
| - namedRule
|
| - .beforeArguments(builder.split(space: requiredParams.isNotEmpty));
|
| -
|
| - // "[" or "{" for optional parameters.
|
| - token(node.leftDelimiter);
|
| -
|
| - for (var param in optionalParams) {
|
| - visit(param);
|
| -
|
| - // Write the trailing comma.
|
| - if (param != node.parameters.last) token(param.endToken.next);
|
| - if (param != optionalParams.last) split();
|
| - }
|
| -
|
| - builder.endRule();
|
| -
|
| - // "]" or "}" for optional parameters.
|
| - token(node.rightDelimiter);
|
| - }
|
| -
|
| - token(node.rightParenthesis);
|
| - builder.unnest();
|
| - }
|
| -
|
| - visitForStatement(ForStatement node) {
|
| - builder.nestExpression();
|
| - token(node.forKeyword);
|
| - space();
|
| - token(node.leftParenthesis);
|
| -
|
| - builder.startRule();
|
| -
|
| - // The initialization clause.
|
| - if (node.initialization != null) {
|
| - visit(node.initialization);
|
| - } else if (node.variables != null) {
|
| - // Indent split variables more so they aren't at the same level
|
| - // as the rest of the loop clauses.
|
| - builder.indent(Indent.loopVariable);
|
| -
|
| - // Allow the variables to stay unsplit even if the clauses split.
|
| - builder.startRule();
|
| -
|
| - var declaration = node.variables;
|
| - visitDeclarationMetadata(declaration.metadata);
|
| - modifier(declaration.keyword);
|
| - visit(declaration.type, after: space);
|
| -
|
| - visitCommaSeparatedNodes(declaration.variables, between: () {
|
| - split();
|
| - });
|
| -
|
| - builder.endRule();
|
| - builder.unindent();
|
| - }
|
| -
|
| - token(node.leftSeparator);
|
| -
|
| - // The condition clause.
|
| - if (node.condition != null) split();
|
| - visit(node.condition);
|
| - token(node.rightSeparator);
|
| -
|
| - // The update clause.
|
| - if (node.updaters.isNotEmpty) {
|
| - split();
|
| -
|
| - // Allow the updates to stay unsplit even if the clauses split.
|
| - builder.startRule();
|
| -
|
| - visitCommaSeparatedNodes(node.updaters, between: split);
|
| -
|
| - builder.endRule();
|
| - }
|
| -
|
| - token(node.rightParenthesis);
|
| - builder.endRule();
|
| - builder.unnest();
|
| -
|
| - // The body.
|
| - if (node.body is! EmptyStatement) space();
|
| - visit(node.body);
|
| - }
|
| -
|
| - visitFunctionDeclaration(FunctionDeclaration node) {
|
| - visitMemberMetadata(node.metadata);
|
| -
|
| - builder.nestExpression();
|
| - modifier(node.externalKeyword);
|
| - visit(node.returnType, after: space);
|
| - modifier(node.propertyKeyword);
|
| - visit(node.name);
|
| - visit(node.functionExpression);
|
| - builder.unnest();
|
| - }
|
| -
|
| - visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
|
| - visit(node.functionDeclaration);
|
| - }
|
| -
|
| - visitFunctionExpression(FunctionExpression node) {
|
| - _visitBody(node.parameters, node.body);
|
| - }
|
| -
|
| - visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
|
| - visit(node.function);
|
| - visit(node.argumentList);
|
| - }
|
| -
|
| - visitFunctionTypeAlias(FunctionTypeAlias node) {
|
| - visitDeclarationMetadata(node.metadata);
|
| -
|
| - _simpleStatement(node, () {
|
| - token(node.typedefKeyword);
|
| - space();
|
| - visit(node.returnType, after: space);
|
| - visit(node.name);
|
| - visit(node.typeParameters);
|
| - visit(node.parameters);
|
| - });
|
| - }
|
| -
|
| - visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
|
| - visitParameterMetadata(node.metadata, () {
|
| - visit(node.returnType, after: space);
|
| -
|
| - // Try to keep the function's parameters with its name.
|
| - builder.startSpan();
|
| - visit(node.identifier);
|
| - visit(node.parameters);
|
| - builder.endSpan();
|
| - });
|
| - }
|
| -
|
| - visitHideCombinator(HideCombinator node) {
|
| - _visitCombinator(node.keyword, node.hiddenNames);
|
| - }
|
| -
|
| - visitIfStatement(IfStatement node) {
|
| - builder.nestExpression();
|
| - token(node.ifKeyword);
|
| - space();
|
| - token(node.leftParenthesis);
|
| - visit(node.condition);
|
| - token(node.rightParenthesis);
|
| -
|
| - space();
|
| - visit(node.thenStatement);
|
| - builder.unnest();
|
| -
|
| - if (node.elseStatement != null) {
|
| - if (node.thenStatement is Block) {
|
| - space();
|
| - } else {
|
| - // Corner case where an else follows a single-statement then clause.
|
| - // This is against the style guide, but we still need to handle it. If
|
| - // it happens, put the else on the next line.
|
| - newline();
|
| - }
|
| -
|
| - token(node.elseKeyword);
|
| - space();
|
| - visit(node.elseStatement);
|
| - }
|
| - }
|
| -
|
| - visitImplementsClause(ImplementsClause node) {
|
| - _visitCombinator(node.implementsKeyword, node.interfaces);
|
| - }
|
| -
|
| - visitImportDirective(ImportDirective node) {
|
| - visitDeclarationMetadata(node.metadata);
|
| -
|
| - _simpleStatement(node, () {
|
| - token(node.keyword);
|
| - space();
|
| - visit(node.uri);
|
| -
|
| - if (node.asKeyword != null) {
|
| - soloSplit();
|
| - token(node.deferredKeyword, after: space);
|
| - token(node.asKeyword);
|
| - space();
|
| - visit(node.prefix);
|
| - }
|
| -
|
| - builder.startRule(new CombinatorRule());
|
| - visitNodes(node.combinators);
|
| - builder.endRule();
|
| - });
|
| - }
|
| -
|
| - visitIndexExpression(IndexExpression node) {
|
| - builder.nestExpression();
|
| -
|
| - if (node.isCascaded) {
|
| - token(node.period);
|
| - } else {
|
| - visit(node.target);
|
| - }
|
| -
|
| - if (node.target is IndexExpression) {
|
| - // Corner case: On a chain of [] accesses, allow splitting between them.
|
| - // Produces nicer output in cases like:
|
| - //
|
| - // someJson['property']['property']['property']['property']...
|
| - soloZeroSplit();
|
| - }
|
| -
|
| - builder.startSpan();
|
| - token(node.leftBracket);
|
| - soloZeroSplit();
|
| - visit(node.index);
|
| - token(node.rightBracket);
|
| - builder.endSpan();
|
| - builder.unnest();
|
| - }
|
| -
|
| - visitInstanceCreationExpression(InstanceCreationExpression node) {
|
| - builder.startSpan();
|
| - token(node.keyword);
|
| - space();
|
| - visit(node.constructorName);
|
| - visit(node.argumentList);
|
| - builder.endSpan();
|
| - }
|
| -
|
| - visitIntegerLiteral(IntegerLiteral node) {
|
| - token(node.literal);
|
| - }
|
| -
|
| - visitInterpolationExpression(InterpolationExpression node) {
|
| - token(node.leftBracket);
|
| - visit(node.expression);
|
| - token(node.rightBracket);
|
| - }
|
| -
|
| - visitInterpolationString(InterpolationString node) {
|
| - token(node.contents);
|
| - }
|
| -
|
| - visitIsExpression(IsExpression node) {
|
| - builder.startSpan();
|
| - visit(node.expression);
|
| - soloSplit();
|
| - token(node.isOperator);
|
| - token(node.notOperator);
|
| - space();
|
| - visit(node.type);
|
| - builder.endSpan();
|
| - }
|
| -
|
| - visitLabel(Label node) {
|
| - visit(node.label);
|
| - token(node.colon);
|
| - }
|
| -
|
| - visitLabeledStatement(LabeledStatement node) {
|
| - visitNodes(node.labels, between: space, after: space);
|
| - visit(node.statement);
|
| - }
|
| -
|
| - visitLibraryDirective(LibraryDirective node) {
|
| - visitDeclarationMetadata(node.metadata);
|
| -
|
| - _simpleStatement(node, () {
|
| - token(node.keyword);
|
| - space();
|
| - visit(node.name);
|
| - });
|
| - }
|
| -
|
| - visitLibraryIdentifier(LibraryIdentifier node) {
|
| - visit(node.components.first);
|
| - for (var component in node.components.skip(1)) {
|
| - token(component.beginToken.previous); // "."
|
| - visit(component);
|
| - }
|
| - }
|
| -
|
| - visitListLiteral(ListLiteral node) {
|
| - // Corner case: Splitting inside a list looks bad if there's only one
|
| - // element, so make those more costly.
|
| - var cost = node.elements.length <= 1 ? Cost.singleElementList : Cost.normal;
|
| - _visitCollectionLiteral(
|
| - node, node.leftBracket, node.elements, node.rightBracket, cost);
|
| - }
|
| -
|
| - visitMapLiteral(MapLiteral node) {
|
| - _visitCollectionLiteral(
|
| - node, node.leftBracket, node.entries, node.rightBracket);
|
| - }
|
| -
|
| - visitMapLiteralEntry(MapLiteralEntry node) {
|
| - visit(node.key);
|
| - token(node.separator);
|
| - soloSplit();
|
| - visit(node.value);
|
| - }
|
| -
|
| - visitMethodDeclaration(MethodDeclaration node) {
|
| - visitMemberMetadata(node.metadata);
|
| -
|
| - modifier(node.externalKeyword);
|
| - modifier(node.modifierKeyword);
|
| - visit(node.returnType, after: space);
|
| - modifier(node.propertyKeyword);
|
| - modifier(node.operatorKeyword);
|
| - visit(node.name);
|
| -
|
| - _visitBody(node.parameters, node.body);
|
| - }
|
| -
|
| - visitMethodInvocation(MethodInvocation node) {
|
| - // If there's no target, this is a "bare" function call like "foo(1, 2)",
|
| - // or a section in a cascade. Handle this case specially.
|
| - if (node.target == null) {
|
| - // Try to keep the entire method invocation one line.
|
| - builder.startSpan();
|
| - builder.nestExpression();
|
| -
|
| - // This will be non-null for cascade sections.
|
| - token(node.operator);
|
| - token(node.methodName.token);
|
| - visit(node.argumentList);
|
| -
|
| - builder.unnest();
|
| - builder.endSpan();
|
| - return;
|
| - }
|
| -
|
| - new CallChainVisitor(this, node).visit();
|
| - }
|
| -
|
| - visitNamedExpression(NamedExpression node) {
|
| - builder.nestExpression();
|
| - builder.startSpan();
|
| - visit(node.name);
|
| - visit(node.expression, before: soloSplit);
|
| - builder.endSpan();
|
| - builder.unnest();
|
| - }
|
| -
|
| - visitNativeClause(NativeClause node) {
|
| - token(node.nativeKeyword);
|
| - space();
|
| - visit(node.name);
|
| - }
|
| -
|
| - visitNativeFunctionBody(NativeFunctionBody node) {
|
| - _simpleStatement(node, () {
|
| - builder.nestExpression(now: true);
|
| - soloSplit();
|
| - token(node.nativeKeyword);
|
| - space();
|
| - visit(node.stringLiteral);
|
| - builder.unnest();
|
| - });
|
| - }
|
| -
|
| - visitNullLiteral(NullLiteral node) {
|
| - token(node.literal);
|
| - }
|
| -
|
| - visitParenthesizedExpression(ParenthesizedExpression node) {
|
| - builder.nestExpression();
|
| - token(node.leftParenthesis);
|
| - visit(node.expression);
|
| - builder.unnest();
|
| - token(node.rightParenthesis);
|
| - }
|
| -
|
| - visitPartDirective(PartDirective node) {
|
| - _simpleStatement(node, () {
|
| - token(node.keyword);
|
| - space();
|
| - visit(node.uri);
|
| - });
|
| - }
|
| -
|
| - visitPartOfDirective(PartOfDirective node) {
|
| - _simpleStatement(node, () {
|
| - token(node.keyword);
|
| - space();
|
| - token(node.ofKeyword);
|
| - space();
|
| - visit(node.libraryName);
|
| - });
|
| - }
|
| -
|
| - 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);
|
| -
|
| - // Corner case: put a space between successive "-" operators so we don't
|
| - // inadvertently turn them into a "--" decrement operator.
|
| - if (node.operand is PrefixExpression &&
|
| - (node.operand as PrefixExpression).operator.lexeme == "-") {
|
| - space();
|
| - }
|
| -
|
| - visit(node.operand);
|
| - }
|
| -
|
| - visitPropertyAccess(PropertyAccess node) {
|
| - if (node.isCascaded) {
|
| - token(node.operator);
|
| - visit(node.propertyName);
|
| - return;
|
| - }
|
| -
|
| - new CallChainVisitor(this, node).visit();
|
| - }
|
| -
|
| - visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) {
|
| - builder.startSpan();
|
| -
|
| - token(node.thisKeyword);
|
| - token(node.period);
|
| - visit(node.constructorName);
|
| - visit(node.argumentList);
|
| -
|
| - builder.endSpan();
|
| - }
|
| -
|
| - visitRethrowExpression(RethrowExpression node) {
|
| - token(node.rethrowKeyword);
|
| - }
|
| -
|
| - visitReturnStatement(ReturnStatement node) {
|
| - _simpleStatement(node, () {
|
| - token(node.returnKeyword);
|
| - visit(node.expression, before: space);
|
| - });
|
| - }
|
| -
|
| - visitScriptTag(ScriptTag node) {
|
| - // The lexeme includes the trailing newline. Strip it off since the
|
| - // formatter ensures it gets a newline after it. Since the script tag must
|
| - // come at the top of the file, we don't have to worry about preceding
|
| - // comments or whitespace.
|
| - _writeText(node.scriptTag.lexeme.trim(), node.offset);
|
| -
|
| - oneOrTwoNewlines();
|
| - }
|
| -
|
| - visitShowCombinator(ShowCombinator node) {
|
| - _visitCombinator(node.keyword, node.shownNames);
|
| - }
|
| -
|
| - visitSimpleFormalParameter(SimpleFormalParameter node) {
|
| - visitParameterMetadata(node.metadata, () {
|
| - modifier(node.keyword);
|
| - visit(node.type, after: space);
|
| - visit(node.identifier);
|
| - });
|
| - }
|
| -
|
| - visitSimpleIdentifier(SimpleIdentifier node) {
|
| - token(node.token);
|
| - }
|
| -
|
| - visitSimpleStringLiteral(SimpleStringLiteral node) {
|
| - // Since we output the string literal manually, ensure any preceding
|
| - // comments are written first.
|
| - writePrecedingCommentsAndNewlines(node.literal);
|
| -
|
| - _writeStringLiteral(node.literal.lexeme, node.offset);
|
| - }
|
| -
|
| - visitStringInterpolation(StringInterpolation node) {
|
| - // Since we output the interpolated text manually, ensure we include any
|
| - // preceding stuff first.
|
| - writePrecedingCommentsAndNewlines(node.beginToken);
|
| -
|
| - // Right now, the formatter does not try to do any reformatting of the
|
| - // contents of interpolated strings. Instead, it treats the entire thing as
|
| - // a single (possibly multi-line) chunk of text.
|
| - _writeStringLiteral(
|
| - _source.text.substring(node.beginToken.offset, node.endToken.end),
|
| - node.offset);
|
| - }
|
| -
|
| - visitSuperConstructorInvocation(SuperConstructorInvocation node) {
|
| - builder.startSpan();
|
| -
|
| - token(node.superKeyword);
|
| - token(node.period);
|
| - visit(node.constructorName);
|
| - visit(node.argumentList);
|
| -
|
| - builder.endSpan();
|
| - }
|
| -
|
| - visitSuperExpression(SuperExpression node) {
|
| - token(node.superKeyword);
|
| - }
|
| -
|
| - visitSwitchCase(SwitchCase node) {
|
| - visitNodes(node.labels, between: space, after: space);
|
| - token(node.keyword);
|
| - space();
|
| - visit(node.expression);
|
| - token(node.colon);
|
| -
|
| - builder.indent();
|
| - // TODO(rnystrom): Allow inline cases?
|
| - newline();
|
| -
|
| - visitNodes(node.statements, between: oneOrTwoNewlines);
|
| - builder.unindent();
|
| - }
|
| -
|
| - visitSwitchDefault(SwitchDefault node) {
|
| - visitNodes(node.labels, between: space, after: space);
|
| - token(node.keyword);
|
| - token(node.colon);
|
| -
|
| - builder.indent();
|
| - // TODO(rnystrom): Allow inline cases?
|
| - newline();
|
| -
|
| - visitNodes(node.statements, between: oneOrTwoNewlines);
|
| - builder.unindent();
|
| - }
|
| -
|
| - visitSwitchStatement(SwitchStatement node) {
|
| - builder.nestExpression();
|
| - token(node.switchKeyword);
|
| - space();
|
| - token(node.leftParenthesis);
|
| - soloZeroSplit();
|
| - visit(node.expression);
|
| - token(node.rightParenthesis);
|
| - space();
|
| - token(node.leftBracket);
|
| - builder.indent();
|
| - newline();
|
| -
|
| - visitNodes(node.members, between: oneOrTwoNewlines, after: newline);
|
| - token(node.rightBracket, before: () {
|
| - builder.unindent();
|
| - newline();
|
| - });
|
| - builder.unnest();
|
| - }
|
| -
|
| - visitSymbolLiteral(SymbolLiteral node) {
|
| - token(node.poundSign);
|
| - var components = node.components;
|
| - for (var component in components) {
|
| - // The '.' separator
|
| - if (component.previous.lexeme == '.') {
|
| - token(component.previous);
|
| - }
|
| - token(component);
|
| - }
|
| - }
|
| -
|
| - visitThisExpression(ThisExpression node) {
|
| - token(node.thisKeyword);
|
| - }
|
| -
|
| - visitThrowExpression(ThrowExpression node) {
|
| - token(node.throwKeyword);
|
| - space();
|
| - visit(node.expression);
|
| - }
|
| -
|
| - visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
|
| - visitDeclarationMetadata(node.metadata);
|
| -
|
| - _simpleStatement(node, () {
|
| - visit(node.variables);
|
| - });
|
| - }
|
| -
|
| - visitTryStatement(TryStatement node) {
|
| - token(node.tryKeyword);
|
| - space();
|
| - visit(node.body);
|
| - visitNodes(node.catchClauses, before: space, between: space);
|
| - token(node.finallyKeyword, before: space, after: space);
|
| - visit(node.finallyBlock);
|
| - }
|
| -
|
| - visitTypeArgumentList(TypeArgumentList node) {
|
| - _visitGenericList(node.leftBracket, node.rightBracket, node.arguments);
|
| - }
|
| -
|
| - visitTypeName(TypeName node) {
|
| - visit(node.name);
|
| - visit(node.typeArguments);
|
| - }
|
| -
|
| - visitTypeParameter(TypeParameter node) {
|
| - visitParameterMetadata(node.metadata, () {
|
| - visit(node.name);
|
| - token(node.extendsKeyword, before: space, after: space);
|
| - visit(node.bound);
|
| - });
|
| - }
|
| -
|
| - visitTypeParameterList(TypeParameterList node) {
|
| - _visitGenericList(node.leftBracket, node.rightBracket, node.typeParameters);
|
| - }
|
| -
|
| - visitVariableDeclaration(VariableDeclaration node) {
|
| - visit(node.name);
|
| - if (node.initializer == null) return;
|
| -
|
| - _visitAssignment(node.equals, node.initializer);
|
| - }
|
| -
|
| - visitVariableDeclarationList(VariableDeclarationList node) {
|
| - visitDeclarationMetadata(node.metadata);
|
| - modifier(node.keyword);
|
| - visit(node.type, after: space);
|
| -
|
| - // Use a single rule for all of the variables. If there are multiple
|
| - // declarations, we will try to keep them all on one line. If that isn't
|
| - // possible, we split after *every* declaration so that each is on its own
|
| - // line.
|
| - builder.startRule();
|
| - visitCommaSeparatedNodes(node.variables, between: split);
|
| - builder.endRule();
|
| - }
|
| -
|
| - visitVariableDeclarationStatement(VariableDeclarationStatement node) {
|
| - _simpleStatement(node, () {
|
| - visit(node.variables);
|
| - });
|
| - }
|
| -
|
| - visitWhileStatement(WhileStatement node) {
|
| - builder.nestExpression();
|
| - token(node.whileKeyword);
|
| - space();
|
| - token(node.leftParenthesis);
|
| - soloZeroSplit();
|
| - visit(node.condition);
|
| - token(node.rightParenthesis);
|
| - if (node.body is! EmptyStatement) space();
|
| - visit(node.body);
|
| - builder.unnest();
|
| - }
|
| -
|
| - visitWithClause(WithClause node) {
|
| - _visitCombinator(node.withKeyword, node.mixinTypes);
|
| - }
|
| -
|
| - visitYieldStatement(YieldStatement node) {
|
| - _simpleStatement(node, () {
|
| - token(node.yieldKeyword);
|
| - token(node.star);
|
| - space();
|
| - visit(node.expression);
|
| - });
|
| - }
|
| -
|
| - /// Visit a [node], and if not null, optionally preceded or followed by the
|
| - /// specified functions.
|
| - void visit(AstNode node, {void before(), void after()}) {
|
| - if (node == null) return;
|
| -
|
| - if (before != null) before();
|
| -
|
| - node.accept(this);
|
| -
|
| - if (after != null) after();
|
| - }
|
| -
|
| - /// Visit metadata annotations on directives and declarations.
|
| - ///
|
| - /// These always force the annotations to be on the previous line.
|
| - void visitDeclarationMetadata(NodeList<Annotation> metadata) {
|
| - // If there are multiple annotations, they are always on their own lines,
|
| - // even the last.
|
| - if (metadata.length > 1) {
|
| - visitNodes(metadata, between: newline, after: newline);
|
| - } else {
|
| - visitNodes(metadata, between: space, after: newline);
|
| - }
|
| - }
|
| -
|
| - /// Visit metadata annotations on members.
|
| - ///
|
| - /// These may be on the same line as the member, or on the previous.
|
| - void visitMemberMetadata(NodeList<Annotation> metadata) {
|
| - // If there are multiple annotations, they are always on their own lines,
|
| - // even the last.
|
| - if (metadata.length > 1) {
|
| - visitNodes(metadata, between: newline, after: newline);
|
| - } else {
|
| - visitNodes(metadata, between: space, after: spaceOrNewline);
|
| - }
|
| - }
|
| -
|
| - /// Visits metadata annotations on parameters and type parameters.
|
| - ///
|
| - /// These are always on the same line as the parameter.
|
| - void visitParameterMetadata(
|
| - NodeList<Annotation> metadata, void visitParameter()) {
|
| - // Split before all of the annotations or none.
|
| - builder.startRule();
|
| - visitNodes(metadata, between: split, after: split);
|
| - visitParameter();
|
| -
|
| - // Wrap the rule around the parameter too. If it splits, we want to force
|
| - // the annotations to split as well.
|
| - builder.endRule();
|
| - }
|
| -
|
| - /// Visits the `=` and the following expression in any place where an `=`
|
| - /// appears:
|
| - ///
|
| - /// * Assignment
|
| - /// * Variable declaration
|
| - /// * Constructor initialization
|
| - void _visitAssignment(Token equalsOperator, Expression rightHandSide) {
|
| - space();
|
| - token(equalsOperator);
|
| - soloSplit(Cost.assignment);
|
| - builder.startSpan();
|
| - visit(rightHandSide);
|
| - builder.endSpan();
|
| - }
|
| -
|
| - /// Visits a type parameter or type argument list.
|
| - void _visitGenericList(
|
| - Token leftBracket, Token rightBracket, List<AstNode> nodes) {
|
| - var rule = new TypeArgumentRule();
|
| - builder.startRule(rule);
|
| - builder.startSpan();
|
| - builder.nestExpression();
|
| -
|
| - token(leftBracket);
|
| - rule.beforeArgument(zeroSplit());
|
| -
|
| - for (var node in nodes) {
|
| - visit(node);
|
| -
|
| - // Write the trailing comma.
|
| - if (node != nodes.last) {
|
| - token(node.endToken.next);
|
| - rule.beforeArgument(split());
|
| - }
|
| - }
|
| -
|
| - token(rightBracket);
|
| -
|
| - builder.unnest();
|
| - builder.endSpan();
|
| - builder.endRule();
|
| - }
|
| -
|
| - /// Visit the given function [parameters] followed by its [body], printing a
|
| - /// space before it if it's not empty.
|
| - ///
|
| - /// If [afterParameters] is provided, it is invoked between the parameters
|
| - /// and body. (It's used for constructor initialization lists.)
|
| - void _visitBody(FormalParameterList parameters, FunctionBody body,
|
| - [afterParameters()]) {
|
| - // If the body is "=>", add an extra level of indentation around the
|
| - // parameters and a rule that spans the parameters and the "=>". This
|
| - // ensures that if the parameters wrap, they wrap more deeply than the "=>"
|
| - // does, as in:
|
| - //
|
| - // someFunction(parameter,
|
| - // parameter, parameter) =>
|
| - // "the body";
|
| - //
|
| - // Also, it ensures that if the parameters wrap, we split at the "=>" too
|
| - // to avoid:
|
| - //
|
| - // someFunction(parameter,
|
| - // parameter) => function(
|
| - // argument);
|
| - //
|
| - // This is confusing because it looks like those two lines are at the same
|
| - // level when they are actually unrelated. Splitting at "=>" forces:
|
| - //
|
| - // someFunction(parameter,
|
| - // parameter) =>
|
| - // function(
|
| - // argument);
|
| - if (body is ExpressionFunctionBody) {
|
| - builder.nestExpression();
|
| -
|
| - // This rule is ended by visitExpressionFunctionBody().
|
| - builder.startLazyRule(new SimpleRule(cost: Cost.arrow));
|
| - }
|
| -
|
| - if (parameters != null) {
|
| - builder.nestExpression();
|
| -
|
| - visit(parameters);
|
| - if (afterParameters != null) afterParameters();
|
| -
|
| - builder.unnest();
|
| - }
|
| -
|
| - visit(body);
|
| -
|
| - if (body is ExpressionFunctionBody) builder.unnest();
|
| - }
|
| -
|
| - /// Visit a list of [nodes] if not null, optionally separated and/or preceded
|
| - /// and followed by the given functions.
|
| - void visitNodes(Iterable<AstNode> nodes, {before(), between(), after()}) {
|
| - if (nodes == null || nodes.isEmpty) return;
|
| -
|
| - if (before != null) before();
|
| -
|
| - visit(nodes.first);
|
| - for (var node in nodes.skip(1)) {
|
| - if (between != null) between();
|
| - visit(node);
|
| - }
|
| -
|
| - if (after != null) after();
|
| - }
|
| -
|
| - /// Visit a comma-separated list of [nodes] if not null.
|
| - void visitCommaSeparatedNodes(Iterable<AstNode> nodes, {between()}) {
|
| - if (nodes == null || nodes.isEmpty) return;
|
| -
|
| - if (between == null) between = space;
|
| -
|
| - var first = true;
|
| - for (var node in nodes) {
|
| - if (!first) between();
|
| - first = false;
|
| -
|
| - visit(node);
|
| -
|
| - // The comma after the node.
|
| - if (node.endToken.next.lexeme == ",") token(node.endToken.next);
|
| - }
|
| - }
|
| -
|
| - /// Visits the collection literal [node] whose body starts with [leftBracket],
|
| - /// ends with [rightBracket] and contains [elements].
|
| - void _visitCollectionLiteral(TypedLiteral node, Token leftBracket,
|
| - Iterable<AstNode> elements, Token rightBracket,
|
| - [int cost]) {
|
| - modifier(node.constKeyword);
|
| - visit(node.typeArguments);
|
| -
|
| - // Don't allow splitting in an empty collection.
|
| - if (elements.isEmpty && rightBracket.precedingComments == null) {
|
| - token(leftBracket);
|
| - token(rightBracket);
|
| -
|
| - // Clear this out in case this empty collection is in an argument list.
|
| - // We don't want this rule to bleed over to some other collection.
|
| - _nextLiteralBodyRule = null;
|
| - return;
|
| - }
|
| -
|
| - // Force all of the surrounding collections to split.
|
| - for (var i = 0; i < _collectionSplits.length; i++) {
|
| - _collectionSplits[i] = true;
|
| - }
|
| -
|
| - // Add this collection to the stack.
|
| - _collectionSplits.add(false);
|
| -
|
| - _startLiteralBody(leftBracket);
|
| -
|
| - // Always use a hard rule to split the elements. The parent chunk of
|
| - // the collection will handle the unsplit case, so this only comes
|
| - // into play when the collection is split.
|
| - var rule = new HardSplitRule();
|
| - builder.startRule(rule);
|
| -
|
| - // If a collection contains a line comment, we assume it's a big complex
|
| - // blob of data with some documented structure. In that case, the user
|
| - // probably broke the elements into lines deliberately, so preserve those.
|
| - var preserveNewlines = _containsLineComments(elements, rightBracket);
|
| -
|
| - for (var element in elements) {
|
| - if (element != elements.first) {
|
| - if (preserveNewlines) {
|
| - if (_endLine(element.beginToken.previous) !=
|
| - _startLine(element.beginToken)) {
|
| - oneOrTwoNewlines();
|
| - } else {
|
| - soloSplit();
|
| - }
|
| - } else {
|
| - builder.blockSplit(space: true);
|
| - }
|
| - }
|
| -
|
| - builder.nestExpression();
|
| - visit(element);
|
| -
|
| - // The comma after the element.
|
| - if (element.endToken.next.lexeme == ",") token(element.endToken.next);
|
| -
|
| - builder.unnest();
|
| - }
|
| -
|
| - builder.endRule();
|
| -
|
| - // If there is a collection inside this one, it forces this one to split.
|
| - var force = _collectionSplits.removeLast();
|
| -
|
| - _endLiteralBody(rightBracket, ignoredRule: rule, forceSplit: force);
|
| - }
|
| -
|
| - /// Returns `true` if the collection withs [elements] delimited by
|
| - /// [rightBracket] contains any line comments.
|
| - ///
|
| - /// This only looks for comments at the element boundary. Comments within an
|
| - /// element are ignored.
|
| - bool _containsLineComments(Iterable<AstNode> elements, Token rightBracket) {
|
| - hasLineCommentBefore(token) {
|
| - var comment = token.precedingComments;
|
| - for (; comment != null; comment = comment.next) {
|
| - if (comment.type == TokenType.SINGLE_LINE_COMMENT) return true;
|
| - }
|
| -
|
| - return false;
|
| - }
|
| -
|
| - // Look before each element.
|
| - for (var element in elements) {
|
| - if (hasLineCommentBefore(element.beginToken)) return true;
|
| - }
|
| -
|
| - // Look before the closing bracket.
|
| - return hasLineCommentBefore(rightBracket);
|
| - }
|
| -
|
| - /// Begins writing a literal body: a collection or block-bodied function
|
| - /// expression.
|
| - ///
|
| - /// Writes the delimiter and then creates the [Rule] that handles splitting
|
| - /// the body.
|
| - void _startLiteralBody(Token leftBracket) {
|
| - token(leftBracket);
|
| -
|
| - // Split the literal. Use the explicitly given rule if we have one.
|
| - // Otherwise, create a new rule.
|
| - var rule = _nextLiteralBodyRule;
|
| - _nextLiteralBodyRule = null;
|
| -
|
| - // Create a rule for whether or not to split the block contents.
|
| - builder.startRule(rule);
|
| -
|
| - // Process the collection contents as a separate set of chunks.
|
| - builder = builder.startBlock();
|
| - }
|
| -
|
| - /// Ends the literal body started by a call to [_startLiteralBody()].
|
| - ///
|
| - /// If [forceSplit] is `true`, forces the body to split. If [ignoredRule] is
|
| - /// given, ignores that rule inside the body when determining if it should
|
| - /// split.
|
| - void _endLiteralBody(Token rightBracket,
|
| - {Rule ignoredRule, bool forceSplit}) {
|
| - if (forceSplit == null) forceSplit = false;
|
| -
|
| - // Put comments before the closing delimiter inside the block.
|
| - var hasLeadingNewline = writePrecedingCommentsAndNewlines(rightBracket);
|
| -
|
| - builder = builder.endBlock(ignoredRule,
|
| - forceSplit: hasLeadingNewline || forceSplit);
|
| -
|
| - builder.endRule();
|
| -
|
| - // Now write the delimiter itself.
|
| - _writeText(rightBracket.lexeme, rightBracket.offset);
|
| - }
|
| -
|
| - /// Visits a "combinator".
|
| - ///
|
| - /// This is a [keyword] followed by a list of [nodes], with specific line
|
| - /// splitting rules. As the name implies, this is used for [HideCombinator]
|
| - /// and [ShowCombinator], but it also used for "with" and "implements"
|
| - /// clauses in class declarations, which are formatted the same way.
|
| - ///
|
| - /// This assumes the current rule is a [CombinatorRule].
|
| - void _visitCombinator(Token keyword, Iterable<AstNode> nodes) {
|
| - // Allow splitting before the keyword.
|
| - var rule = builder.rule as CombinatorRule;
|
| - rule.addCombinator(split());
|
| -
|
| - builder.nestExpression();
|
| - token(keyword);
|
| -
|
| - rule.addName(split());
|
| - visitCommaSeparatedNodes(nodes, between: () => rule.addName(split()));
|
| -
|
| - builder.unnest();
|
| - }
|
| -
|
| - /// Writes the simple statement or semicolon-delimited top-level declaration.
|
| - ///
|
| - /// Handles nesting if a line break occurs in the statement and writes the
|
| - /// terminating semicolon. Invokes [body] which should write statement itself.
|
| - void _simpleStatement(AstNode node, body()) {
|
| - builder.nestExpression();
|
| - body();
|
| -
|
| - // TODO(rnystrom): Can the analyzer move "semicolon" to some shared base
|
| - // type?
|
| - token((node as dynamic).semicolon);
|
| - builder.unnest();
|
| - }
|
| -
|
| - /// Makes [rule] the rule that will be used for the contents of a collection
|
| - /// or function literal body that are about to be visited.
|
| - void setNextLiteralBodyRule(Rule rule) {
|
| - _nextLiteralBodyRule = rule;
|
| - }
|
| -
|
| - /// Writes an bracket-delimited body and handles indenting and starting the
|
| - /// rule used to split the contents.
|
| - ///
|
| - /// If [space] is `true`, then the contents and delimiters will have a space
|
| - /// between then when unsplit.
|
| - void _writeBody(Token leftBracket, Token rightBracket,
|
| - {bool space: false, body()}) {
|
| - token(leftBracket);
|
| -
|
| - // Indent the body.
|
| - builder.indent();
|
| -
|
| - // Split after the bracket.
|
| - builder.startRule();
|
| - builder.blockSplit(space: space, isDouble: false);
|
| -
|
| - body();
|
| -
|
| - token(rightBracket, before: () {
|
| - // Split before the closing bracket character.
|
| - builder.unindent();
|
| - builder.blockSplit(space: space);
|
| - });
|
| -
|
| - builder.endRule();
|
| - }
|
| -
|
| - /// Returns `true` if [node] is immediately contained within an anonymous
|
| - /// [FunctionExpression].
|
| - bool _isInLambda(AstNode node) => node.parent is FunctionExpression &&
|
| - node.parent.parent is! FunctionDeclaration;
|
| -
|
| - /// Writes the string literal [string] to the output.
|
| - ///
|
| - /// Splits multiline strings into separate chunks so that the line splitter
|
| - /// can handle them correctly.
|
| - void _writeStringLiteral(String string, int offset) {
|
| - // Split each line of a multiline string into separate chunks.
|
| - var lines = string.split(_formatter.lineEnding);
|
| -
|
| - _writeText(lines.first, offset);
|
| - offset += lines.first.length;
|
| -
|
| - for (var line in lines.skip(1)) {
|
| - builder.writeWhitespace(Whitespace.newlineFlushLeft);
|
| - offset++;
|
| - _writeText(line, offset);
|
| - offset += line.length;
|
| - }
|
| - }
|
| -
|
| - /// Emit the given [modifier] if it's non null, followed by non-breaking
|
| - /// whitespace.
|
| - void modifier(Token modifier) {
|
| - token(modifier, after: space);
|
| - }
|
| -
|
| - /// Emit a non-breaking space.
|
| - void space() {
|
| - builder.writeWhitespace(Whitespace.space);
|
| - }
|
| -
|
| - /// Emit a single mandatory newline.
|
| - void newline() {
|
| - builder.writeWhitespace(Whitespace.newline);
|
| - }
|
| -
|
| - /// Emit a two mandatory newlines.
|
| - void twoNewlines() {
|
| - builder.writeWhitespace(Whitespace.twoNewlines);
|
| - }
|
| -
|
| - /// Allow either a single space or newline to be emitted before the next
|
| - /// non-whitespace token based on whether a newline exists in the source
|
| - /// between the last token and the next one.
|
| - void spaceOrNewline() {
|
| - builder.writeWhitespace(Whitespace.spaceOrNewline);
|
| - }
|
| -
|
| - /// Allow either a single split or newline to be emitted before the next
|
| - /// non-whitespace token based on whether a newline exists in the source
|
| - /// between the last token and the next one.
|
| - void splitOrNewline() {
|
| - builder.writeWhitespace(Whitespace.splitOrNewline);
|
| - }
|
| -
|
| - /// Allow either one or two newlines to be emitted before the next
|
| - /// non-whitespace token based on whether more than one newline exists in the
|
| - /// source between the last token and the next one.
|
| - void oneOrTwoNewlines() {
|
| - builder.writeWhitespace(Whitespace.oneOrTwoNewlines);
|
| - }
|
| -
|
| - /// Writes a single space split owned by the current rule.
|
| - ///
|
| - /// Returns the chunk the split was applied to.
|
| - Chunk split() => builder.split(space: true);
|
| -
|
| - /// Writes a zero-space split owned by the current rule.
|
| - ///
|
| - /// Returns the chunk the split was applied to.
|
| - Chunk zeroSplit() => builder.split();
|
| -
|
| - /// Writes a single space split with its own rule.
|
| - void soloSplit([int cost]) {
|
| - builder.startRule(new SimpleRule(cost: cost));
|
| - split();
|
| - builder.endRule();
|
| - }
|
| -
|
| - /// Writes a zero-space split with its own rule.
|
| - void soloZeroSplit() {
|
| - builder.startRule();
|
| - builder.split();
|
| - builder.endRule();
|
| - }
|
| -
|
| - /// Emit [token], along with any comments and formatted whitespace that comes
|
| - /// before it.
|
| - ///
|
| - /// Does nothing if [token] is `null`. If [before] is given, it will be
|
| - /// executed before the token is outout. Likewise, [after] will be called
|
| - /// after the token is output.
|
| - void token(Token token, {before(), after()}) {
|
| - if (token == null) return;
|
| -
|
| - writePrecedingCommentsAndNewlines(token);
|
| -
|
| - if (before != null) before();
|
| -
|
| - _writeText(token.lexeme, token.offset);
|
| -
|
| - if (after != null) after();
|
| - }
|
| -
|
| - /// Writes all formatted whitespace and comments that appear before [token].
|
| - bool writePrecedingCommentsAndNewlines(Token token) {
|
| - var comment = token.precedingComments;
|
| -
|
| - // For performance, avoid calculating newlines between tokens unless
|
| - // actually needed.
|
| - if (comment == null) {
|
| - if (builder.needsToPreserveNewlines) {
|
| - builder.preserveNewlines(_startLine(token) - _endLine(token.previous));
|
| - }
|
| -
|
| - return false;
|
| - }
|
| -
|
| - var previousLine = _endLine(token.previous);
|
| -
|
| - // Corner case! The analyzer includes the "\n" in the script tag's lexeme,
|
| - // which means it appears to be one line later than it is. That causes a
|
| - // comment following it to appear to be on the same line. Fix that here by
|
| - // correcting the script tag's line.
|
| - if (token.previous.type == TokenType.SCRIPT_TAG) previousLine--;
|
| -
|
| - var tokenLine = _startLine(token);
|
| -
|
| - var comments = [];
|
| - while (comment != null) {
|
| - var commentLine = _startLine(comment);
|
| -
|
| - // Don't preserve newlines at the top of the file.
|
| - if (comment == token.precedingComments &&
|
| - token.previous.type == TokenType.EOF) {
|
| - previousLine = commentLine;
|
| - }
|
| -
|
| - var text = comment.toString().trim();
|
| - var linesBefore = commentLine - previousLine;
|
| - var flushLeft = _startColumn(comment) == 1;
|
| -
|
| - if (text.startsWith("///") && !text.startsWith("////")) {
|
| - // Line doc comments are always indented even if they were flush left.
|
| - flushLeft = false;
|
| -
|
| - // Always add a blank line (if possible) before a doc comment block.
|
| - if (comment == token.precedingComments) linesBefore = 2;
|
| - }
|
| -
|
| - var sourceComment = new SourceComment(text, linesBefore,
|
| - isLineComment: comment.type == TokenType.SINGLE_LINE_COMMENT,
|
| - flushLeft: flushLeft);
|
| -
|
| - // If this comment contains either of the selection endpoints, mark them
|
| - // in the comment.
|
| - var start = _getSelectionStartWithin(comment.offset, comment.length);
|
| - if (start != null) sourceComment.startSelection(start);
|
| -
|
| - var end = _getSelectionEndWithin(comment.offset, comment.length);
|
| - if (end != null) sourceComment.endSelection(end);
|
| -
|
| - comments.add(sourceComment);
|
| -
|
| - previousLine = _endLine(comment);
|
| - comment = comment.next;
|
| - }
|
| -
|
| - builder.writeComments(comments, tokenLine - previousLine, token.lexeme);
|
| -
|
| - // TODO(rnystrom): This is wrong. Consider:
|
| - //
|
| - // [/* inline comment */
|
| - // // line comment
|
| - // element];
|
| - return comments.first.linesBefore > 0;
|
| - }
|
| -
|
| - /// Write [text] to the current chunk, given that it starts at [offset] in
|
| - /// the original source.
|
| - ///
|
| - /// Also outputs the selection endpoints if needed.
|
| - void _writeText(String text, int offset) {
|
| - builder.write(text);
|
| -
|
| - // If this text contains either of the selection endpoints, mark them in
|
| - // the chunk.
|
| - var start = _getSelectionStartWithin(offset, text.length);
|
| - if (start != null) {
|
| - builder.startSelectionFromEnd(text.length - start);
|
| - }
|
| -
|
| - var end = _getSelectionEndWithin(offset, text.length);
|
| - if (end != null) {
|
| - builder.endSelectionFromEnd(text.length - end);
|
| - }
|
| - }
|
| -
|
| - /// Returns the number of characters past [offset] in the source where the
|
| - /// selection start appears if it appears before `offset + length`.
|
| - ///
|
| - /// Returns `null` if the selection start has already been processed or is
|
| - /// not within that range.
|
| - int _getSelectionStartWithin(int offset, int length) {
|
| - // If there is no selection, do nothing.
|
| - if (_source.selectionStart == null) return null;
|
| -
|
| - // If we've already passed it, don't consider it again.
|
| - if (_passedSelectionStart) return null;
|
| -
|
| - var start = _source.selectionStart - offset;
|
| -
|
| - // If it started in whitespace before this text, push it forward to the
|
| - // beginning of the non-whitespace text.
|
| - if (start < 0) start = 0;
|
| -
|
| - // If we haven't reached it yet, don't consider it.
|
| - if (start >= length) return null;
|
| -
|
| - // We found it.
|
| - _passedSelectionStart = true;
|
| -
|
| - return start;
|
| - }
|
| -
|
| - /// Returns the number of characters past [offset] in the source where the
|
| - /// selection endpoint appears if it appears before `offset + length`.
|
| - ///
|
| - /// Returns `null` if the selection endpoint has already been processed or is
|
| - /// not within that range.
|
| - int _getSelectionEndWithin(int offset, int length) {
|
| - // If there is no selection, do nothing.
|
| - if (_source.selectionLength == null) return null;
|
| -
|
| - // If we've already passed it, don't consider it again.
|
| - if (_passedSelectionEnd) return null;
|
| -
|
| - var end = _findSelectionEnd() - offset;
|
| -
|
| - // If it started in whitespace before this text, push it forward to the
|
| - // beginning of the non-whitespace text.
|
| - if (end < 0) end = 0;
|
| -
|
| - // If we haven't reached it yet, don't consider it.
|
| - if (end > length) return null;
|
| -
|
| - if (end == length && _findSelectionEnd() == _source.selectionStart) {
|
| - return null;
|
| - }
|
| -
|
| - // We found it.
|
| - _passedSelectionEnd = true;
|
| -
|
| - return end;
|
| - }
|
| -
|
| - /// Calculates the character offset in the source text of the end of the
|
| - /// selection.
|
| - ///
|
| - /// Removes any trailing whitespace from the selection.
|
| - int _findSelectionEnd() {
|
| - if (_selectionEnd != null) return _selectionEnd;
|
| -
|
| - _selectionEnd = _source.selectionStart + _source.selectionLength;
|
| -
|
| - // If the selection bumps to the end of the source, pin it there.
|
| - if (_selectionEnd == _source.text.length) return _selectionEnd;
|
| -
|
| - // Trim off any trailing whitespace. We want the selection to "rubberband"
|
| - // around the selected non-whitespace tokens since the whitespace will
|
| - // be munged by the formatter itself.
|
| - while (_selectionEnd > _source.selectionStart) {
|
| - // Stop if we hit anything other than space, tab, newline or carriage
|
| - // return.
|
| - var char = _source.text.codeUnitAt(_selectionEnd - 1);
|
| - if (char != 0x20 && char != 0x09 && char != 0x0a && char != 0x0d) {
|
| - break;
|
| - }
|
| -
|
| - _selectionEnd--;
|
| - }
|
| -
|
| - return _selectionEnd;
|
| - }
|
| -
|
| - /// Gets the 1-based line number that the beginning of [token] lies on.
|
| - int _startLine(Token token) => _lineInfo.getLocation(token.offset).lineNumber;
|
| -
|
| - /// Gets the 1-based line number that the end of [token] lies on.
|
| - int _endLine(Token token) => _lineInfo.getLocation(token.end).lineNumber;
|
| -
|
| - /// Gets the 1-based column number that the beginning of [token] lies on.
|
| - int _startColumn(Token token) =>
|
| - _lineInfo.getLocation(token.offset).columnNumber;
|
| -}
|
|
|