Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Unified Diff: packages/dart_style/lib/src/source_visitor.dart

Issue 2990843002: Removed fixed dependencies (Closed)
Patch Set: Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « packages/dart_style/lib/src/rule/rule.dart ('k') | packages/dart_style/lib/src/whitespace.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: packages/dart_style/lib/src/source_visitor.dart
diff --git a/packages/dart_style/lib/src/source_visitor.dart b/packages/dart_style/lib/src/source_visitor.dart
index a34b36789e70b91f0226e75fd12f41c67e349361..2370d2b5a0b2af0bac595d32c8413c6cb3969c02 100644
--- a/packages/dart_style/lib/src/source_visitor.dart
+++ b/packages/dart_style/lib/src/source_visitor.dart
@@ -5,7 +5,7 @@
library dart_style.src.source_visitor;
import 'package:analyzer/analyzer.dart';
-import 'package:analyzer/src/generated/scanner.dart';
+import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/src/generated/source.dart';
import 'argument_list_visitor.dart';
@@ -23,7 +23,7 @@ import 'whitespace.dart';
/// Visits every token of the AST and passes all of the relevant bits to a
/// [ChunkBuilder].
-class SourceVisitor implements AstVisitor {
+class SourceVisitor extends ThrowingAstVisitor {
/// The builder for the block that is currently being visited.
ChunkBuilder builder;
@@ -133,7 +133,7 @@ class SourceVisitor implements AstVisitor {
/// 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) {
+ visitArgumentList(ArgumentList node, {bool nestExpression: true}) {
// Corner case: handle empty argument lists.
if (node.arguments.isEmpty) {
token(node.leftParenthesis);
@@ -145,26 +145,60 @@ class SourceVisitor implements AstVisitor {
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 (node.arguments.last.endToken.next.type == TokenType.COMMA) {
+ _visitCollectionLiteral(
+ null, node.leftParenthesis, node.arguments, node.rightParenthesis);
+ return;
+ }
+
+ if (nestExpression) builder.nestExpression();
new ArgumentListVisitor(this, node).visit();
+ if (nestExpression) builder.unnest();
}
visitAsExpression(AsExpression node) {
builder.startSpan();
+ builder.nestExpression();
visit(node.expression);
soloSplit();
token(node.asOperator);
space();
visit(node.type);
+ builder.unnest();
builder.endSpan();
}
+ // TODO(rnystrom): Type annotate once analyzer publishes a version with the
+ // new AST type.
+ // TODO(rnystrom): Test.
+ visitAssertInitializer(node) {
+ _simpleStatement(node, () {
+ token(node.assertKeyword);
+
+ var arguments = <Expression>[node.condition];
+ if (node.message != null) arguments.add(node.message);
+
+ builder.nestExpression();
+ var visitor = new ArgumentListVisitor.forArguments(
+ this, node.leftParenthesis, node.rightParenthesis, arguments);
+ visitor.visit();
+ builder.unnest();
+ });
+ }
+
visitAssertStatement(AssertStatement node) {
_simpleStatement(node, () {
token(node.assertKeyword);
- token(node.leftParenthesis);
- soloZeroSplit();
- visit(node.condition);
- token(node.rightParenthesis);
+
+ var arguments = [node.condition];
+ if (node.message != null) arguments.add(node.message);
+
+ var visitor = new ArgumentListVisitor.forArguments(
+ this, node.leftParenthesis, node.rightParenthesis, arguments);
+ visitor.visit();
});
}
@@ -241,18 +275,43 @@ class SourceVisitor implements AstVisitor {
return;
}
- // 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;
+ // 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);
}
- _startLiteralBody(node.leftBracket);
- visitNodes(node.statements, between: oneOrTwoNewlines, after: newline);
- _endLiteralBody(node.rightBracket, forceSplit: node.statements.isNotEmpty);
+ 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);
+ }
}
visitBlockFunctionBody(BlockFunctionBody node) {
@@ -339,10 +398,13 @@ class SourceVisitor implements AstVisitor {
// 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) {
+ if (expression is MethodInvocation) {
+ if (name == null) {
+ name = expression.methodName.name;
+ } else if (name != expression.methodName.name) {
+ return false;
+ }
+ } else {
return false;
}
}
@@ -372,7 +434,7 @@ class SourceVisitor implements AstVisitor {
}
visitClassDeclaration(ClassDeclaration node) {
- visitDeclarationMetadata(node.metadata);
+ visitMetadata(node.metadata);
builder.nestExpression();
modifier(node.abstractKeyword);
@@ -391,43 +453,44 @@ class SourceVisitor implements AstVisitor {
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;
- }
+ _beginBody(node.leftBracket);
- 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 (node.members.isNotEmpty) {
+ for (var member in node.members) {
+ visit(member);
- if (needsDouble) {
- twoNewlines();
- } else {
- // Variables and arrow-bodied members can be more tightly packed if
- // the user wants to group things together.
- oneOrTwoNewlines();
+ 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.
+ if (member.body is BlockFunctionBody) {
+ var body = member.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();
+ }
}
- });
+ }
+
+ _endBody(node.rightBracket);
}
visitClassTypeAlias(ClassTypeAlias node) {
- visitDeclarationMetadata(node.metadata);
+ visitMetadata(node.metadata);
_simpleStatement(node, () {
modifier(node.abstractKeyword);
@@ -466,34 +529,30 @@ class SourceVisitor implements AstVisitor {
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();
- }
+ var needsDouble = true;
+ for (var declaration in node.declarations) {
+ // Add a blank line before classes.
+ if (declaration is ClassDeclaration) needsDouble = true;
- visit(declaration);
+ if (needsDouble) {
+ twoNewlines();
+ } else {
+ // Variables and arrow-bodied members can be more tightly packed if
+ // the user wants to group things together.
+ oneOrTwoNewlines();
+ }
- 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;
- }
+ 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 body = declaration.functionExpression.body;
+ if (body is BlockFunctionBody) {
+ needsDouble = body.block.statements.isNotEmpty;
}
}
}
@@ -502,21 +561,22 @@ class SourceVisitor implements AstVisitor {
visitConditionalExpression(ConditionalExpression node) {
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();
- 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();
@@ -524,7 +584,6 @@ class SourceVisitor implements AstVisitor {
split();
token(node.colon);
space();
-
visit(node.elseExpression);
builder.endRule();
@@ -533,8 +592,28 @@ class SourceVisitor implements AstVisitor {
builder.unnest();
}
+ 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);
+ }
+
visitConstructorDeclaration(ConstructorDeclaration node) {
- visitMemberMetadata(node.metadata);
+ visitMetadata(node.metadata);
modifier(node.externalKeyword);
modifier(node.constKeyword);
@@ -552,7 +631,7 @@ class SourceVisitor implements AstVisitor {
// the parameter list gets more deeply indented.
if (node.redirectedConstructor != null) builder.nestExpression();
- _visitBody(node.parameters, node.body, () {
+ _visitBody(null, node.parameters, node.body, () {
// Check for redirects or initializer lists.
if (node.redirectedConstructor != null) {
_visitConstructorRedirects(node);
@@ -666,6 +745,17 @@ class SourceVisitor implements AstVisitor {
builder.unnest();
}
+ visitDottedName(DottedName node) {
+ for (var component in node.components) {
+ // Write the preceding ".".
+ if (component != node.components.first) {
+ token(component.beginToken.previous);
+ }
+
+ visit(component);
+ }
+ }
+
visitDoubleLiteral(DoubleLiteral node) {
token(node.literal);
}
@@ -683,26 +773,34 @@ class SourceVisitor implements AstVisitor {
}
visitEnumDeclaration(EnumDeclaration node) {
- visitDeclarationMetadata(node.metadata);
+ visitMetadata(node.metadata);
token(node.enumKeyword);
space();
visit(node.name);
space();
- _writeBody(node.leftBracket, node.rightBracket, space: true, body: () {
- visitCommaSeparatedNodes(node.constants, between: split);
- });
+ _beginBody(node.leftBracket, space: true);
+ visitCommaSeparatedNodes(node.constants, between: split);
+
+ // If there is a trailing comma, always force the constants to split.
+ if (node.constants.last.endToken.next.type == TokenType.COMMA) {
+ builder.forceRules();
+ }
+
+ _endBody(node.rightBracket, space: true);
}
visitExportDirective(ExportDirective node) {
- visitDeclarationMetadata(node.metadata);
+ visitMetadata(node.metadata);
_simpleStatement(node, () {
token(node.keyword);
space();
visit(node.uri);
+ _visitConfigurations(node.configurations);
+
builder.startRule(new CombinatorRule());
visitNodes(node.combinators);
builder.endRule();
@@ -757,16 +855,18 @@ class SourceVisitor implements AstVisitor {
}
visitFieldDeclaration(FieldDeclaration node) {
- visitMemberMetadata(node.metadata);
+ visitMetadata(node.metadata);
_simpleStatement(node, () {
modifier(node.staticKeyword);
+ modifier(node.covariantKeyword);
visit(node.fields);
});
}
visitFieldFormalParameter(FieldFormalParameter node) {
visitParameterMetadata(node.metadata, () {
+ modifier(node.covariantKeyword);
token(node.keyword, after: space);
visit(node.type, after: space);
token(node.thisKeyword);
@@ -792,12 +892,13 @@ class SourceVisitor implements AstVisitor {
space();
visit(node.iterable);
token(node.rightParenthesis);
- builder.unnest(now: false);
+ builder.unnest();
_visitLoopBody(node.body);
}
- visitFormalParameterList(FormalParameterList node) {
+ visitFormalParameterList(FormalParameterList node,
+ {bool nestExpression: true}) {
// Corner case: empty parameter lists.
if (node.parameters.isEmpty) {
token(node.leftParenthesis);
@@ -809,6 +910,14 @@ class SourceVisitor implements AstVisitor {
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 (node.parameters.last.endToken.next.type == TokenType.COMMA) {
+ _visitTrailingCommaParameterList(node);
+ return;
+ }
+
var requiredParams = node.parameters
.where((param) => param is! DefaultFormalParameter)
.toList();
@@ -816,19 +925,14 @@ class SourceVisitor implements AstVisitor {
.where((param) => param is DefaultFormalParameter)
.toList();
- builder.nestExpression();
+ if (nestExpression) builder.nestExpression();
token(node.leftParenthesis);
_metadataRules.add(new MetadataRule());
var rule;
if (requiredParams.isNotEmpty) {
- if (requiredParams.length > 1) {
- rule = new MultiplePositionalRule(null, 0, 0);
- } else {
- rule = new SinglePositionalRule(null);
- }
-
+ rule = new PositionalRule(null, 0, 0);
_metadataRules.last.bindPositionalRule(rule);
builder.startRule(rule);
@@ -847,8 +951,10 @@ class SourceVisitor implements AstVisitor {
for (var param in requiredParams) {
visit(param);
- // Write the trailing comma.
- if (param != node.parameters.last) token(param.endToken.next);
+ // Write the following comma.
+ if (param.endToken.next.type == TokenType.COMMA) {
+ token(param.endToken.next);
+ }
if (param != requiredParams.last) rule.beforeArgument(split());
}
@@ -876,8 +982,11 @@ class SourceVisitor implements AstVisitor {
for (var param in optionalParams) {
visit(param);
- // Write the trailing comma.
- if (param != node.parameters.last) token(param.endToken.next);
+ // Write the following comma.
+ if (param.endToken.next.type == TokenType.COMMA) {
+ token(param.endToken.next);
+ }
+
if (param != optionalParams.last) namedRule.beforeArgument(split());
}
@@ -891,7 +1000,7 @@ class SourceVisitor implements AstVisitor {
_metadataRules.removeLast();
token(node.rightParenthesis);
- builder.unnest();
+ if (nestExpression) builder.unnest();
}
visitForStatement(ForStatement node) {
@@ -914,7 +1023,7 @@ class SourceVisitor implements AstVisitor {
builder.startRule();
var declaration = node.variables;
- visitDeclarationMetadata(declaration.metadata);
+ visitMetadata(declaration.metadata);
modifier(declaration.keyword);
visit(declaration.type, after: space);
@@ -961,7 +1070,20 @@ class SourceVisitor implements AstVisitor {
}
visitFunctionExpression(FunctionExpression node) {
- _visitBody(node.parameters, node.body);
+ // TODO(rnystrom): This is working but not tested. As of 2016/11/29, the
+ // latest version of analyzer on pub does not parse generic lambdas. When
+ // a version of it that does is published, upgrade dart_style to use it and
+ // then test it:
+ //
+ // >>> generic function expression
+ // main() {
+ // var generic = < T,S >(){};
+ // }
+ // <<<
+ // main() {
+ // var generic = <T, S>() {};
+ // }
+ _visitBody(node.typeParameters, node.parameters, node.body);
}
visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
@@ -970,7 +1092,7 @@ class SourceVisitor implements AstVisitor {
}
visitFunctionTypeAlias(FunctionTypeAlias node) {
- visitDeclarationMetadata(node.metadata);
+ visitMetadata(node.metadata);
_simpleStatement(node, () {
token(node.typedefKeyword);
@@ -984,6 +1106,7 @@ class SourceVisitor implements AstVisitor {
visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
visitParameterMetadata(node.metadata, () {
+ modifier(node.covariantKeyword);
visit(node.returnType, after: space);
// Try to keep the function's parameters with its name.
@@ -1005,31 +1128,31 @@ class SourceVisitor implements AstVisitor {
token(node.leftParenthesis);
visit(node.condition);
token(node.rightParenthesis);
- builder.unnest(now: false);
+ builder.unnest();
visitClause(Statement clause) {
if (clause is Block || clause is IfStatement) {
space();
visit(clause);
} else {
- // Allow splitting in an expression-bodied if even though it's against
+ // 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.nestExpression(indent: 2, now: true);
+ 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.nestedNewline);
+ builder.writeWhitespace(Whitespace.newline);
} else {
- split();
+ builder.split(nest: false, space: true);
}
visit(clause);
builder.endRule();
- builder.unnest();
+ builder.unindent();
}
}
@@ -1055,13 +1178,15 @@ class SourceVisitor implements AstVisitor {
}
visitImportDirective(ImportDirective node) {
- visitDeclarationMetadata(node.metadata);
+ visitMetadata(node.metadata);
_simpleStatement(node, () {
token(node.keyword);
space();
visit(node.uri);
+ _visitConfigurations(node.configurations);
+
if (node.asKeyword != null) {
soloSplit();
token(node.deferredKeyword, after: space);
@@ -1103,7 +1228,7 @@ class SourceVisitor implements AstVisitor {
soloZeroSplit();
}
- builder.startSpan();
+ builder.startSpan(Cost.index);
token(node.leftBracket);
soloZeroSplit();
visit(node.index);
@@ -1116,10 +1241,18 @@ class SourceVisitor implements AstVisitor {
token(node.keyword);
space();
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);
+
builder.endSpan();
- visit(node.argumentList);
+ visitArgumentList(node.argumentList, nestExpression: false);
builder.endSpan();
+
+ builder.unnest();
}
visitIntegerLiteral(IntegerLiteral node) {
@@ -1138,12 +1271,14 @@ class SourceVisitor implements AstVisitor {
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();
}
@@ -1153,12 +1288,12 @@ class SourceVisitor implements AstVisitor {
}
visitLabeledStatement(LabeledStatement node) {
- visitNodes(node.labels, between: space, after: space);
+ _visitLabels(node.labels);
visit(node.statement);
}
visitLibraryDirective(LibraryDirective node) {
- visitDeclarationMetadata(node.metadata);
+ visitMetadata(node.metadata);
_simpleStatement(node, () {
token(node.keyword);
@@ -1209,8 +1344,21 @@ class SourceVisitor implements AstVisitor {
// This will be non-null for cascade sections.
token(node.operator);
- token(node.methodName.token);
- visit(node.argumentList);
+ visit(node.methodName);
+
+ // 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.
+ visit(node.typeArguments);
+ visitArgumentList(node.argumentList, nestExpression: false);
builder.unnest();
builder.endSpan();
@@ -1221,21 +1369,7 @@ class SourceVisitor implements AstVisitor {
}
visitNamedExpression(NamedExpression node) {
- 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 MapLiteral) {
- space();
- } else {
- soloSplit();
- }
-
- visit(node.expression);
- builder.endSpan();
- builder.unnest();
+ visitNamedArgument(node);
}
visitNativeClause(NativeClause node) {
@@ -1268,7 +1402,7 @@ class SourceVisitor implements AstVisitor {
}
visitPartDirective(PartDirective node) {
- visitDeclarationMetadata(node.metadata);
+ visitMetadata(node.metadata);
_simpleStatement(node, () {
token(node.keyword);
@@ -1278,7 +1412,7 @@ class SourceVisitor implements AstVisitor {
}
visitPartOfDirective(PartOfDirective node) {
- visitDeclarationMetadata(node.metadata);
+ visitMetadata(node.metadata);
_simpleStatement(node, () {
token(node.keyword);
@@ -1349,8 +1483,7 @@ class SourceVisitor implements AstVisitor {
// 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();
+ newline();
}
visitShowCombinator(ShowCombinator node) {
@@ -1359,6 +1492,7 @@ class SourceVisitor implements AstVisitor {
visitSimpleFormalParameter(SimpleFormalParameter node) {
visitParameterMetadata(node.metadata, () {
+ modifier(node.covariantKeyword);
modifier(node.keyword);
visit(node.type, after: space);
visit(node.identifier);
@@ -1406,7 +1540,7 @@ class SourceVisitor implements AstVisitor {
}
visitSwitchCase(SwitchCase node) {
- visitNodes(node.labels, between: space, after: space);
+ _visitLabels(node.labels);
token(node.keyword);
space();
visit(node.expression);
@@ -1421,7 +1555,7 @@ class SourceVisitor implements AstVisitor {
}
visitSwitchDefault(SwitchDefault node) {
- visitNodes(node.labels, between: space, after: space);
+ _visitLabels(node.labels);
token(node.keyword);
token(node.colon);
@@ -1477,7 +1611,7 @@ class SourceVisitor implements AstVisitor {
}
visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
- visitDeclarationMetadata(node.metadata);
+ visitMetadata(node.metadata);
_simpleStatement(node, () {
visit(node.variables);
@@ -1522,11 +1656,28 @@ class SourceVisitor implements AstVisitor {
visit(node.name);
if (node.initializer == null) return;
- _visitAssignment(node.equals, node.initializer);
+ // 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);
}
visitVariableDeclarationList(VariableDeclarationList node) {
- visitDeclarationMetadata(node.metadata);
+ visitMetadata(node.metadata);
// Allow but try to avoid splitting between the type and name.
builder.startSpan();
@@ -1559,7 +1710,7 @@ class SourceVisitor implements AstVisitor {
soloZeroSplit();
visit(node.condition);
token(node.rightParenthesis);
- builder.unnest(now: false);
+ builder.unnest();
_visitLoopBody(node.body);
}
@@ -1589,35 +1740,17 @@ class SourceVisitor implements AstVisitor {
if (after != null) after();
}
- /// Visit metadata annotations on directives and declarations.
+ /// Visit metadata annotations on directives, declarations, and members.
///
/// 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);
- }
+ void visitMetadata(NodeList<Annotation> metadata) {
+ visitNodes(metadata, between: newline, after: newline);
}
/// Visits metadata annotations on parameters and type parameters.
///
- /// These are always on the same line as the parameter.
+ /// Unlike other annotations, these are allowed to stay on the same line as
+ /// the parameter.
void visitParameterMetadata(
NodeList<Annotation> metadata, void visitParameter()) {
if (metadata == null || metadata.isEmpty) {
@@ -1648,19 +1781,53 @@ class SourceVisitor implements AstVisitor {
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 MapLiteral) {
+ space();
+ } else {
+ var split = soloSplit();
+ if (rule != null) split.imply(rule);
+ }
+
+ visit(node.expression);
+ builder.endSpan();
+ builder.unnest();
+ }
+
/// Visits the `=` and the following expression in any place where an `=`
/// appears:
///
/// * Assignment
/// * Variable declaration
/// * Constructor initialization
- void _visitAssignment(Token equalsOperator, Expression rightHandSide) {
+ ///
+ /// If [nest] is true, an extra level of expression nesting is added after
+ /// the "=".
+ void _visitAssignment(Token equalsOperator, Expression rightHandSide,
+ {bool nest: false}) {
space();
token(equalsOperator);
+
+ if (nest) builder.nestExpression(now: true);
+
soloSplit(_assignmentCost(rightHandSide));
builder.startSpan();
visit(rightHandSide);
builder.endSpan();
+
+ if (nest) builder.unnest();
}
/// Visits a type parameter or type argument list.
@@ -1691,6 +1858,11 @@ class SourceVisitor implements AstVisitor {
builder.endRule();
}
+ /// Visits a sequence of labels before a statement or switch case.
+ void _visitLabels(NodeList<Label> labels) {
+ visitNodes(labels, between: newline, after: newline);
+ }
+
/// Visits a top-level function or method declaration.
///
/// The two AST node types are very similar but, alas, share no common
@@ -1698,7 +1870,7 @@ class SourceVisitor implements AstVisitor {
void _visitMemberDeclaration(
/* FunctionDeclaration|MethodDeclaration */ node,
/* FunctionExpression|MethodDeclaration */ function) {
- visitMemberMetadata(node.metadata);
+ visitMetadata(node.metadata as NodeList<Annotation>);
// Nest the signature in case we have to split between the return type and
// name.
@@ -1712,23 +1884,31 @@ class SourceVisitor implements AstVisitor {
visit(node.name);
builder.endSpan();
- // If the body is a block, we need to exit any nesting first. If it's an
- // expression, we want to wrap the nesting around that so that the body
- // gets nested farther.
- if (function.body is! ExpressionFunctionBody) builder.unnest();
+ TypeParameterList typeParameters;
+ if (node is FunctionDeclaration) {
+ typeParameters = node.functionExpression.typeParameters;
+ } else {
+ typeParameters = (node as MethodDeclaration).typeParameters;
+ }
- _visitBody(function.parameters, function.body);
+ _visitBody(typeParameters, function.parameters, function.body, () {
+ // If the body is a block, we need to exit nesting before we hit the body
+ // indentation, but we do want to wrap it around the parameters.
+ if (function.body is! ExpressionFunctionBody) builder.unnest();
+ });
+ // If it's an expression, we want to wrap the nesting around that so that
+ // the body gets nested farther.
if (function.body is ExpressionFunctionBody) builder.unnest();
}
/// 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 [beforeBody] is provided, it is invoked before the body is visited.
+ void _visitBody(TypeParameterList typeParameters,
+ FormalParameterList parameters, FunctionBody body,
+ [beforeBody()]) {
// 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 "=>"
@@ -1759,20 +1939,24 @@ class SourceVisitor implements AstVisitor {
builder.startLazyRule(new Rule(Cost.arrow));
}
- if (parameters != null) {
- builder.nestExpression();
- visit(parameters);
- builder.unnest();
+ // Start the nesting for the parameters here, so they wrap around the
+ // type parameters too, if any.
+ builder.nestExpression();
- if (afterParameters != null) afterParameters();
+ visit(typeParameters);
+ if (parameters != null) {
+ visitFormalParameterList(parameters, nestExpression: false);
}
+ builder.unnest();
+
+ if (beforeBody != null) beforeBody();
visit(body);
if (body is ExpressionFunctionBody) builder.unnest();
}
- /// Visits the body statement of a `for` or `for in` loop.
+ /// Visits the body statement of a `for`, `for in`, or `while` loop.
void _visitLoopBody(Statement body) {
if (body is EmptyStatement) {
// No space before the ";".
@@ -1781,17 +1965,17 @@ class SourceVisitor implements AstVisitor {
space();
visit(body);
} else {
- // Allow splitting in an expression-bodied for even though it's against
+ // Allow splitting in a statement-bodied loop 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.nestExpression(indent: 2, now: true);
+ builder.indent();
builder.startRule();
- split();
+ builder.split(nest: false, space: true);
visit(body);
builder.endRule();
- builder.unnest();
+ builder.unindent();
}
}
@@ -1831,11 +2015,16 @@ class SourceVisitor implements AstVisitor {
/// Visits the collection literal [node] whose body starts with [leftBracket],
/// ends with [rightBracket] and contains [elements].
+ ///
+ /// This is also used for argument lists with a trailing comma which are
+ /// considered "collection-like". In that case, [node] is `null`.
void _visitCollectionLiteral(TypedLiteral node, Token leftBracket,
Iterable<AstNode> elements, Token rightBracket,
[int cost]) {
- modifier(node.constKeyword);
- visit(node.typeArguments);
+ if (node != null) {
+ modifier(node.constKeyword);
+ visit(node.typeArguments);
+ }
// Don't allow splitting in an empty collection.
if (elements.isEmpty && rightBracket.precedingComments == null) {
@@ -1854,25 +2043,42 @@ class SourceVisitor implements AstVisitor {
_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 Rule.hard();
- 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);
+ var rule;
+ var lineRule;
+ if (preserveNewlines) {
+ // Newlines are significant, so we'll explicitly write those. Elements
+ // on the same line all share an argument-list-like rule that allows
+ // splitting between zero, one, or all of them. This is faster in long
+ // lists than using individual splits after each element.
+ lineRule = new TypeArgumentRule();
+ builder.startLazyRule(lineRule);
+ } else {
+ // Newlines aren't significant, so 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.
+ rule = new Rule.hard();
+ builder.startRule(rule);
+ }
+
for (var element in elements) {
if (element != elements.first) {
if (preserveNewlines) {
+ // See if the next element is on the next line.
if (_endLine(element.beginToken.previous) !=
_startLine(element.beginToken)) {
oneOrTwoNewlines();
+
+ // Start a new rule for the new line.
+ builder.endRule();
+ lineRule = new TypeArgumentRule();
+ builder.startLazyRule(lineRule);
} else {
- soloSplit();
+ lineRule.beforeArgument(split());
}
} else {
builder.split(nest: false, space: true);
@@ -1883,7 +2089,9 @@ class SourceVisitor implements AstVisitor {
visit(element);
// The comma after the element.
- if (element.endToken.next.lexeme == ",") token(element.endToken.next);
+ if (element.endToken.next.type == TokenType.COMMA) {
+ token(element.endToken.next);
+ }
builder.unnest();
}
@@ -1893,9 +2101,88 @@ class SourceVisitor implements AstVisitor {
// If there is a collection inside this one, it forces this one to split.
var force = _collectionSplits.removeLast();
+ // If the collection has a trailing comma, the user must want it to split.
+ if (elements.isNotEmpty &&
+ elements.last.endToken.next.type == TokenType.COMMA) {
+ force = true;
+ }
+
_endLiteralBody(rightBracket, ignoredRule: rule, forceSplit: force);
}
+ /// Writes [parameters], which is assumed to have a trailing comma after the
+ /// last parameter.
+ ///
+ /// Parameter lists with trailing commas are formatted differently from
+ /// regular parameter lists. They are treated more like collection literals.
+ ///
+ /// We don't reuse [_visitCollectionLiteral] here because there are enough
+ /// weird differences around optional parameters that it's easiest just to
+ /// give them their own method.
+ void _visitTrailingCommaParameterList(FormalParameterList parameters) {
+ // Can't have a trailing comma if there are no parameters.
+ assert(parameters.parameters.isNotEmpty);
+
+ _metadataRules.add(new MetadataRule());
+
+ // Always split the parameters.
+ builder.startRule(new Rule.hard());
+
+ token(parameters.leftParenthesis);
+
+ // Find the parameter immediately preceding the optional parameters (if
+ // there are any).
+ FormalParameter lastRequired;
+ for (var i = 0; i < parameters.parameters.length; i++) {
+ if (parameters.parameters[i] is DefaultFormalParameter) {
+ if (i > 0) lastRequired = parameters.parameters[i - 1];
+ break;
+ }
+ }
+
+ // If all parameters are optional, put the "[" or "{" right after "(".
+ if (parameters.parameters.first is DefaultFormalParameter) {
+ token(parameters.leftDelimiter);
+ }
+
+ // Process the parameters as a separate set of chunks.
+ builder = builder.startBlock(null);
+
+ for (var parameter in parameters.parameters) {
+ visit(parameter);
+
+ // The comma after the parameter.
+ if (parameter.endToken.next.type == TokenType.COMMA) {
+ token(parameter.endToken.next);
+ }
+
+ // If the optional parameters start after this one, put the delimiter
+ // at the end of its line.
+ if (parameter == lastRequired) {
+ space();
+ token(parameters.leftDelimiter);
+ lastRequired = null;
+ }
+
+ newline();
+ }
+
+ // Put comments before the closing ")", "]", or "}" inside the block.
+ var firstDelimiter =
+ parameters.rightDelimiter ?? parameters.rightParenthesis;
+ writePrecedingCommentsAndNewlines(firstDelimiter);
+ builder = builder.endBlock(null, forceSplit: true);
+ builder.endRule();
+
+ _metadataRules.removeLast();
+
+ // Now write the delimiter itself.
+ _writeText(firstDelimiter.lexeme, firstDelimiter.offset);
+ if (firstDelimiter != parameters.rightParenthesis) {
+ token(parameters.rightParenthesis);
+ }
+ }
+
/// Gets the cost to split at an assignment (or `:` in the case of a named
/// default value) with the given [rightHandSide].
///
@@ -1997,6 +2284,20 @@ class SourceVisitor implements AstVisitor {
_writeText(rightBracket.lexeme, rightBracket.offset);
}
+ /// Visits a list of configurations in an import or export directive.
+ void _visitConfigurations(NodeList<Configuration> configurations) {
+ if (configurations.isEmpty) return;
+
+ builder.startRule();
+
+ for (var configuration in configurations) {
+ split();
+ visit(configuration);
+ }
+
+ builder.endRule();
+ }
+
/// Visits a "combinator".
///
/// This is a [keyword] followed by a list of [nodes], with specific line
@@ -2042,13 +2343,9 @@ class SourceVisitor implements AstVisitor {
_collectionArgumentLists[leftBracket] = argumentList;
}
- /// 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()}) {
+ /// Writes the beginning of a brace-delimited body and handles indenting and
+ /// starting the rule used to split the contents.
+ void _beginBody(Token leftBracket, {bool space: false}) {
token(leftBracket);
// Indent the body.
@@ -2057,9 +2354,10 @@ class SourceVisitor implements AstVisitor {
// Split after the bracket.
builder.startRule();
builder.split(isDouble: false, nest: false, space: space);
+ }
- body();
-
+ /// Finishes the body started by a call to [_beginBody].
+ void _endBody(Token rightBracket, {bool space: false}) {
token(rightBracket, before: () {
// Split before the closing bracket character.
builder.unindent();
@@ -2115,13 +2413,6 @@ class SourceVisitor implements AstVisitor {
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.
@@ -2147,10 +2438,12 @@ class SourceVisitor implements AstVisitor {
Chunk zeroSplit() => builder.split();
/// Writes a single space split with its own rule.
- void soloSplit([int cost]) {
- builder.startRule(new Rule(cost));
+ Rule soloSplit([int cost]) {
+ var rule = new Rule(cost);
+ builder.startRule(rule);
split();
builder.endRule();
+ return rule;
}
/// Writes a zero-space split with its own rule.
@@ -2193,16 +2486,15 @@ class SourceVisitor implements AstVisitor {
}
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 = [];
+ // Edge case: The analyzer includes the "\n" in the script tag's lexeme,
+ // which confuses some of these calculations. We don't want to allow a
+ // blank line between the script tag and a following comment anyway, so
+ // just override the script tag's line.
+ if (token.previous.type == TokenType.SCRIPT_TAG) previousLine = tokenLine;
+
+ var comments = <SourceComment>[];
while (comment != null) {
var commentLine = _startLine(comment);
« no previous file with comments | « packages/dart_style/lib/src/rule/rule.dart ('k') | packages/dart_style/lib/src/whitespace.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698