Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format class declarations. #1309

Merged
merged 5 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,4 @@ class Indent {

/// The ":" on a wrapped constructor initialization list.
static const constructorInitializer = 4;

/// A split name in a show or hide combinator.
static const combinatorName = 8;
}
53 changes: 48 additions & 5 deletions lib/src/front_end/ast_node_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,29 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
sequence.visit(directive);
}

var needsBlank = true;
for (var declaration in node.declarations) {
// Add a blank line before types with bodies.
var hasBody = declaration is ClassDeclaration ||
declaration is EnumDeclaration ||
declaration is ExtensionDeclaration;

if (hasBody) needsBlank = true;

if (needsBlank) sequence.addBlank();
munificent marked this conversation as resolved.
Show resolved Hide resolved
sequence.visit(declaration);

needsBlank = false;
if (hasBody) {
// Add a blank line after type declarations with bodies.
needsBlank = true;
} else if (declaration is FunctionDeclaration) {
// Add a blank line after non-empty block functions.
var body = declaration.functionExpression.body;
if (body is BlockFunctionBody) {
needsBlank = body.block.statements.isNotEmpty;
}
}
}
} else {
// Just formatting a single statement.
Expand Down Expand Up @@ -193,7 +214,26 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>

@override
void visitClassDeclaration(ClassDeclaration node) {
throw UnimplementedError();
createType(
node.metadata,
[
node.abstractKeyword,
node.baseKeyword,
node.interfaceKeyword,
node.finalKeyword,
node.sealedKeyword,
node.mixinKeyword,
],
node.classKeyword,
node.name,
node.typeParameters,
node.extendsClause,
node.withClause,
node.implementsClause,
node.nativeClause,
node.leftBracket,
node.members,
node.rightBracket);
}

@override
Expand Down Expand Up @@ -374,7 +414,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>

@override
void visitExtendsClause(ExtendsClause node) {
throw UnimplementedError();
assert(false, 'This node is handled by PieceFactory.createType().');
}

@override
Expand Down Expand Up @@ -600,7 +640,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>

@override
void visitImplementsClause(ImplementsClause node) {
throw UnimplementedError();
assert(false, 'This node is handled by PieceFactory.createType().');
}

@override
Expand Down Expand Up @@ -761,7 +801,10 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>

@override
void visitNativeClause(NativeClause node) {
throw UnimplementedError();
space();
token(node.nativeKeyword);
space();
visit(node.name);
}

@override
Expand Down Expand Up @@ -1235,7 +1278,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>

@override
void visitWithClause(WithClause node) {
throw UnimplementedError();
assert(false, 'This node is handled by PieceFactory.createType().');
}

@override
Expand Down
2 changes: 1 addition & 1 deletion lib/src/front_end/delimited_list_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:dart_style/src/front_end/comment_writer.dart';

import '../comment_type.dart';
import '../piece/list.dart';
import '../piece/piece.dart';
import 'comment_writer.dart';
import 'piece_factory.dart';

/// Incrementally builds a [ListPiece], handling commas, comments, and
Expand Down
158 changes: 126 additions & 32 deletions lib/src/front_end/piece_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';

import '../ast_extensions.dart';
import '../piece/adjacent.dart';
import '../piece/assign.dart';
import '../piece/block.dart';
import '../piece/clause.dart';
import '../piece/function.dart';
import '../piece/if.dart';
import '../piece/import.dart';
import '../piece/infix.dart';
import '../piece/list.dart';
import '../piece/piece.dart';
import '../piece/postfix.dart';
import '../piece/type.dart';
import 'ast_node_visitor.dart';
import 'comment_writer.dart';
import 'delimited_list_builder.dart';
Expand Down Expand Up @@ -58,24 +60,44 @@ mixin PieceFactory implements CommentWriter {
/// if (condition) {
/// } else {}
/// ```
void createBlock(Block block, {bool forceSplit = false}) {
token(block.leftBracket);
void createBody(Token leftBracket, List<AstNode> contents, Token rightBracket,
{bool forceSplit = false}) {
token(leftBracket);
var leftBracketPiece = pieces.split();

var sequence = SequenceBuilder(this);
for (var node in block.statements) {
for (var node in contents) {
sequence.visit(node);

// If the node has a non-empty braced body, then require a blank line
// between it and the next node.
if (node.hasNonEmptyBody) sequence.addBlank();
}

// Place any comments before the "}" inside the block.
sequence.addCommentsBefore(block.rightBracket);
sequence.addCommentsBefore(rightBracket);

token(block.rightBracket);
token(rightBracket);
var rightBracketPiece = pieces.take();

pieces.give(BlockPiece(
leftBracketPiece, sequence.build(), rightBracketPiece,
alwaysSplit: forceSplit || block.statements.isNotEmpty));
alwaysSplit: forceSplit || contents.isNotEmpty));
}

/// Creates a [BlockPiece] for a given [Block].
///
/// If [forceSplit] is `true`, then the block will split even if empty. This
/// is used, for example, with empty blocks in `if` statements followed by
/// `else` clauses:
///
/// ```
/// if (condition) {
/// } else {}
/// ```
void createBlock(Block block, {bool forceSplit = false}) {
createBody(block.leftBracket, block.statements, block.rightBracket,
forceSplit: forceSplit);
}

/// Creates a piece for a `break` or `continue` statement.
Expand Down Expand Up @@ -224,9 +246,8 @@ mixin PieceFactory implements CommentWriter {
token(keyword);
space();
visit(directive.uri);
var directivePiece = pieces.take();
var importPieces = [pieces.take()];

Piece? configurationsPiece;
if (directive.configurations.isNotEmpty) {
var configurations = <Piece>[];
for (var configuration in directive.configurations) {
Expand All @@ -235,44 +256,49 @@ mixin PieceFactory implements CommentWriter {
configurations.add(pieces.take());
}

configurationsPiece = PostfixPiece(configurations);
importPieces.add(PostfixPiece(configurations));
}

Piece? asClause;
if (asKeyword != null) {
pieces.split();
token(deferredKeyword, after: space);
token(asKeyword);
space();
visit(prefix);
asClause = PostfixPiece([pieces.take()]);
importPieces.add(PostfixPiece([pieces.take()]));
}

var combinators = <ImportCombinator>[];
for (var combinatorNode in directive.combinators) {
pieces.split();
token(combinatorNode.keyword);
var combinator = ImportCombinator(pieces.take());
combinators.add(combinator);

switch (combinatorNode) {
case HideCombinator(hiddenNames: var names):
case ShowCombinator(shownNames: var names):
for (var name in names) {
pieces.split();
token(name.token);
commaAfter(name);
combinator.names.add(pieces.take());
}
default:
throw StateError('Unknown combinator type $combinatorNode.');
if (directive.combinators.isNotEmpty) {
var combinators = <ClausePiece>[];
for (var combinatorNode in directive.combinators) {
pieces.split();
token(combinatorNode.keyword);
var combinatorKeyword = pieces.split();

switch (combinatorNode) {
case HideCombinator(hiddenNames: var names):
case ShowCombinator(shownNames: var names):
var parts = <Piece>[];
for (var name in names) {
pieces.split();
token(name.token);
commaAfter(name);
parts.add(pieces.take());
}

var combinator = ClausePiece(combinatorKeyword, parts);
combinators.add(combinator);
default:
throw StateError('Unknown combinator type $combinatorNode.');
}
}

importPieces.add(ClausesPiece(combinators));
}

token(directive.semicolon);

pieces.give(ImportPiece(
directivePiece, configurationsPiece, asClause, combinators));
pieces.give(AdjacentPiece(importPieces));
}

/// Creates a single infix operation.
Expand Down Expand Up @@ -382,6 +408,74 @@ mixin PieceFactory implements CommentWriter {
pieces.give(builder.build());
}

/// Creates a class, enum, extension, etc. declaration with a body containing
/// members.
void createType(
NodeList<Annotation> metadata,
List<Token?> modifiers,
Token keyword,
Token name,
TypeParameterList? typeParameters,
ExtendsClause? extendsClause,
WithClause? withClause,
ImplementsClause? implementsClause,
NativeClause? nativeClause,
Token leftBracket,
List<AstNode> members,
Token rightBracket) {
if (metadata.isNotEmpty) throw UnimplementedError('Type metadata.');
if (members.isNotEmpty) throw UnimplementedError('Type members.');

modifiers.forEach(modifier);
token(keyword);
space();
token(name);
visit(typeParameters);
var header = pieces.split();

var clauses = <ClausePiece>[];

void typeClause(Token keyword, List<AstNode> types) {
token(keyword);
var keywordPiece = pieces.split();

var typePieces = <Piece>[];
for (var type in types) {
visit(type);
commaAfter(type);
typePieces.add(pieces.split());
}

clauses.add(ClausePiece(keywordPiece, typePieces));
}

if (extendsClause != null) {
typeClause(extendsClause.extendsKeyword, [extendsClause.superclass]);
}

if (withClause != null) {
typeClause(withClause.withKeyword, withClause.mixinTypes);
}

if (implementsClause != null) {
typeClause(
implementsClause.implementsKeyword, implementsClause.interfaces);
}

ClausesPiece? clausesPiece;
if (clauses.isNotEmpty) {
clausesPiece =
ClausesPiece(clauses, allowLeadingClause: extendsClause != null);
}

visit(nativeClause);
space();
createBody(leftBracket, members, rightBracket);
var body = pieces.take();

pieces.give(TypePiece(header, clausesPiece, body));
}

/// Creates a [ListPiece] for a type argument or type parameter list.
void createTypeList(
Token leftBracket, Iterable<AstNode> elements, Token rightBracket) {
Expand Down
27 changes: 27 additions & 0 deletions lib/src/piece/adjacent.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2023, 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 '../back_end/code_writer.dart';
import 'piece.dart';

/// A simple piece that just writes its child pieces one after the other.
class AdjacentPiece extends Piece {
final List<Piece> _pieces;

AdjacentPiece(this._pieces);

@override
void format(CodeWriter writer, State state) {
for (var piece in _pieces) {
writer.format(piece);
}
}

@override
void forEachChild(void Function(Piece piece) callback) {
_pieces.forEach(callback);
}

@override
String toString() => 'Adjacent';
}
2 changes: 1 addition & 1 deletion lib/src/piece/block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class BlockPiece extends Piece {
}

@override
List<State> get additionalStates => const [State.split];
List<State> get additionalStates => [if (contents.isNotEmpty) State.split];

@override
void format(CodeWriter writer, State state) {
Expand Down
Loading