Skip to content

Commit

Permalink
Format try statements. (#1332)
Browse files Browse the repository at this point in the history
* Format try statements.

* Add try comment tests just in case.

* Use token's API to add spaces.

* Remove unnecessary override after rebase.

* Put the exception into a delimited list and everything else into an adjacentpiece.

* Remove unnecessary space.
  • Loading branch information
kallentu authored Dec 5, 2023
1 parent 8b1f24a commit 214f289
Show file tree
Hide file tree
Showing 6 changed files with 383 additions and 4 deletions.
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();

// 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

0 comments on commit 214f289

Please sign in to comment.