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 try statements. #1332

Merged
merged 6 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions lib/src/front_end/ast_node_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,12 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>

@override
void visitCatchClause(CatchClause node) {
throw UnimplementedError();
assert(false, 'This node is handled by visitTryStatement().');
}

@override
void visitCatchClauseParameter(CatchClauseParameter node) {
throw UnimplementedError();
token(node.name);
}

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

@override
void visitTryStatement(TryStatement node) {
throw UnimplementedError();
createTry(node);
}

@override
Expand Down
87 changes: 87 additions & 0 deletions lib/src/front_end/piece_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import '../piece/infix.dart';
import '../piece/list.dart';
import '../piece/piece.dart';
import '../piece/postfix.dart';
import '../piece/try.dart';
import '../piece/type.dart';
import 'ast_node_visitor.dart';
import 'comment_writer.dart';
Expand Down Expand Up @@ -235,6 +236,92 @@ mixin PieceFactory implements CommentWriter {
pieces.give(FunctionPiece(returnTypePiece, parametersPiece));
}

/// Creates a [TryPiece] for try statement.
void createTry(TryStatement tryStatement) {
var piece = TryPiece();

token(tryStatement.tryKeyword);
var tryHeader = pieces.split();
createBlock(tryStatement.body);
var tryBlock = pieces.split();
piece.add(tryHeader, tryBlock);

for (var i = 0; i < tryStatement.catchClauses.length; i++) {
var catchClause = tryStatement.catchClauses[i];

Piece? onPiece;
if (catchClause.onKeyword case var onKeyword?) {
token(onKeyword, after: space);
visit(catchClause.exceptionType);
onPiece = pieces.split();
}

Piece? catchPiece;
if (catchClause.catchKeyword case var catchKeyword?) {
token(catchKeyword);
var catchKeywordPiece = pieces.split();

var builder = DelimitedListBuilder(this);
builder.leftBracket(catchClause.leftParenthesis!);
if (catchClause.exceptionParameter case var exceptionParameter?) {
builder.visit(exceptionParameter);
}
if (catchClause.stackTraceParameter case var stackTraceParameter?) {
builder.visit(stackTraceParameter);
}
builder.rightBracket(catchClause.rightParenthesis!);

catchPiece = AdjacentPiece(
[catchKeywordPiece, builder.build()],
spaceAfter: [catchKeywordPiece],
);
}

if (onPiece != null && catchPiece != null) {
pieces.give(AdjacentPiece(
[onPiece, catchPiece],
spaceAfter: [onPiece],
));
} else if (onPiece != null) {
pieces.give(onPiece);
} else if (catchPiece != null) {
pieces.give(catchPiece);
}
var catchClauseHeader = pieces.split();
munificent marked this conversation as resolved.
Show resolved Hide resolved

// Edge case: When there's another catch/on/finally after this one, we
// want to force the block to split even if it's empty.
//
// ```
// try {
// ..
// } on Foo {
// } finally Bar {
// body;
// }
// ```
var forceSplit = i < tryStatement.catchClauses.length - 1 ||
tryStatement.finallyBlock != null;
createBlock(
catchClause.body,
forceSplit: forceSplit,
);
var catchClauseBody = pieces.split();

piece.add(catchClauseHeader, catchClauseBody);
}

if (tryStatement.finallyBlock case var finallyBlock?) {
token(tryStatement.finallyKeyword);
var finallyHeader = pieces.split();
createBlock(finallyBlock);
var finallyBody = pieces.split();
piece.add(finallyHeader, finallyBody);
}

pieces.give(piece);
}

// TODO(tall): Generalize this to work with if elements too.
/// Creates a piece for a chain of if-else-if... statements.
void createIf(IfStatement ifStatement) {
Expand Down
7 changes: 6 additions & 1 deletion lib/src/piece/adjacent.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ import 'piece.dart';
class AdjacentPiece extends Piece {
final List<Piece> _pieces;

AdjacentPiece(this._pieces);
/// The pieces that should have a space after them.
final Set<Piece> _spaceAfter;

AdjacentPiece(this._pieces, {List<Piece> spaceAfter = const []})
: _spaceAfter = spaceAfter.toSet();

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

Expand Down
48 changes: 48 additions & 0 deletions lib/src/piece/try.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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 piece for a try statement.
class TryPiece extends Piece {
final List<_TrySectionPiece> _sections = [];

void add(Piece header, Piece block) {
_sections.add(_TrySectionPiece(header, block));
}

@override
void format(CodeWriter writer, State state) {
for (var i = 0; i < _sections.length; i++) {
var section = _sections[i];
writer.format(section.header);
writer.space();
writer.format(section.body);

// Adds the space between the end of a block and the next header.
if (i < _sections.length - 1) {
writer.space();
}
}
}

@override
void forEachChild(void Function(Piece piece) callback) {
for (var section in _sections) {
callback(section.header);
callback(section.body);
}
}
}

/// A section for a try.
///
/// This could be a try, an on/catch, or a finally section. They are all
/// formatted similarly.
class _TrySectionPiece {
final Piece header;
final Piece body;

_TrySectionPiece(this.header, this.body);
}
113 changes: 113 additions & 0 deletions test/statement/try.stmt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
40 columns |
>>> Try and catch exception.
try {
doSomething();
} catch (e) {
print(e);
}
<<<
try {
doSomething();
} catch (e) {
print(e);
}
>>> Try and catch exception with on clause.
try{
doSomething();
}on Exception catch (e){
print(e);
}
<<<
try {
doSomething();
} on Exception catch (e) {
print(e);
}
>>> Try and catch exception with on clause and stack trace.
try{
doSomething();
}on Exception catch (e, s){
print(e);
}
<<<
try {
doSomething();
} on Exception catch (e, s) {
print(e);
}
>>> Split empty catch if there is a finally.
try {;} catch (err) {} finally {;}
<<<
try {
;
} catch (err) {
} finally {
;
}
>>> Split empty on if there is a finally.
try {;} on Exception {} finally {;}
<<<
try {
;
} on Exception {
} finally {
;
}
>>> Split all empty catches if there is a finally.
try {;} catch (err1) {} catch (err2) {} catch (err3) {} finally {;}
<<<
try {
;
} catch (err1) {
} catch (err2) {
} catch (err3) {
} finally {
;
}
>>> Split leading empty catches if there are multiple.
try {;} catch (err1) {} catch (err2) {} catch (err3) {}
<<<
try {
;
} catch (err1) {
} catch (err2) {
} catch (err3) {}
>>> Split empty catch with on clause if there is a finally.
try {
doSomething();
} on Exception catch (e) {} finally {
cleanupSomething();
}
<<<
try {
doSomething();
} on Exception catch (e) {
} finally {
cleanupSomething();
}
>>> Split multiple on clauses.
try {
doSomething();
} on FooException {} on BarException {
doSomething();
}
<<<
try {
doSomething();
} on FooException {
} on BarException {
doSomething();
}
>>> Don't split try.
try {
doSomething();
} on FooException {} on BarException {
doSomething();
}
<<<
try {
doSomething();
} on FooException {
} on BarException {
doSomething();
}
Loading