| // 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. |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/standard_ast_factory.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/ast/visitor.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/metadata.dart'; |
| import 'rule/rule.dart'; |
| import 'rule/type_argument.dart'; |
| import 'source_code.dart'; |
| import 'style_fix.dart'; |
| import 'whitespace.dart'; |
| |
| /// Visits every token of the AST and passes all of the relevant bits to a |
| /// [ChunkBuilder]. |
| class SourceVisitor extends ThrowingAstVisitor { |
| /// Returns `true` if [node] is a method invocation that looks like it might |
| /// be a static method or constructor call without a `new` keyword. |
| /// |
| /// With optional `new`, we can no longer reliably identify constructor calls |
| /// statically, but we still don't want to mix named constructor calls into |
| /// a call chain like: |
| /// |
| /// Iterable |
| /// .generate(...) |
| /// .toList(); |
| /// |
| /// And instead prefer: |
| /// |
| /// Iterable.generate(...) |
| /// .toList(); |
| /// |
| /// So we try to identify these calls syntactically. The heuristic we use is |
| /// that a target that's a capitalized name (possibly prefixed by "_") is |
| /// assumed to be a class. |
| /// |
| /// This has the effect of also keeping static method calls with the class, |
| /// but that tends to look pretty good too, and is certainly better than |
| /// splitting up named constructors. |
| static bool looksLikeStaticCall(Expression node) { |
| if (node is! MethodInvocation) return false; |
| if (node.target == null) return false; |
| |
| // A prefixed unnamed constructor call: |
| // |
| // prefix.Foo(); |
| if (node.target is SimpleIdentifier && |
| _looksLikeClassName(node.methodName.name)) { |
| return true; |
| } |
| |
| // A prefixed or unprefixed named constructor call: |
| // |
| // Foo.named(); |
| // prefix.Foo.named(); |
| var target = node.target; |
| if (target is PrefixedIdentifier) target = target.identifier; |
| |
| return target is SimpleIdentifier && _looksLikeClassName(target.name); |
| } |
| |
| /// Whether [name] appears to be a type name. |
| /// |
| /// Type names begin with a capital letter and contain at least one lowercase |
| /// letter (so that we can distinguish them from SCREAMING_CAPS constants). |
| static bool _looksLikeClassName(String name) { |
| // Handle the weird lowercase corelib names. |
| if (name == 'bool') return true; |
| if (name == 'double') return true; |
| if (name == 'int') return true; |
| if (name == 'num') return true; |
| |
| // TODO(rnystrom): A simpler implementation is to test against the regex |
| // "_?[A-Z].*?[a-z]". However, that currently has much worse performance on |
| // AOT: https://github.com/dart-lang/sdk/issues/37785. |
| const underscore = 95; |
| const capitalA = 65; |
| const capitalZ = 90; |
| const lowerA = 97; |
| const lowerZ = 122; |
| |
| var start = 0; |
| var firstChar = name.codeUnitAt(start++); |
| |
| // It can be private. |
| if (firstChar == underscore) { |
| if (name.length == 1) return false; |
| firstChar = name.codeUnitAt(start++); |
| } |
| |
| // It must start with a capital letter. |
| if (firstChar < capitalA || firstChar > capitalZ) return false; |
| |
| // And have at least one lowercase letter in it. Otherwise it could be a |
| // SCREAMING_CAPS constant. |
| for (var i = start; i < name.length; i++) { |
| var char = name.codeUnitAt(i); |
| if (char >= lowerA && char <= lowerZ) return true; |
| } |
| |
| return false; |
| } |
| |
| static bool _isControlFlowElement(AstNode node) => |
| node is IfElement || node is ForElement; |
| |
| /// The builder for the block that is currently being visited. |
| ChunkBuilder builder; |
| |
| final DartFormatter _formatter; |
| |
| /// Cached line info for calculating blank lines. |
| final 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; |
| |
| /// How many levels deep inside a constant context the visitor currently is. |
| int _constNesting = 0; |
| |
| /// Whether we are currently fixing a typedef declaration. |
| /// |
| /// Set to `true` while traversing the parameters of a typedef being converted |
| /// to the new syntax. The new syntax does not allow `int foo()` as a |
| /// parameter declaration, so it needs to be converted to `int Function() foo` |
| /// as part of the fix. |
| bool _insideNewTypedefFix = false; |
| |
| /// 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 = []; |
| |
| /// The stack of current rules for handling parameter metadata. |
| /// |
| /// Each time a parameter (or type parameter) list is begun, a single rule |
| /// for all of the metadata annotations on parameters in that list is pushed |
| /// onto this stack. We reuse this rule for all annotations so that they split |
| /// in unison. |
| final List<MetadataRule> _metadataRules = []; |
| |
| /// The mapping for blocks that are managed by the argument list that contains |
| /// them. |
| /// |
| /// When a block expression, such as a collection literal or a multiline |
| /// string, appears inside an [ArgumentSublist], the argument list provides a |
| /// rule for the body to split to ensure that all blocks split in unison. It |
| /// also tracks the chunk before the argument that determines whether or not |
| /// the block body is indented like an expression or a statement. |
| /// |
| /// Before a block argument is visited, [ArgumentSublist] binds itself to the |
| /// beginning token of each block it controls. When we later visit that |
| /// literal, we use the token to find that association. |
| /// |
| /// This mapping is also used for spread collection literals that appear |
| /// inside control flow elements to ensure that when a "then" collection |
| /// splits, the corresponding "else" one does too. |
| final Map<Token, Rule> _blockRules = {}; |
| final Map<Token, Chunk> _blockPreviousChunks = {}; |
| |
| /// Comments and new lines attached to tokens added here are suppressed |
| /// from the output. |
| final Set<Token> _suppressPrecedingCommentsAndNewLines = {}; |
| |
| /// 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 = 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!); |
| |
| assert(_constNesting == 0, 'Should have exited all const contexts.'); |
| |
| // Finish writing and return the complete result. |
| return builder.end(); |
| } |
| |
| @override |
| void visitAdjacentStrings(AdjacentStrings node) { |
| // We generally want to indent adjacent strings because it can be confusing |
| // otherwise when they appear in a list of expressions, like: |
| // |
| // [ |
| // "one", |
| // "two" |
| // "three", |
| // "four" |
| // ] |
| // |
| // Especially when these stings are longer, it can be hard to tell that |
| // "three" is a continuation of the previous argument. |
| // |
| // However, the indentation is distracting in argument lists that don't |
| // suffer from this ambiguity: |
| // |
| // test( |
| // "A very long test description..." |
| // "this indentation looks bad.", () { ... }); |
| // |
| // To balance these, we omit the indentation when an adjacent string |
| // expression is the only string in an argument list. |
| var shouldNest = true; |
| |
| var parent = node.parent; |
| if (parent is ArgumentList) { |
| shouldNest = false; |
| |
| for (var argument in parent.arguments) { |
| if (argument == node) continue; |
| if (argument is StringLiteral) { |
| shouldNest = true; |
| break; |
| } |
| } |
| } else if (parent is Assertion) { |
| // Treat asserts like argument lists. |
| shouldNest = false; |
| if (parent.condition != node && parent.condition is StringLiteral) { |
| shouldNest = true; |
| } |
| |
| if (parent.message != node && parent.message is StringLiteral) { |
| shouldNest = true; |
| } |
| } else if (parent is VariableDeclaration || |
| parent is AssignmentExpression && |
| parent.rightHandSide == node && |
| parent.parent is ExpressionStatement) { |
| // Don't add extra indentation in a variable initializer or assignment: |
| // |
| // var variable = |
| // "no extra" |
| // "indent"; |
| shouldNest = false; |
| } else if (parent is NamedExpression || parent is ExpressionFunctionBody) { |
| shouldNest = false; |
| } |
| |
| builder.startSpan(); |
| builder.startRule(); |
| if (shouldNest) builder.nestExpression(); |
| visitNodes(node.strings, between: splitOrNewline); |
| if (shouldNest) builder.unnest(); |
| builder.endRule(); |
| builder.endSpan(); |
| } |
| |
| @override |
| void visitAnnotation(Annotation node) { |
| token(node.atSign); |
| visit(node.name); |
| |
| builder.nestExpression(); |
| visit(node.typeArguments); |
| token(node.period); |
| visit(node.constructorName); |
| |
| if (node.arguments != null) { |
| // Metadata annotations are always const contexts. |
| _constNesting++; |
| visitArgumentList(node.arguments!, nestExpression: false); |
| _constNesting--; |
| } |
| |
| builder.unnest(); |
| } |
| |
| /// 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. |
| @override |
| void visitArgumentList(ArgumentList node, {bool nestExpression = true}) { |
| // 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; |
| } |
| |
| // If the argument list has a trailing comma, format it like a collection |
| // literal where each argument goes on its own line, they are indented +2, |
| // and the ")" ends up on its own line. |
| if (hasCommaAfter(node.arguments.last)) { |
| _visitCollectionLiteral( |
| null, node.leftParenthesis, node.arguments, node.rightParenthesis); |
| return; |
| } |
| |
| if (nestExpression) builder.nestExpression(); |
| ArgumentListVisitor(this, node).visit(); |
| if (nestExpression) builder.unnest(); |
| } |
| |
| @override |
| void visitAsExpression(AsExpression node) { |
| builder.startSpan(); |
| builder.nestExpression(); |
| visit(node.expression); |
| soloSplit(); |
| token(node.asOperator); |
| space(); |
| visit(node.type); |
| builder.unnest(); |
| builder.endSpan(); |
| } |
| |
| @override |
| void visitAssertInitializer(AssertInitializer node) { |
| token(node.assertKeyword); |
| |
| var arguments = <Expression>[node.condition]; |
| if (node.message != null) arguments.add(node.message!); |
| |
| // If the argument list has a trailing comma, format it like a collection |
| // literal where each argument goes on its own line, they are indented +2, |
| // and the ")" ends up on its own line. |
| if (hasCommaAfter(arguments.last)) { |
| _visitCollectionLiteral( |
| null, node.leftParenthesis, arguments, node.rightParenthesis); |
| return; |
| } |
| |
| builder.nestExpression(); |
| var visitor = ArgumentListVisitor.forArguments( |
| this, node.leftParenthesis, node.rightParenthesis, arguments); |
| visitor.visit(); |
| builder.unnest(); |
| } |
| |
| @override |
| void visitAssertStatement(AssertStatement node) { |
| _simpleStatement(node, () { |
| token(node.assertKeyword); |
| |
| var arguments = [node.condition]; |
| if (node.message != null) arguments.add(node.message!); |
| |
| // If the argument list has a trailing comma, format it like a collection |
| // literal where each argument goes on its own line, they are indented +2, |
| // and the ")" ends up on its own line. |
| if (hasCommaAfter(arguments.last)) { |
| _visitCollectionLiteral( |
| null, node.leftParenthesis, arguments, node.rightParenthesis); |
| return; |
| } |
| |
| var visitor = ArgumentListVisitor.forArguments( |
| this, node.leftParenthesis, node.rightParenthesis, arguments); |
| visitor.visit(); |
| }); |
| } |
| |
| @override |
| void visitAssignmentExpression(AssignmentExpression node) { |
| builder.nestExpression(); |
| |
| visit(node.leftHandSide); |
| _visitAssignment(node.operator, node.rightHandSide); |
| |
| builder.unnest(); |
| } |
| |
| @override |
| void visitAwaitExpression(AwaitExpression node) { |
| token(node.awaitKeyword); |
| space(); |
| visit(node.expression); |
| } |
| |
| @override |
| void visitBinaryExpression(BinaryExpression node) { |
| builder.startSpan(); |
| |
| // If a binary operator sequence appears immediately after a `=>`, don't |
| // add an extra level of nesting. Instead, let the subsequent operands line |
| // up with the first, as in: |
| // |
| // method() => |
| // argument && |
| // argument && |
| // argument; |
| var isArrowBody = node.parent is ExpressionFunctionBody; |
| if (!isArrowBody) 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; |
| |
| @override |
| void 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(); |
| |
| if (!isArrowBody) builder.unnest(); |
| builder.endSpan(); |
| builder.endRule(); |
| } |
| |
| @override |
| void visitBlock(Block node) { |
| // Treat empty blocks specially. In most cases, they are not allowed to |
| // split. However, an empty block as the then statement of an if with an |
| // else is always split. |
| if (_isEmptyCollection(node.statements, node.rightBracket)) { |
| token(node.leftBracket); |
| |
| // Force a split when used as the then body of an if with an else: |
| // |
| // if (condition) { |
| // } else ... |
| if (node.parent is IfStatement) { |
| var ifStatement = node.parent as IfStatement; |
| if (ifStatement.elseStatement != null && |
| ifStatement.thenStatement == node) { |
| newline(); |
| } |
| } |
| |
| token(node.rightBracket); |
| return; |
| } |
| |
| // If the block is a function body, it may get expression-level indentation, |
| // so handle it specially. Otherwise, just bump the indentation and keep it |
| // in the current block. |
| if (node.parent is BlockFunctionBody) { |
| _startLiteralBody(node.leftBracket); |
| } else { |
| _beginBody(node.leftBracket); |
| } |
| |
| var needsDouble = true; |
| for (var statement in node.statements) { |
| if (needsDouble) { |
| twoNewlines(); |
| } else { |
| oneOrTwoNewlines(); |
| } |
| |
| visit(statement); |
| |
| needsDouble = false; |
| if (statement is FunctionDeclarationStatement) { |
| // Add a blank line after non-empty block functions. |
| var body = statement.functionDeclaration.functionExpression.body; |
| if (body is BlockFunctionBody) { |
| needsDouble = body.block.statements.isNotEmpty; |
| } |
| } |
| } |
| |
| if (node.statements.isNotEmpty) newline(); |
| |
| if (node.parent is BlockFunctionBody) { |
| _endLiteralBody(node.rightBracket, |
| forceSplit: node.statements.isNotEmpty); |
| } else { |
| _endBody(node.rightBracket); |
| } |
| } |
| |
| @override |
| void 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); |
| } |
| |
| @override |
| void visitBooleanLiteral(BooleanLiteral node) { |
| token(node.literal); |
| } |
| |
| @override |
| void visitBreakStatement(BreakStatement node) { |
| _simpleStatement(node, () { |
| token(node.breakKeyword); |
| visit(node.label, before: space); |
| }); |
| } |
| |
| @override |
| void visitCascadeExpression(CascadeExpression node) { |
| // Optimized path if we know the cascade will split. |
| if (node.cascadeSections.length > 1) { |
| _visitSplitCascade(node); |
| return; |
| } |
| |
| // Whether a split in the cascade target expression forces the cascade to |
| // move to the next line. It looks weird to move the cascade down if the |
| // target expression is a collection, so we don't: |
| // |
| // var list = [ |
| // stuff |
| // ] |
| // ..add(more); |
| var splitIfOperandsSplit = |
| node.cascadeSections.length > 1 || _isCollectionLike(node.target); |
| if (splitIfOperandsSplit) { |
| builder.startLazyRule(_allowInlineCascade(node) ? Rule() : Rule.hard()); |
| } |
| |
| visit(node.target); |
| |
| builder.nestExpression(indent: Indent.cascade, now: true); |
| builder.startBlockArgumentNesting(); |
| |
| // If the cascade section shouldn't cause the cascade to split, end the |
| // rule early so it isn't affected by it. |
| if (!splitIfOperandsSplit) { |
| builder.startRule(_allowInlineCascade(node) ? Rule() : Rule.hard()); |
| } |
| |
| zeroSplit(); |
| |
| if (!splitIfOperandsSplit) { |
| builder.endRule(); |
| } |
| |
| visitNodes(node.cascadeSections, between: zeroSplit); |
| |
| if (splitIfOperandsSplit) { |
| builder.endRule(); |
| } |
| |
| builder.endBlockArgumentNesting(); |
| builder.unnest(); |
| } |
| |
| /// Format the cascade using a nested block instead of a single inline |
| /// expression. |
| /// |
| /// If the cascade has multiple sections, we know each section will be on its |
| /// own line and we know there will be at least one trailing section following |
| /// a preceding one. That let's us treat all of the earlier sections as a |
| /// separate block like we do with collections and functions, instead of a |
| /// monolithic expression. Using a block in turn makes big cascades much |
| /// faster to format (like 10x) since the block formatting is memoized and |
| /// each cascade section in it is formatted independently. |
| /// |
| /// The tricky part is that block formatting assumes the entire line will be |
| /// part of the block. This is not true of the last section in a cascade, |
| /// which may have other trailing code, like the `;` here: |
| /// |
| /// var x = someLeadingExpression |
| /// ..firstCascade() |
| /// ..secondCascade() |
| /// ..thirdCascade() |
| /// ..fourthCascade(); |
| /// |
| /// To handle that, we don't put the last section in the block and instead |
| /// format it with the surrounding expression. So, from the formatter's |
| /// view, the above casade is formatted like: |
| /// |
| /// var x = someLeadingExpression |
| /// [ begin block ] |
| /// ..firstCascade() |
| /// ..secondCascade() |
| /// ..thirdCascade() |
| /// [ end block ] |
| /// ..fourthCascade(); |
| /// |
| /// This somewhere between clever and hacky, but it works and allows cascades |
| /// of essentially unbounded length to be formatted quickly. |
| void _visitSplitCascade(CascadeExpression node) { |
| // Rule to split before the first `..`. |
| builder.startLazyRule(Rule.hard()); |
| visit(node.target); |
| |
| builder.nestExpression(indent: Indent.cascade, now: true); |
| builder.startBlockArgumentNesting(); |
| |
| zeroSplit(); |
| |
| // If there are comments before the first section, keep them outside of the |
| // block. That way code like: |
| // |
| // receiver // comment |
| // ..cascade(); |
| // |
| // Keeps the comment on the first line. |
| var firstCommentToken = node.cascadeSections.first.beginToken; |
| writePrecedingCommentsAndNewlines(firstCommentToken); |
| _suppressPrecedingCommentsAndNewLines.add(firstCommentToken); |
| |
| // Process the inner cascade sections as a separate block. This way the |
| // entire cascade expression isn't line split as a single monolithic unit, |
| // which is very slow. |
| builder = builder.startBlock(null, indent: false); |
| |
| for (var i = 0; i < node.cascadeSections.length - 1; i++) { |
| if (i > 0) newline(); |
| visit(node.cascadeSections[i]); |
| } |
| |
| // Put comments before the last section inside the block. |
| var lastCommentToken = node.cascadeSections.last.beginToken; |
| writePrecedingCommentsAndNewlines(lastCommentToken); |
| _suppressPrecedingCommentsAndNewLines.add(lastCommentToken); |
| |
| builder = builder.endBlock(null, forceSplit: true); |
| |
| // The last section is outside of the block. |
| visit(node.cascadeSections.last); |
| |
| builder.endRule(); |
| builder.endBlockArgumentNesting(); |
| builder.unnest(); |
| } |
| |
| /// Whether [expression] is a collection literal, or a call with a trailing |
| /// comma in an argument list. |
| /// |
| /// In that case, when the expression is a target of a cascade, we don't |
| /// force a split before the ".." as eagerly to avoid ugly results like: |
| /// |
| /// [ |
| /// 1, |
| /// 2, |
| /// ]..addAll(numbers); |
| bool _isCollectionLike(Expression expression) { |
| if (expression is ListLiteral) return false; |
| if (expression is SetOrMapLiteral) return false; |
| |
| // If the target is a call with a trailing comma in the argument list, |
| // treat it like a collection literal. |
| ArgumentList? arguments; |
| if (expression is InvocationExpression) { |
| arguments = expression.argumentList; |
| } else if (expression is InstanceCreationExpression) { |
| arguments = expression.argumentList; |
| } |
| |
| // TODO(rnystrom): Do we want to allow an invocation where the last |
| // argument is a collection literal? Like: |
| // |
| // foo(argument, [ |
| // element |
| // ])..cascade(); |
| |
| return arguments == null || |
| arguments.arguments.isEmpty || |
| !hasCommaAfter(arguments.arguments.last); |
| } |
| |
| /// Whether a cascade should be allowed to be inline as opposed to moving the |
| /// section to the next line. |
| bool _allowInlineCascade(CascadeExpression node) { |
| // Cascades with multiple sections are handled elsewhere and are never |
| // inline. |
| assert(node.cascadeSections.length == 1); |
| |
| // If the receiver is an expression that makes the cascade's very low |
| // precedence confusing, force it to split. For example: |
| // |
| // a ? b : c..d(); |
| // |
| // Here, the cascade is applied to the result of the conditional, not "c". |
| if (node.target is ConditionalExpression) return false; |
| if (node.target is BinaryExpression) return false; |
| if (node.target is PrefixExpression) return false; |
| if (node.target is AwaitExpression) return false; |
| |
| return true; |
| } |
| |
| @override |
| void 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); |
| } |
| |
| @override |
| void visitClassDeclaration(ClassDeclaration node) { |
| visitMetadata(node.metadata); |
| |
| builder.nestExpression(); |
| modifier(node.abstractKeyword); |
| token(node.classKeyword); |
| space(); |
| visit(node.name); |
| visit(node.typeParameters); |
| visit(node.extendsClause); |
| |
| builder.startRule(CombinatorRule()); |
| visit(node.withClause); |
| visit(node.implementsClause); |
| builder.endRule(); |
| |
| visit(node.nativeClause, before: space); |
| space(); |
| |
| builder.unnest(); |
| _beginBody(node.leftBracket); |
| _visitMembers(node.members); |
| _endBody(node.rightBracket); |
| } |
| |
| @override |
| void visitClassTypeAlias(ClassTypeAlias node) { |
| visitMetadata(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(CombinatorRule()); |
| visit(node.withClause); |
| visit(node.implementsClause); |
| builder.endRule(); |
| }); |
| } |
| |
| @override |
| void visitComment(Comment node) => null; |
| |
| @override |
| void visitCommentReference(CommentReference node) => null; |
| |
| @override |
| void visitCompilationUnit(CompilationUnit node) { |
| visit(node.scriptTag); |
| |
| // Put a blank line between the library tag and the other directives. |
| Iterable<Directive> directives = node.directives; |
| if (directives.isNotEmpty && directives.first is LibraryDirective) { |
| visit(directives.first); |
| twoNewlines(); |
| |
| directives = directives.skip(1); |
| } |
| |
| visitNodes(directives, between: oneOrTwoNewlines); |
| |
| var needsDouble = true; |
| for (var declaration in node.declarations) { |
| var hasClassBody = declaration is ClassDeclaration || |
| declaration is ExtensionDeclaration; |
| |
| // Add a blank line before declarations with class-like bodies. |
| if (hasClassBody) 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 (hasClassBody) { |
| // Add a blank line after declarations with class-like bodies. |
| needsDouble = true; |
| } else if (declaration is FunctionDeclaration) { |
| // Add a blank line after non-empty block functions. |
| var body = declaration.functionExpression.body; |
| if (body is BlockFunctionBody) { |
| needsDouble = body.block.statements.isNotEmpty; |
| } |
| } |
| } |
| } |
| |
| @override |
| void visitConditionalExpression(ConditionalExpression node) { |
| // TODO(rnystrom): Consider revisiting whether users prefer this after 2.13. |
| /* |
| // Flatten else-if style chained conditionals. |
| var shouldNest = node.parent is! ConditionalExpression || |
| (node.parent as ConditionalExpression).elseExpression != node; |
| if (shouldNest) builder.nestExpression(); |
| */ |
| builder.nestExpression(); |
| |
| // Start lazily so we don't force the operator to split if a line comment |
| // appears before the first operand. If we split after one clause in a |
| // conditional, always split after both. |
| builder.startLazyRule(); |
| visit(node.condition); |
| |
| // Push any block arguments all the way past the leading "?" and ":". |
| builder.nestExpression(indent: Indent.block, now: true); |
| builder.startBlockArgumentNesting(); |
| builder.unnest(); |
| |
| builder.startSpan(); |
| |
| split(); |
| token(node.question); |
| space(); |
| builder.nestExpression(); |
| visit(node.thenExpression); |
| builder.unnest(); |
| |
| split(); |
| token(node.colon); |
| space(); |
| visit(node.elseExpression); |
| |
| // If conditional expressions are directly nested, force them all to split. |
| // This line here forces the child, which implicitly forces the surrounding |
| // parent rules to split too. |
| if (node.parent is ConditionalExpression) builder.forceRules(); |
| |
| builder.endRule(); |
| builder.endSpan(); |
| builder.endBlockArgumentNesting(); |
| |
| // TODO(rnystrom): Consider revisiting whether users prefer this after 2.13. |
| /* |
| if (shouldNest) builder.unnest(); |
| */ |
| builder.unnest(); |
| } |
| |
| @override |
| void visitConfiguration(Configuration node) { |
| token(node.ifKeyword); |
| space(); |
| token(node.leftParenthesis); |
| visit(node.name); |
| |
| if (node.equalToken != null) { |
| builder.nestExpression(); |
| space(); |
| token(node.equalToken); |
| soloSplit(); |
| visit(node.value); |
| builder.unnest(); |
| } |
| |
| token(node.rightParenthesis); |
| space(); |
| visit(node.uri); |
| } |
| |
| @override |
| void visitConstructorDeclaration(ConstructorDeclaration node) { |
| visitMetadata(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. |
| if (node.initializers.isNotEmpty) builder.startRule(); |
| |
| // If the redirecting constructor happens to wrap, we want to make sure |
| // the parameter list gets more deeply indented. |
| if (node.redirectedConstructor != null) builder.nestExpression(); |
| |
| _visitBody(null, node.parameters, node.body, () { |
| // Check for redirects or initializer lists. |
| if (node.redirectedConstructor != null) { |
| _visitConstructorRedirects(node); |
| builder.unnest(); |
| } else if (node.initializers.isNotEmpty) { |
| _visitConstructorInitializers(node); |
| |
| // End the rule for ":" after all of the initializers. |
| builder.endRule(); |
| } |
| }); |
| } |
| |
| void _visitConstructorRedirects(ConstructorDeclaration node) { |
| token(node.separator /* = */, before: space); |
| soloSplit(); |
| visitCommaSeparatedNodes(node.initializers); |
| visit(node.redirectedConstructor); |
| } |
| |
| void _visitConstructorInitializers(ConstructorDeclaration node) { |
| var hasTrailingComma = node.parameters.parameters.isNotEmpty && |
| hasCommaAfter(node.parameters.parameters.last); |
| |
| if (hasTrailingComma) { |
| // Since the ")", "])", or "})" on the preceding line doesn't take up |
| // much space, it looks weird to move the ":" onto it's own line. Instead, |
| // keep it and the first initializer on the current line but add enough |
| // space before it to line it up with any subsequent initializers. |
| // |
| // Foo( |
| // parameter, |
| // ) : field = value, |
| // super(); |
| space(); |
| if (node.initializers.length > 1) { |
| var padding = ' '; |
| if (node.parameters.parameters.last.isNamed || |
| node.parameters.parameters.last.isOptionalPositional) { |
| padding = ' '; |
| } |
| _writeText(padding, node.separator!.offset); |
| } |
| |
| // ":". |
| token(node.separator); |
| space(); |
| |
| builder.indent(6); |
| } else { |
| // Shift the itself ":" forward. |
| builder.indent(Indent.constructorInitializer); |
| |
| // If the parameters or initializers split, put the ":" on its own line. |
| split(); |
| |
| // ":". |
| token(node.separator); |
| space(); |
| |
| // Try to line up the initializers with the first one that follows the ":": |
| // |
| // Foo(notTrailing) |
| // : initializer = value, |
| // super(); // +2 from previous line. |
| // |
| // Foo( |
| // trailing, |
| // ) : initializer = value, |
| // super(); // +4 from previous line. |
| // |
| // This doesn't work if there is a trailing comma in an optional parameter, |
| // but we don't want to do a weird +5 alignment: |
| // |
| // Foo({ |
| // trailing, |
| // }) : initializer = value, |
| // super(); // Doesn't quite line up. :( |
| builder.indent(2); |
| } |
| |
| 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(); |
| if (!hasTrailingComma) builder.unindent(); |
| } |
| |
| @override |
| void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
| builder.nestExpression(); |
| |
| token(node.thisKeyword); |
| token(node.period); |
| visit(node.fieldName); |
| |
| _visitAssignment(node.equals, node.expression); |
| |
| builder.unnest(); |
| } |
| |
| @override |
| void visitConstructorName(ConstructorName node) { |
| visit(node.type); |
| token(node.period); |
| visit(node.name); |
| } |
| |
| @override |
| void visitContinueStatement(ContinueStatement node) { |
| _simpleStatement(node, () { |
| token(node.continueKeyword); |
| visit(node.label, before: space); |
| }); |
| } |
| |
| @override |
| void visitDeclaredIdentifier(DeclaredIdentifier node) { |
| modifier(node.keyword); |
| visit(node.type, after: space); |
| visit(node.identifier); |
| } |
| |
| @override |
| void visitDefaultFormalParameter(DefaultFormalParameter node) { |
| visit(node.parameter); |
| if (node.separator != null) { |
| builder.startSpan(); |
| builder.nestExpression(); |
| |
| if (_formatter.fixes.contains(StyleFix.namedDefaultSeparator)) { |
| // Change the separator to "=". |
| space(); |
| writePrecedingCommentsAndNewlines(node.separator!); |
| _writeText('=', node.separator!.offset); |
| } else { |
| // The '=' separator is preceded by a space, ":" is not. |
| if (node.separator!.type == TokenType.EQ) space(); |
| token(node.separator); |
| } |
| |
| soloSplit(_assignmentCost(node.defaultValue!)); |
| visit(node.defaultValue); |
| |
| builder.unnest(); |
| builder.endSpan(); |
| } |
| } |
| |
| @override |
| void visitDoStatement(DoStatement node) { |
| builder.nestExpression(); |
| token(node.doKeyword); |
| space(); |
| builder.unnest(now: false); |
| visit(node.body); |
| |
| builder.nestExpression(); |
| space(); |
| token(node.whileKeyword); |
| space(); |
| token(node.leftParenthesis); |
| soloZeroSplit(); |
| visit(node.condition); |
| token(node.rightParenthesis); |
| token(node.semicolon); |
| builder.unnest(); |
| } |
| |
| @override |
| void visitDottedName(DottedName node) { |
| for (var component in node.components) { |
| // Write the preceding ".". |
| if (component != node.components.first) { |
| token(component.beginToken.previous); |
| } |
| |
| visit(component); |
| } |
| } |
| |
| @override |
| void visitDoubleLiteral(DoubleLiteral node) { |
| token(node.literal); |
| } |
| |
| @override |
| void visitEmptyFunctionBody(EmptyFunctionBody node) { |
| token(node.semicolon); |
| } |
| |
| @override |
| void visitEmptyStatement(EmptyStatement node) { |
| token(node.semicolon); |
| } |
| |
| @override |
| void visitEnumConstantDeclaration(EnumConstantDeclaration node) { |
| visitMetadata(node.metadata); |
| visit(node.name); |
| } |
| |
| @override |
| void visitEnumDeclaration(EnumDeclaration node) { |
| visitMetadata(node.metadata); |
| |
| token(node.enumKeyword); |
| space(); |
| visit(node.name); |
| space(); |
| |
| _beginBody(node.leftBracket, space: true); |
| visitCommaSeparatedNodes(node.constants, between: splitOrTwoNewlines); |
| |
| // If there is a trailing comma, always force the constants to split. |
| if (hasCommaAfter(node.constants.last)) { |
| builder.forceRules(); |
| } |
| |
| _endBody(node.rightBracket, space: true); |
| } |
| |
| @override |
| void visitExportDirective(ExportDirective node) { |
| _visitDirectiveMetadata(node); |
| _simpleStatement(node, () { |
| token(node.keyword); |
| space(); |
| visit(node.uri); |
| |
| _visitConfigurations(node.configurations); |
| |
| builder.startRule(CombinatorRule()); |
| visitNodes(node.combinators); |
| builder.endRule(); |
| }); |
| } |
| |
| @override |
| void 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(); |
| |
| // If the body is a binary operator expression, then we want to force the |
| // split at `=>` if the operators split. See visitBinaryExpression(). |
| if (node.expression is! BinaryExpression) builder.endRule(); |
| |
| if (_isInLambda(node)) builder.endSpan(); |
| |
| // If this function invocation appears in an argument list with trailing |
| // comma, don't add extra nesting to preserve normal indentation. |
| var isArgWithTrailingComma = false; |
| var parent = node.parent; |
| if (parent is FunctionExpression) { |
| isArgWithTrailingComma = _isTrailingCommaArgument(parent); |
| } |
| |
| if (!isArgWithTrailingComma) builder.startBlockArgumentNesting(); |
| builder.startSpan(); |
| visit(node.expression); |
| builder.endSpan(); |
| if (!isArgWithTrailingComma) builder.endBlockArgumentNesting(); |
| |
| if (node.expression is BinaryExpression) builder.endRule(); |
| |
| token(node.semicolon); |
| } |
| |
| /// Synthesize a token with [type] to replace the given [operator]. |
| /// |
| /// Offset, comments, and previous/next links are all preserved. |
| static Token _synthesizeToken(TokenType type, Token operator) => |
| Token(type, operator.offset, operator.precedingComments) |
| ..previous = operator.previous |
| ..next = operator.next; |
| |
| static Expression _realTargetOf(Expression expression) { |
| if (expression is PropertyAccess) { |
| return expression.realTarget; |
| } else if (expression is MethodInvocation) { |
| return expression.realTarget!; |
| } else if (expression is IndexExpression) { |
| return expression.realTarget; |
| } |
| throw UnimplementedError('Unhandled ${expression.runtimeType}' |
| '($expression)'); |
| } |
| |
| /// Recursively insert [cascadeTarget] (the LHS of the cascade) into the |
| /// LHS of the assignment expression that used to be the cascade's RHS. |
| static Expression _insertCascadeTargetIntoExpression( |
| Expression expression, Expression cascadeTarget) { |
| // Base case: We've recursed as deep as possible. |
| if (expression == cascadeTarget) return cascadeTarget; |
| |
| // Otherwise, copy `expression` and recurse into its LHS. |
| var expressionTarget = _realTargetOf(expression); |
| if (expression is PropertyAccess) { |
| return astFactory.propertyAccess( |
| _insertCascadeTargetIntoExpression(expressionTarget, cascadeTarget), |
| // If we've reached the end, replace the `..` operator with `.` |
| expressionTarget == cascadeTarget |
| ? _synthesizeToken(TokenType.PERIOD, expression.operator) |
| : expression.operator, |
| expression.propertyName); |
| } else if (expression is MethodInvocation) { |
| return astFactory.methodInvocation( |
| _insertCascadeTargetIntoExpression(expressionTarget, cascadeTarget), |
| // If we've reached the end, replace the `..` operator with `.` |
| expressionTarget == cascadeTarget |
| ? _synthesizeToken(TokenType.PERIOD, expression.operator!) |
| : expression.operator, |
| expression.methodName, |
| expression.typeArguments, |
| expression.argumentList); |
| } else if (expression is IndexExpression) { |
| var question = expression.question; |
| |
| // A null-aware cascade treats the `?` in `?..` as part of the token, but |
| // for a non-cascade index, it is a separate `?` token. |
| if (expression.period?.type == TokenType.QUESTION_PERIOD_PERIOD) { |
| question = _synthesizeToken(TokenType.QUESTION, expression.period!); |
| } |
| |
| return astFactory.indexExpressionForTarget2( |
| target: _insertCascadeTargetIntoExpression( |
| expressionTarget, cascadeTarget), |
| question: question, |
| leftBracket: expression.leftBracket, |
| index: expression.index, |
| rightBracket: expression.rightBracket); |
| } |
| throw UnimplementedError('Unhandled ${expression.runtimeType}' |
| '($expression)'); |
| } |
| |
| /// Parenthesize the target of the given statement's expression (assumed to |
| /// be a CascadeExpression) before removing the cascade. |
| void _fixCascadeByParenthesizingTarget(ExpressionStatement statement) { |
| var cascade = statement.expression as CascadeExpression; |
| assert(cascade.cascadeSections.length == 1); |
| |
| // Write any leading comments and whitespace immediately, as they should |
| // precede the new opening parenthesis, but then prevent them from being |
| // written again after the parenthesis. |
| writePrecedingCommentsAndNewlines(cascade.target.beginToken); |
| _suppressPrecedingCommentsAndNewLines.add(cascade.target.beginToken); |
| |
| var newTarget = astFactory.parenthesizedExpression( |
| Token(TokenType.OPEN_PAREN, 0) |
| ..previous = statement.beginToken.previous |
| ..next = cascade.target.beginToken, |
| cascade.target, |
| Token(TokenType.CLOSE_PAREN, 0) |
| ..previous = cascade.target.endToken |
| ..next = statement.semicolon); |
| |
| // Finally, we can revisit a clone of this ExpressionStatement to actually |
| // remove the cascade. |
| visit(astFactory.expressionStatement( |
| astFactory.cascadeExpression(newTarget, cascade.cascadeSections), |
| statement.semicolon)); |
| } |
| |
| void _removeCascade(ExpressionStatement statement) { |
| var cascade = statement.expression as CascadeExpression; |
| var subexpression = cascade.cascadeSections.single; |
| builder.nestExpression(); |
| |
| if (subexpression is AssignmentExpression) { |
| // CascadeExpression("leftHandSide", "..", |
| // AssignmentExpression("target", "=", "rightHandSide")) |
| // |
| // transforms to |
| // |
| // AssignmentExpression( |
| // PropertyAccess("leftHandSide", ".", "target"), |
| // "=", |
| // "rightHandSide") |
| visit(astFactory.assignmentExpression( |
| _insertCascadeTargetIntoExpression( |
| subexpression.leftHandSide, cascade.target), |
| subexpression.operator, |
| subexpression.rightHandSide)); |
| } else if (subexpression is MethodInvocation || |
| subexpression is PropertyAccess) { |
| // CascadeExpression("leftHandSide", "..", |
| // MethodInvocation("target", ".", "methodName", ...)) |
| // |
| // transforms to |
| // |
| // MethodInvocation( |
| // PropertyAccess("leftHandSide", ".", "target"), |
| // ".", |
| // "methodName", ...) |
| // |
| // And similarly for PropertyAccess expressions. |
| visit(_insertCascadeTargetIntoExpression(subexpression, cascade.target)); |
| } else { |
| throw UnsupportedError( |
| '--fix-single-cascade-statements: subexpression of cascade ' |
| '"$cascade" has unsupported type ${subexpression.runtimeType}.'); |
| } |
| |
| token(statement.semicolon); |
| builder.unnest(); |
| } |
| |
| /// Remove any unnecessary single cascade from the given expression statement, |
| /// which is assumed to contain a [CascadeExpression]. |
| /// |
| /// Returns true after applying the fix, which involves visiting the nested |
| /// expression. Callers must visit the nested expression themselves |
| /// if-and-only-if this method returns false. |
| bool _fixSingleCascadeStatement(ExpressionStatement statement) { |
| var cascade = statement.expression as CascadeExpression; |
| if (cascade.cascadeSections.length != 1) return false; |
| |
| var target = cascade.target; |
| if (target is AsExpression || |
| target is AwaitExpression || |
| target is BinaryExpression || |
| target is ConditionalExpression || |
| target is IsExpression || |
| target is PostfixExpression || |
| target is PrefixExpression) { |
| // In these cases, the cascade target needs to be parenthesized before |
| // removing the cascade, otherwise the semantics will change. |
| _fixCascadeByParenthesizingTarget(statement); |
| return true; |
| } else if (target is BooleanLiteral || |
| target is FunctionExpression || |
| target is IndexExpression || |
| target is InstanceCreationExpression || |
| target is IntegerLiteral || |
| target is ListLiteral || |
| target is NullLiteral || |
| target is MethodInvocation || |
| target is ParenthesizedExpression || |
| target is PrefixedIdentifier || |
| target is PropertyAccess || |
| target is SimpleIdentifier || |
| target is StringLiteral || |
| target is ThisExpression) { |
| // OK to simply remove the cascade. |
| _removeCascade(statement); |
| return true; |
| } else { |
| // If we get here, some new syntax was added to the language that the fix |
| // does not yet support. Leave it as is. |
| return false; |
| } |
| } |
| |
| @override |
| void visitExpressionStatement(ExpressionStatement node) { |
| if (_formatter.fixes.contains(StyleFix.singleCascadeStatements) && |
| node.expression is CascadeExpression && |
| _fixSingleCascadeStatement(node)) { |
| return; |
| } |
| |
| _simpleStatement(node, () { |
| visit(node.expression); |
| }); |
| } |
| |
| @override |
| void visitExtendsClause(ExtendsClause node) { |
| soloSplit(); |
| token(node.extendsKeyword); |
| space(); |
| visit(node.superclass); |
| } |
| |
| @override |
| void visitExtensionDeclaration(ExtensionDeclaration node) { |
| visitMetadata(node.metadata); |
| |
| builder.nestExpression(); |
| token(node.extensionKeyword); |
| |
| // Don't put a space after `extension` if the extension is unnamed. That |
| // way, generic unnamed extensions format like `extension<T> on ...`. |
| if (node.name != null) { |
| space(); |
| visit(node.name); |
| } |
| |
| visit(node.typeParameters); |
| soloSplit(); |
| token(node.onKeyword); |
| space(); |
| visit(node.extendedType); |
| space(); |
| builder.unnest(); |
| |
| _beginBody(node.leftBracket); |
| _visitMembers(node.members); |
| _endBody(node.rightBracket); |
| } |
| |
| @override |
| void visitFieldDeclaration(FieldDeclaration node) { |
| visitMetadata(node.metadata); |
| |
| _simpleStatement(node, () { |
| modifier(node.externalKeyword); |
| modifier(node.staticKeyword); |
| modifier(node.abstractKeyword); |
| modifier(node.covariantKeyword); |
| visit(node.fields); |
| }); |
| } |
| |
| @override |
| void visitFieldFormalParameter(FieldFormalParameter node) { |
| visitParameterMetadata(node.metadata, () { |
| _beginFormalParameter(node); |
| token(node.keyword, after: space); |
| visit(node.type, after: split); |
| token(node.thisKeyword); |
| token(node.period); |
| visit(node.identifier); |
| visit(node.parameters); |
| token(node.question); |
| _endFormalParameter(node); |
| }); |
| } |
| |
| @override |
| void visitFormalParameterList(FormalParameterList node, |
| {bool nestExpression = true}) { |
| // 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; |
| } |
| |
| // If the parameter list has a trailing comma, format it like a collection |
| // literal where each parameter goes on its own line, they are indented +2, |
| // and the ")" ends up on its own line. |
| if (hasCommaAfter(node.parameters.last)) { |
| _visitTrailingCommaParameterList(node); |
| return; |
| } |
| |
| var requiredParams = node.parameters |
| .where((param) => param is! DefaultFormalParameter) |
| .toList(); |
| var optionalParams = |
| node.parameters.whereType<DefaultFormalParameter>().toList(); |
| |
| if (nestExpression) builder.nestExpression(); |
| token(node.leftParenthesis); |
| |
| _metadataRules.add(MetadataRule()); |
| |
| var rule; |
| if (requiredParams.isNotEmpty) { |
| rule = PositionalRule(null, 0, 0); |
| _metadataRules.last.bindPositionalRule(rule); |
| |
| 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); |
| _writeCommaAfter(param); |
| |
| if (param != requiredParams.last) rule.beforeArgument(split()); |
| } |
| |
| builder.endSpan(); |
| builder.endRule(); |
| } |
| |
| if (optionalParams.isNotEmpty) { |
| var namedRule = NamedRule(null, 0, 0); |
| if (rule != null) rule.setNamedArgsRule(namedRule); |
| |
| _metadataRules.last.bindNamedRule(namedRule); |
| |
| builder.startRule(namedRule); |
| |
| // Make sure multi-line default values are indented. |
| builder.startBlockArgumentNesting(); |
| |
| namedRule.beforeArgument(builder.split(space: requiredParams.isNotEmpty)); |
| |
| // "[" or "{" for optional parameters. |
| token(node.leftDelimiter); |
| |
| for (var param in optionalParams) { |
| visit(param); |
| _writeCommaAfter(param); |
| |
| if (param != optionalParams.last) namedRule.beforeArgument(split()); |
| } |
| |
| builder.endBlockArgumentNesting(); |
| builder.endRule(); |
| |
| // "]" or "}" for optional parameters. |
| token(node.rightDelimiter); |
| } |
| |
| _metadataRules.removeLast(); |
| |
| token(node.rightParenthesis); |
| if (nestExpression) builder.unnest(); |
| } |
| |
| @override |
| void visitForElement(ForElement node) { |
| // Treat a spread of a collection literal like a block in a for statement |
| // and don't split after the for parts. |
| var isSpreadBody = _isSpreadCollection(node.body); |
| |
| builder.nestExpression(); |
| token(node.awaitKeyword, after: space); |
| token(node.forKeyword); |
| space(); |
| token(node.leftParenthesis); |
| |
| // Start the body rule so that if the parts split, the body does too. |
| builder.startRule(); |
| |
| // The rule for the parts. |
| builder.startRule(); |
| visit(node.forLoopParts); |
| token(node.rightParenthesis); |
| builder.endRule(); |
| builder.unnest(); |
| |
| builder.nestExpression(indent: 2, now: true); |
| |
| if (isSpreadBody) { |
| space(); |
| } else { |
| split(); |
| |
| // If the body is a non-spread collection or lambda, indent it. |
| builder.startBlockArgumentNesting(); |
| } |
| |
| visit(node.body); |
| |
| if (!isSpreadBody) builder.endBlockArgumentNesting(); |
| builder.unnest(); |
| |
| // If a control flow element is nested inside another, force the outer one |
| // to split. |
| if (_isControlFlowElement(node.body)) builder.forceRules(); |
| |
| builder.endRule(); |
| } |
| |
| @override |
| void visitForStatement(ForStatement node) { |
| builder.nestExpression(); |
| token(node.awaitKeyword, after: space); |
| token(node.forKeyword); |
| space(); |
| token(node.leftParenthesis); |
| |
| builder.startRule(); |
| |
| visit(node.forLoopParts); |
| |
| token(node.rightParenthesis); |
| builder.endRule(); |
| builder.unnest(); |
| |
| _visitLoopBody(node.body); |
| } |
| |
| @override |
| void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) { |
| // TODO(rnystrom): The formatting logic here is slightly different from |
| // how parameter metadata is handled and from how variable metadata is |
| // handled. I think what it does works better in the context of a for-in |
| // loop, but consider trying to unify this with one of the above. |
| // |
| // Metadata on class and variable declarations is *always* split: |
| // |
| // @foo |
| // class Bar {} |
| // |
| // Metadata on parameters has some complex logic to handle multiple |
| // parameters with metadata. It also indents the parameters farther than |
| // the metadata when split: |
| // |
| // function( |
| // @foo(long arg list...) |
| // parameter1, |
| // @foo |
| // parameter2) {} |
| // |
| // For for-in variables, we allow it to not split, like parameters, but |
| // don't indent the variable when it does split: |
| // |
| // for ( |
| // @foo |
| // @bar |
| // var blah in stuff) {} |
| // TODO(rnystrom): we used to call builder.startRule() here, but now we call |
| // it from visitForStatement2 prior to the `(`. Is that ok? |
| visitNodes(node.loopVariable.metadata, between: split, after: split); |
| visit(node.loopVariable); |
| // TODO(rnystrom): we used to call builder.endRule() here, but now we call |
| // it from visitForStatement2 after the `)`. Is that ok? |
| |
| _visitForEachPartsFromIn(node); |
| } |
| |
| void _visitForEachPartsFromIn(ForEachParts node) { |
| soloSplit(); |
| token(node.inKeyword); |
| space(); |
| visit(node.iterable); |
| } |
| |
| @override |
| void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) { |
| visit(node.identifier); |
| _visitForEachPartsFromIn(node); |
| } |
| |
| @override |
| void visitForPartsWithDeclarations(ForPartsWithDeclarations node) { |
| // Nest split variables more so they aren't at the same level |
| // as the rest of the loop clauses. |
| builder.nestExpression(); |
| |
| // Allow the variables to stay unsplit even if the clauses split. |
| builder.startRule(); |
| |
| var declaration = node.variables; |
| visitMetadata(declaration.metadata); |
| modifier(declaration.keyword); |
| visit(declaration.type, after: space); |
| |
| visitCommaSeparatedNodes(declaration.variables, between: () { |
| split(); |
| }); |
| |
| builder.endRule(); |
| builder.unnest(); |
| |
| _visitForPartsFromLeftSeparator(node); |
| } |
| |
| @override |
| void visitForPartsWithExpression(ForPartsWithExpression node) { |
| visit(node.initialization); |
| _visitForPartsFromLeftSeparator(node); |
| } |
| |
| void _visitForPartsFromLeftSeparator(ForParts node) { |
| 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(); |
| } |
| } |
| |
| @override |
| void visitFunctionDeclaration(FunctionDeclaration node) { |
| _visitMemberDeclaration(node, node.functionExpression); |
| } |
| |
| @override |
| void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) { |
| visit(node.functionDeclaration); |
| } |
| |
| @override |
| void visitFunctionExpression(FunctionExpression node) { |
| // Inside a function body is no longer in the surrounding const context. |
| var oldConstNesting = _constNesting; |
| _constNesting = 0; |
| |
| _visitBody(node.typeParameters, node.parameters, node.body); |
| |
| _constNesting = oldConstNesting; |
| } |
| |
| @override |
| void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
| // Try to keep the entire invocation one line. |
| builder.startSpan(); |
| builder.nestExpression(); |
| |
| visit(node.function); |
| visit(node.typeArguments); |
| visitArgumentList(node.argumentList, nestExpression: false); |
| |
| builder.unnest(); |
| builder.endSpan(); |
| } |
| |
| @override |
| void visitFunctionTypeAlias(FunctionTypeAlias node) { |
| visitMetadata(node.metadata); |
| |
| if (_formatter.fixes.contains(StyleFix.functionTypedefs)) { |
| _simpleStatement(node, () { |
| // Inlined visitGenericTypeAlias |
| _visitGenericTypeAliasHeader(node.typedefKeyword, node.name, |
| node.typeParameters, null, (node.returnType ?? node.name).offset); |
| |
| space(); |
| |
| // Recursively convert function-arguments to Function syntax. |
| _insideNewTypedefFix = true; |
| _visitGenericFunctionType( |
| node.returnType, null, node.name.offset, null, node.parameters); |
| _insideNewTypedefFix = false; |
| }); |
| return; |
| } |
| |
| _simpleStatement(node, () { |
| token(node.typedefKeyword); |
| space(); |
| visit(node.returnType, after: space); |
| visit(node.name); |
| visit(node.typeParameters); |
| visit(node.parameters); |
| }); |
| } |
| |
| @override |
| void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) { |
| visitParameterMetadata(node.metadata, () { |
| if (!_insideNewTypedefFix) { |
| modifier(node.requiredKeyword); |
| modifier(node.covariantKeyword); |
| visit(node.returnType, after: space); |
| // Try to keep the function's parameters with its name. |
| builder.startSpan(); |
| visit(node.identifier); |
| _visitParameterSignature(node.typeParameters, node.parameters); |
| token(node.question); |
| builder.endSpan(); |
| } else { |
| _beginFormalParameter(node); |
| _visitGenericFunctionType(node.returnType, null, node.identifier.offset, |
| node.typeParameters, node.parameters); |
| token(node.question); |
| split(); |
| visit(node.identifier); |
| _endFormalParameter(node); |
| } |
| }); |
| } |
| |
| @override |
| void visitGenericFunctionType(GenericFunctionType node) { |
| _visitGenericFunctionType(node.returnType, node.functionKeyword, null, |
| node.typeParameters, node.parameters); |
| token(node.question); |
| } |
| |
| @override |
| void visitGenericTypeAlias(GenericTypeAlias node) { |
| visitNodes(node.metadata, between: newline, after: newline); |
| _simpleStatement(node, () { |
| _visitGenericTypeAliasHeader(node.typedefKeyword, node.name, |
| node.typeParameters, node.equals, null); |
| |
| space(); |
| |
| visit(node.type); |
| }); |
| } |
| |
| @override |
| void visitHideCombinator(HideCombinator node) { |
| _visitCombinator(node.keyword, node.hiddenNames); |
| } |
| |
| @override |
| void visitIfElement(IfElement node) { |
| // Treat a chain of if-else elements as a single unit so that we don't |
| // unnecessarily indent each subsequent section of the chain. |
| var ifElements = [ |
| for (CollectionElement? thisNode = node; |
| thisNode is IfElement; |
| thisNode = thisNode.elseElement) |
| thisNode |
| ]; |
| |
| // If the body of the then or else branch is a spread of a collection |
| // literal, then we want to format those collections more like blocks than |
| // like standalone objects. In particular, if both the then and else branch |
| // are spread collection literals, we want to ensure that they both split |
| // if either splits. So this: |
| // |
| // [ |
| // if (condition) ...[ |
| // thenClause |
| // ] else ...[ |
| // elseClause |
| // ] |
| // ] |
| // |
| // And not something like this: |
| // |
| // [ |
| // if (condition) ...[ |
| // thenClause |
| // ] else ...[elseClause] |
| // ] |
| // |
| // To do that, if we see that either clause is a spread collection, we |
| // create a single rule and force both collections to use it. |
| var spreadRule = Rule(); |
| var spreadBrackets = <CollectionElement, Token>{}; |
| for (var element in ifElements) { |
| var spreadBracket = _findSpreadCollectionBracket(element.thenElement); |
| if (spreadBracket != null) { |
| spreadBrackets[element] = spreadBracket; |
| beforeBlock(spreadBracket, spreadRule, null); |
| } |
| } |
| |
| var elseSpreadBracket = |
| _findSpreadCollectionBracket(ifElements.last.elseElement); |
| if (elseSpreadBracket != null) { |
| spreadBrackets[ifElements.last.elseElement!] = elseSpreadBracket; |
| beforeBlock(elseSpreadBracket, spreadRule, null); |
| } |
| |
| void visitChild(CollectionElement element, CollectionElement child) { |
| builder.nestExpression(indent: 2, now: true); |
| |
| // Treat a spread of a collection literal like a block in an if statement |
| // and don't split after the "else". |
| var isSpread = spreadBrackets.containsKey(element); |
| if (isSpread) { |
| space(); |
| } else { |
| split(); |
| |
| // If the then clause is a non-spread collection or lambda, make sure the |
| // body is indented. |
| builder.startBlockArgumentNesting(); |
| } |
| |
| visit(child); |
| |
| if (!isSpread) builder.endBlockArgumentNesting(); |
| builder.unnest(); |
| } |
| |
| // Wrap the whole thing in a single rule. If a split happens inside the |
| // condition or the then clause, we want the then and else clauses to split. |
| builder.startLazyRule(); |
| |
| var hasInnerControlFlow = false; |
| for (var element in ifElements) { |
| // The condition. |
| token(element.ifKeyword); |
| space(); |
| token(element.leftParenthesis); |
| visit(element.condition); |
| token(element.rightParenthesis); |
| |
| visitChild(element, element.thenElement); |
| if (_isControlFlowElement(element.thenElement)) { |
| hasInnerControlFlow = true; |
| } |
| |
| // Handle this element's "else" keyword and prepare to write the element, |
| // but don't write it. It will either be the next element in [ifElements] |
| // or the final else element handled after the loop. |
| if (element.elseElement != null) { |
| if (spreadBrackets.containsKey(element)) { |
| space(); |
| } else { |
| split(); |
| } |
| |
| token(element.elseKeyword); |
| |
| // If there is another if element in the chain, put a space between |
| // it and this "else". |
| if (element != ifElements.last) space(); |
| } |
| } |
| |
| // Handle the final trailing else if there is one. |
| var lastElse = ifElements.last.elseElement; |
| if (lastElse != null) { |
| visitChild(lastElse, lastElse); |
| |
| if (_isControlFlowElement(lastElse)) { |
| hasInnerControlFlow = true; |
| } |
| } |
| |
| // If a control flow element is nested inside another, force the outer one |
| // to split. |
| if (hasInnerControlFlow) builder.forceRules(); |
| builder.endRule(); |
| } |
| |
| @override |
| void visitIfStatement(IfStatement node) { |
| builder.nestExpression(); |
| token(node.ifKeyword); |
| space(); |
| token(node.leftParenthesis); |
| visit(node.condition); |
| token(node.rightParenthesis); |
| builder.unnest(); |
| |
| @override |
| void visitClause(Statement clause) { |
| if (clause is Block || clause is IfStatement) { |
| space(); |
| visit(clause); |
| } else { |
| // Allow splitting in a statement-bodied if even though it's against |
| // the style guide. Since we can't fix the code itself to follow the |
| // style guide, we should at least format it as well as we can. |
| builder.indent(); |
| builder.startRule(); |
| |
| // If there is an else clause, always split before both the then and |
| // else statements. |
| if (node.elseStatement != null) { |
| builder.writeWhitespace(Whitespace.newline); |
| } else { |
| builder.split(nest: false, space: true); |
| } |
| |
| visit(clause); |
| |
| builder.endRule(); |
| builder.unindent(); |
| } |
| } |
| |
| visitClause(node.thenStatement); |
| |
| 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); |
| visitClause(node.elseStatement!); |
| } |
| } |
| |
| @override |
| void visitImplementsClause(ImplementsClause node) { |
| _visitCombinator(node.implementsKeyword, node.interfaces); |
| } |
| |
| @override |
| void visitImportDirective(ImportDirective node) { |
| _visitDirectiveMetadata(node); |
| _simpleStatement(node, () { |
| token(node.keyword); |
| space(); |
| visit(node.uri); |
| |
| _visitConfigurations(node.configurations); |
| |
| if (node.asKeyword != null) { |
| soloSplit(); |
| token(node.deferredKeyword, after: space); |
| token(node.asKeyword); |
| space(); |
| visit(node.prefix); |
| } |
| |
| builder.startRule(CombinatorRule()); |
| visitNodes(node.combinators); |
| builder.endRule(); |
| }); |
| } |
| |
| @override |
| void visitIndexExpression(IndexExpression node) { |
| builder.nestExpression(); |
| |
| if (node.isCascaded) { |
| token(node.period); |
| } else { |
| visit(node.target); |
| } |
| |
| finishIndexExpression(node); |
| |
| builder.unnest(); |
| } |
| |
| /// Visit the index part of [node], excluding the target. |
| /// |
| /// Called by [CallChainVisitor] to handle index expressions in the middle of |
| /// call chains. |
| void finishIndexExpression(IndexExpression node) { |
| if (node.target is IndexExpression) { |
| // Edge case: On a chain of [] accesses, allow splitting between them. |
| // Produces nicer output in cases like: |
| // |
| // someJson['property']['property']['property']['property']... |
| soloZeroSplit(); |
| } |
| |
| builder.startSpan(Cost.index); |
| token(node.question); |
| token(node.leftBracket); |
| soloZeroSplit(); |
| visit(node.index); |
| token(node.rightBracket); |
| builder.endSpan(); |
| } |
| |
| @override |
| void visitInstanceCreationExpression(InstanceCreationExpression node) { |
| builder.startSpan(); |
| |
| var includeKeyword = true; |
| |
| if (node.keyword != null) { |
| if (node.keyword!.keyword == Keyword.NEW && |
| _formatter.fixes.contains(StyleFix.optionalNew)) { |
| includeKeyword = false; |
| } else if (node.keyword!.keyword == Keyword.CONST && |
| _formatter.fixes.contains(StyleFix.optionalConst) && |
| _constNesting > 0) { |
| includeKeyword = false; |
| } |
| } |
| |
| if (includeKeyword) { |
| token(node.keyword, after: space); |
| } else { |
| // Don't lose comments before the discarded keyword, if any. |
| writePrecedingCommentsAndNewlines(node.keyword!); |
| } |
| |
| builder.startSpan(Cost.constructorName); |
| |
| // Start the expression nesting for the argument list here, in case this |
| // is a generic constructor with type arguments. If it is, we need the type |
| // arguments to be nested too so they get indented past the arguments. |
| builder.nestExpression(); |
| visit(node.constructorName); |
| |
| _startPossibleConstContext(node.keyword); |
| |
| builder.endSpan(); |
| visitArgumentList(node.argumentList, nestExpression: false); |
| builder.endSpan(); |
| |
| _endPossibleConstContext(node.keyword); |
| |
| builder.unnest(); |
| } |
| |
| @override |
| void visitIntegerLiteral(IntegerLiteral node) { |
| token(node.literal); |
| } |
| |
| @override |
| void visitInterpolationExpression(InterpolationExpression node) { |
| builder.preventSplit(); |
| token(node.leftBracket); |
| builder.startSpan(); |
| visit(node.expression); |
| builder.endSpan(); |
| token(node.rightBracket); |
| builder.endPreventSplit(); |
| } |
| |
| @override |
| void visitInterpolationString(InterpolationString node) { |
| _writeStringLiteral(node.contents); |
| } |
| |
| @override |
| void visitIsExpression(IsExpression node) { |
| builder.startSpan(); |
| builder.nestExpression(); |
| visit(node.expression); |
| soloSplit(); |
| token(node.isOperator); |
| token(node.notOperator); |
| space(); |
| visit(node.type); |
| builder.unnest(); |
| builder.endSpan(); |
| } |
| |
| @override |
| void visitLabel(Label node) { |
| visit(node.label); |
| token(node.colon); |
| } |
| |
| @override |
| void visitLabeledStatement(LabeledStatement node) { |
| _visitLabels(node.labels); |
| visit(node.statement); |
| } |
| |
| @override |
| void visitLibraryDirective(LibraryDirective node) { |
| _visitDirectiveMetadata(node); |
| _simpleStatement(node, () { |
| token(node.keyword); |
| space(); |
| visit(node.name); |
| }); |
| } |
| |
| @override |
| void visitLibraryIdentifier(LibraryIdentifier node) { |
| visit(node.components.first); |
| for (var component in node.components.skip(1)) { |
| token(component.beginToken.previous); // "." |
| visit(component); |
| } |
| } |
| |
| @override |
| void 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); |
| } |
| |
| @override |
| void visitMapLiteralEntry(MapLiteralEntry node) { |
| builder.nestExpression(); |
| visit(node.key); |
| token(node.separator); |
| soloSplit(); |
| visit(node.value); |
| builder.unnest(); |
| } |
| |
| @override |
| void visitMethodDeclaration(MethodDeclaration node) { |
| _visitMemberDeclaration(node, node); |
| } |
| |
| @override |
| void visitMethodInvocation(MethodInvocation node) { |
| // If there's no target, this is a "bare" function call like "foo(1, 2)", |
| // or a section in a cascade. |
| // |
| // If it looks like a constructor or static call, we want to keep the |
| // target and method together instead of including the method in the |
| // subsequent method chain. When this happens, it's important that this |
| // code here has the same rules as in [visitInstanceCreationExpression]. |
| // |
| // That ensures that the way some code is formatted is not affected by the |
| // presence or absence of `new`/`const`. In particular, it means that if |
| // they run `dartfmt --fix`, and then run `dartfmt` *again*, the second run |
| // will not produce any additional changes. |
| if (node.target == null || looksLikeStaticCall(node)) { |
| // Try to keep the entire method invocation one line. |
| builder.nestExpression(); |
| builder.startSpan(); |
| |
| if (node.target != null) { |
| builder.startSpan(Cost.constructorName); |
| visit(node.target); |
| soloZeroSplit(); |
| } |
| |
| // If target is null, this will be `..` for a cascade. |
| token(node.operator); |
| visit(node.methodName); |
| |
| if (node.target != null) builder.endSpan(); |
| |
| // TODO(rnystrom): Currently, there are no constraints between a generic |
| // method's type arguments and arguments. That can lead to some funny |
| // splitting like: |
| // |
| // method<VeryLongType, |
| // AnotherTypeArgument>(argument, |
| // argument, argument, argument); |
| // |
| // The indentation is fine, but splitting in the middle of each argument |
| // list looks kind of strange. If this ends up happening in real world |
| // code, consider putting a constraint between them. |
| builder.nestExpression(); |
| visit(node.typeArguments); |
| visitArgumentList(node.argumentList, nestExpression: false); |
| builder.unnest(); |
| |
| builder.endSpan(); |
| builder.unnest(); |
| return; |
| } |
| |
| CallChainVisitor(this, node).visit(); |
| } |
| |
| @override |
| void visitMixinDeclaration(MixinDeclaration node) { |
| visitMetadata(node.metadata); |
| |
| builder.nestExpression(); |
| token(node.mixinKeyword); |
| space(); |
| visit(node.name); |
| visit(node.typeParameters); |
| |
| // If there is only a single superclass constraint, format it like an |
| // "extends" in a class. |
| var onClause = node.onClause; |
| if (onClause != null && onClause.superclassConstraints.length == 1) { |
| soloSplit(); |
| token(onClause.onKeyword); |
| space(); |
| visit(onClause.superclassConstraints.single); |
| } |
| |
| builder.startRule(CombinatorRule()); |
| |
| // If there are multiple superclass constraints, format them like the |
| // "implements" clause. |
| if (onClause != null && onClause.superclassConstraints.length > 1) { |
| visit(onClause); |
| } |
| |
| visit(node.implementsClause); |
| builder.endRule(); |
| |
| space(); |
| |
| builder.unnest(); |
| _beginBody(node.leftBracket); |
| _visitMembers(node.members); |
| _endBody(node.rightBracket); |
| } |
| |
| @override |
| void visitNamedExpression(NamedExpression node) { |
| visitNamedArgument(node); |
| } |
| |
| @override |
| void visitNativeClause(NativeClause node) { |
| token(node.nativeKeyword); |
| visit(node.name, before: space); |
| } |
| |
| @override |
| void visitNativeFunctionBody(NativeFunctionBody node) { |
| _simpleStatement(node, () { |
| builder.nestExpression(now: true); |
| soloSplit(); |
| token(node.nativeKeyword); |
| visit(node.stringLiteral, before: space); |
| builder.unnest(); |
| }); |
| } |
| |
| @override |
| void visitNullLiteral(NullLiteral node) { |
| token(node.literal); |
| } |
| |
| @override |
| void visitOnClause(OnClause node) { |
| _visitCombinator(node.onKeyword, node.superclassConstraints); |
| } |
| |
| @override |
| void visitParenthesizedExpression(ParenthesizedExpression node) { |
| builder.nestExpression(); |
| token(node.leftParenthesis); |
| visit(node.expression); |
| builder.unnest(); |
| token(node.rightParenthesis); |
| } |
| |
| @override |
| void visitPartDirective(PartDirective node) { |
| _visitDirectiveMetadata(node); |
| _simpleStatement(node, () { |
| token(node.keyword); |
| space(); |
| visit(node.uri); |
| }); |
| } |
| |
| @override |
| void visitPartOfDirective(PartOfDirective node) { |
| _visitDirectiveMetadata(node); |
| _simpleStatement(node, () { |
| token(node.keyword); |
| space(); |
| token(node.ofKeyword); |
| space(); |
| |
| // Part-of may have either a name or a URI. Only one of these will be |
| // non-null. We visit both since visit() ignores null. |
| visit(node.libraryName); |
| visit(node.uri); |
| }); |
| } |
| |
| @override |
| void visitPostfixExpression(PostfixExpression node) { |
| visit(node.operand); |
| token(node.operator); |
| } |
| |
| @override |
| void visitPrefixedIdentifier(PrefixedIdentifier node) { |
| CallChainVisitor(this, node).visit(); |
| } |
| |
| @override |
| void visitPrefixExpression(PrefixExpression node) { |
| token(node.operator); |
| |
| // Edge case: put a space after "-" if the operand is "-" or "--" so we |
| // don't merge the operators. |
| var operand = node.operand; |
| if (operand is PrefixExpression && |
| (operand.operator.lexeme == '-' || operand.operator.lexeme == '--')) { |
| space(); |
| } |
| |
| visit(node.operand); |
| } |
| |
| @override |
| void visitPropertyAccess(PropertyAccess node) { |
| if (node.isCascaded) { |
| token(node.operator); |
| visit(node.propertyName); |
| return; |
| } |
| |
| CallChainVisitor(this, node).visit(); |
| } |
| |
| @override |
| void visitRedirectingConstructorInvocation( |
| RedirectingConstructorInvocation node) { |
| builder.startSpan(); |
| |
| token(node.thisKeyword); |
| token(node.period); |
| visit(node.constructorName); |
| visit(node.argumentList); |
| |
| builder.endSpan(); |
| } |
| |
| @override |
| void visitRethrowExpression(RethrowExpression node) { |
| token(node.rethrowKeyword); |
| } |
| |
| @override |
| void visitReturnStatement(ReturnStatement node) { |
| _simpleStatement(node, () { |
| token(node.returnKeyword); |
| visit(node.expression, before: space); |
| }); |
| } |
| |
| @override |
| void 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); |
| twoNewlines(); |
| } |
| |
| @override |
| void visitSetOrMapLiteral(SetOrMapLiteral node) { |
| _visitCollectionLiteral( |
| node, node.leftBracket, node.elements, node.rightBracket); |
| } |
| |
| @override |
| void visitShowCombinator(ShowCombinator node) { |
| _visitCombinator(node.keyword, node.shownNames); |
| } |
| |
| @override |
| void visitSimpleFormalParameter(SimpleFormalParameter node) { |
| visitParameterMetadata(node.metadata, () { |
| _beginFormalParameter(node); |
| |
| var hasType = node.type != null; |
| if (_insideNewTypedefFix && !hasType) { |
| // Parameters can use "var" instead of "dynamic". Since we are inserting |
| // "dynamic" in that case, remove the "var". |
| if (node.keyword != null) { |
| if (node.keyword!.type != Keyword.VAR) { |
| modifier(node.keyword); |
| } else { |
| // Keep any comment attached to "var". |
| writePrecedingCommentsAndNewlines(node.keyword!); |
| } |
| } |
| |
| // In function declarations and the old typedef syntax, you can have a |
| // parameter name without a type. In the new syntax, you can have a type |
| // without a name. Add "dynamic" in that case. |
| |
| // Ensure comments on the identifier comes before the inserted type. |
| token(node.identifier!.token, before: () { |
| _writeText('dynamic', node.identifier!.offset); |
| split(); |
| }); |
| } else { |
| modifier(node.keyword); |
| visit(node.type); |
| |
| if (hasType && node.identifier != null) split(); |
| |
| visit(node.identifier); |
| } |
| |
| _endFormalParameter(node); |
| }); |
| } |
| |
| @override |
| void visitSimpleIdentifier(SimpleIdentifier node) { |
| token(node.token); |
| } |
| |
| @override |
| void visitSimpleStringLiteral(SimpleStringLiteral node) { |
| _writeStringLiteral(node.literal); |
| } |
| |
| @override |
| void visitSpreadElement(SpreadElement node) { |
| token(node.spreadOperator); |
| visit(node.expression); |
| } |
| |
| @override |
| void visitStringInterpolation(StringInterpolation node) { |
| for (var element in node.elements) { |
| visit(element); |
| } |
| } |
| |
| @override |
| void visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
| builder.startSpan(); |
| |
| token(node.superKeyword); |
| token(node.period); |
| visit(node.constructorName); |
| visit(node.argumentList); |
| |
| builder.endSpan(); |
| } |
| |
| @override |
| void visitSuperExpression(SuperExpression node) { |
| token(node.superKeyword); |
| } |
| |
| @override |
| void visitSwitchCase(SwitchCase node) { |
| _visitLabels(node.labels); |
| 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(); |
| } |
| |
| @override |
| void visitSwitchDefault(SwitchDefault node) { |
| _visitLabels(node.labels); |
| token(node.keyword); |
| token(node.colon); |
| |
| builder.indent(); |
| // TODO(rnystrom): Allow inline cases? |
| newline(); |
| |
| visitNodes(node.statements, between: oneOrTwoNewlines); |
| builder.unindent(); |
| } |
| |
| @override |
| void visitSwitchStatement(SwitchStatement node) { |
| builder.nestExpression(); |
| token(node.switchKeyword); |
| space(); |
| token(node.leftParenthesis); |
| soloZeroSplit(); |
| visit(node.expression); |
| token(node.rightParenthesis); |
| space(); |
| token(node.leftBracket); |
| builder.unnest(); |
| builder.indent(); |
| newline(); |
| |
| visitNodes(node.members, between: oneOrTwoNewlines, after: newline); |
| token(node.rightBracket, before: () { |
| builder.unindent(); |
| newline(); |
| }); |
| } |
| |
| @override |
| void 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); |
| } |
| } |
| |
| @override |
| void visitThisExpression(ThisExpression node) { |
| token(node.thisKeyword); |
| } |
| |
| @override |
| void visitThrowExpression(ThrowExpression node) { |
| token(node.throwKeyword); |
| space(); |
| visit(node.expression); |
| } |
| |
| @override |
| void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
| visitMetadata(node.metadata); |
| |
| _simpleStatement(node, () { |
| modifier(node.externalKeyword); |
| visit(node.variables); |
| }); |
| } |
| |
| @override |
| void 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); |
| } |
| |
| @override |
| void visitTypeArgumentList(TypeArgumentList node) { |
| _visitGenericList(node.leftBracket, node.rightBracket, node.arguments); |
| } |
| |
| @override |
| void visitTypeName(TypeName node) { |
| visit(node.name); |
| visit(node.typeArguments); |
| token(node.question); |
| } |
| |
| @override |
| void visitTypeParameter(TypeParameter node) { |
| visitParameterMetadata(node.metadata, () { |
| visit(node.name); |
| token(node.extendsKeyword, before: space, after: space); |
| visit(node.bound); |
| }); |
| } |
| |
| @override |
| void visitTypeParameterList(TypeParameterList node) { |
| _metadataRules.add(MetadataRule()); |
| |
| _visitGenericList(node.leftBracket, node.rightBracket, node.typeParameters); |
| |
| _metadataRules.removeLast(); |
| } |
| |
| @override |
| void visitVariableDeclaration(VariableDeclaration node) { |
| visit(node.name); |
| if (node.initializer == null) return; |
| |
| // If there are multiple variables being declared, we want to nest the |
| // initializers farther so they don't line up with the variables. Bad: |
| // |
| // var a = |
| // aValue, |
| // b = |
| // bValue; |
| // |
| // Good: |
| // |
| // var a = |
| // aValue, |
| // b = |
| // bValue; |
| var hasMultipleVariables = |
| (node.parent as VariableDeclarationList).variables.length > 1; |
| |
| _visitAssignment(node.equals!, node.initializer!, |
| nest: hasMultipleVariables); |
| } |
| |
| @override |
| void visitVariableDeclarationList(VariableDeclarationList node) { |
| visitMetadata(node.metadata); |
| |
| // Allow but try to avoid splitting between the type and name. |
| builder.startSpan(); |
| |
| modifier(node.lateKeyword); |
| modifier(node.keyword); |
| visit(node.type, after: soloSplit); |
| |
| builder.endSpan(); |
| |
| _startPossibleConstContext(node.keyword); |
| |
| // 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(); |
| |
| // If there are multiple declarations split across lines, then we want any |
| // blocks in the initializers to indent past the variables. |
| if (node.variables.length > 1) builder.startBlockArgumentNesting(); |
| |
| visitCommaSeparatedNodes(node.variables, between: split); |
| |
| if (node.variables.length > 1) builder.endBlockArgumentNesting(); |
| |
| builder.endRule(); |
| _endPossibleConstContext(node.keyword); |
| } |
| |
| @override |
| void visitVariableDeclarationStatement(VariableDeclarationStatement node) { |
| _simpleStatement(node, () { |
| visit(node.variables); |
| }); |
| } |
| |
| @override |
| void visitWhileStatement(WhileStatement node) { |
| builder.nestExpression(); |
| token(node.whileKeyword); |
| space(); |
| token(node.leftParenthesis); |
| soloZeroSplit(); |
| visit(node.condition); |
| token(node.rightParenthesis); |
| builder.unnest(); |
| |
| _visitLoopBody(node.body); |
| } |
| |
| @override |
| void visitWithClause(WithClause node) { |
| _visitCombinator(node.withKeyword, node.mixinTypes); |
| } |
| |
| @override |
| void 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 Function()? before, void Function()? after}) { |
| if (node == null) return; |
| |
| if (before != null) before(); |
| |
| node.accept(this); |
| |
| if (after != null) after(); |
| } |
| |
| /// Visit metadata annotations on declarations, and members. |
| /// |
| /// These always force the annotations to be on the previous line. |
| void visitMetadata(NodeList<Annotation> metadata) { |
| visitNodes(metadata, between: newline, after: newline); |
| } |
| |
| /// Visit metadata annotations for a directive. |
| /// |
| /// Always force the annotations to be on a previous line. |
| void _visitDirectiveMetadata(Directive directive) { |
| // Preserve a blank line before the first directive since users (in |
| // particular the test package) sometimes use that for metadata that |
| // applies to the entire library and not the following directive itself. |
| var isFirst = |
| directive == (directive.parent as CompilationUnit).directives.first; |
| |
| visitNodes(directive.metadata, |
| between: newline, after: isFirst ? oneOrTwoNewlines : newline); |
| } |
| |
| /// Visits metadata annotations on parameters and type parameters. |
| /// |
| /// Unlike other annotations, these are allowed to stay on the same line as |
| /// the parameter. |
| void visitParameterMetadata( |
| NodeList<Annotation> metadata, void Function() visitParameter) { |
| if (metadata.isEmpty) { |
| visitParameter(); |
| return; |
| } |
| |
| // Split before all of the annotations or none. |
| builder.startLazyRule(_metadataRules.last); |
| |
| visitNodes(metadata, between: split, after: () { |
| // Don't nest until right before the last metadata. Ensures we only |
| // indent the parameter and not any of the metadata: |
| // |
| // function( |
| // @LongAnnotation |
| // @LongAnnotation |
| // indentedParameter) {} |
| builder.nestExpression(now: true); |
| split(); |
| }); |
| visitParameter(); |
| |
| builder.unnest(); |
| |
| // Wrap the rule around the parameter too. If it splits, we want to force |
| // the annotations to split as well. |
| builder.endRule(); |
| } |
| |
| /// Visits [node], which may be in an argument list controlled by [rule]. |
| /// |
| /// This is called directly by [ArgumentListVisitor] so that it can pass in |
| /// the surrounding named argument rule. That way, this can ensure that a |
| /// split between the name and argument forces the argument list to split |
| /// too. |
| void visitNamedArgument(NamedExpression node, [NamedRule? rule]) { |
| builder.nestExpression(); |
| builder.startSpan(); |
| visit(node.name); |
| |
| // Don't allow a split between a name and a collection. Instead, we want |
| // the collection itself to split, or to split before the argument. |
| if (node.expression is ListLiteral || node.expression is SetOrMapLiteral) { |
| space(); |
| } else { |
| var |