blob: 143c8b0351915007f1c2579839bcee5b629b3b19 [file] [edit]
// Copyright (c) 2025, 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:code_builder/code_builder.dart';
import 'package:code_builder/src/specs/expression.dart';
import 'package:test/test.dart';
import '../../common.dart';
void main() {
useDartfmt();
group('control expression', () {
// general
test('should insert a single body element', () {
final expr = ControlExpression('test', body: [literal(1)]);
expect(expr, equalsDart('test (1)'));
});
test('should insert multiple body elements with a separator', () {
final expr = ControlExpression(
'test',
body: [literal(1), literal(2)],
separator: ',',
);
expect(expr, equalsDart('test (1, 2)'));
});
test('should throw on multiple body elements w/o a separator', () {
expect(
() => ControlExpression(
'test',
body: [literal(1), literal(2)],
// separator: null // default
).accept(DartEmitter()),
throwsArgumentError,
);
});
test('should not wrap body in parens if parenthesised is false', () {
final expr = ControlExpression(
'else',
body: [refer('block')],
parenthesised: false,
);
expect(expr, equalsDart('else block'));
});
test('should still insert separator for nulls in body', () {
final expr = ControlExpression(
'for',
body: [null, refer('middle'), null],
separator: ';',
);
expect(expr, equalsDart('for (; middle;)'));
});
test('should allow null/empty body and still emit control keyword', () {
expect(const ControlExpression('while'), equalsDart('while'));
expect(const ControlExpression('while', body: []), equalsDart('while'));
});
// specific constructors
test('should emit an if statement', () {
expect(
ControlExpression.ifStatement(literal(1).equalTo(literal(2))),
equalsDart('if (1 == 2)'),
);
});
test('should emit an else statement', () {
expect(ControlExpression.elseStatement(null), equalsDart('else'));
});
test('should emit an else if statement', () {
expect(
ControlExpression.elseStatement(
ControlExpression.ifStatement(literal(false)),
),
equalsDart('else if (false)'),
);
});
test('should emit a for loop with all parts', () {
expect(
ControlExpression.forLoop(
declareVar('i', type: refer('int')).assign(literal(0)),
refer('i').lessThan(literal(10)),
refer('i').operatorUnaryPostfixIncrement(),
),
equalsDart('for (int i = 0; i < 10; i++)'),
);
});
test('should emit a for loop with only init', () {
expect(
ControlExpression.forLoop(
declareVar('i', type: refer('int')).assign(literal(0)),
null,
null,
),
equalsDart('for (int i = 0;;)'),
);
});
test('should emit a for loop with only condition', () {
expect(
ControlExpression.forLoop(null, refer('running'), null),
equalsDart('for (; running;)'),
);
});
test('should emit a for loop with only advance', () {
expect(
ControlExpression.forLoop(
null,
null,
refer('i').operatorUnaryPostfixIncrement(),
),
equalsDart('for (;; i++)'),
);
});
test('should emit a for loop with all null body entries', () {
expect(
ControlExpression.forLoop(null, null, null),
equalsDart('for (;;)'),
);
});
test('should emit a for-in loop', () {
expect(
ControlExpression.forInLoop(refer('x'), refer('list')),
equalsDart('for (x in list)'),
);
});
test('should emit an await for loop', () {
expect(
ControlExpression.awaitForLoop(refer('x'), refer('stream')),
equalsDart('await for (x in stream)'),
);
});
test('should emit a while loop', () {
expect(
ControlExpression.whileLoop(literal(true)),
equalsDart('while (true)'),
);
});
test('should emit a do statement', () {
expect(ControlExpression.doStatement, equalsDart('do'));
});
test('should emit a try statement', () {
expect(ControlExpression.tryStatement, equalsDart('try'));
});
test('should emit a catch statement with only error', () {
expect(ControlExpression.catchStatement('e'), equalsDart('catch (e)'));
});
test('should emit a catch statement with error and stacktrace', () {
expect(
ControlExpression.catchStatement('e', 's'),
equalsDart('catch (e, s)'),
);
});
test('should emit an on statement', () {
expect(
ControlExpression.onStatement(refer('FormatException')),
equalsDart('on FormatException'),
);
});
test('should emit an on statement with catch', () {
expect(
ControlExpression.onStatement(
refer('FormatException'),
ControlExpression.catchStatement('e'),
),
equalsDart('on FormatException catch (e)'),
);
});
test('should emit a finally statement', () {
expect(ControlExpression.finallyStatement, equalsDart('finally'));
});
test('should emit a switch expression', () {
final expression = ControlExpression.switchStatement(refer('object'));
expect(expression, equalsDart('switch (object)'));
});
});
group('expression control-flow', () {
test('should emit a yield expression', () {
final expr = refer('value').yielded;
expect(expr, equalsDart('yield value'));
});
test('should emit a yield* expression', () {
final expr = refer('stream').yieldStarred;
expect(expr, equalsDart('yield* stream'));
});
test('should emit return statement', () {
expect(ControlFlow.returnVoid, equalsDart('return'));
});
test('should emit break statement', () {
expect(ControlFlow.breakVoid, equalsDart('break'));
});
test('should emit continue statement', () {
expect(ControlFlow.continueVoid, equalsDart('continue'));
});
test('should emit labeled break statement', () {
final expr = ControlFlow.breakLabel('loop1');
expect(expr, equalsDart('break loop1'));
});
test('should emit labeled continue statement', () {
final expr = ControlFlow.continueLabel('loop2');
expect(expr, equalsDart('continue loop2'));
});
test('should emit a rethrow statement', () {
expect(ControlFlow.rethrowVoid, equalsDart('rethrow'));
});
test('should emit an if-case expression', () {
final expr = ControlFlow.ifCase(
object: refer('value'),
pattern: refer('int'),
);
expect(expr, equalsDart('value case int'));
});
test('should emit an if-case expression with a guard', () {
final expr = ControlFlow.ifCase(
object: refer('value'),
pattern: refer('int'),
guard: refer('value').greaterThan(literal(0)),
);
expect(expr, equalsDart('value case int when value > 0'));
});
test('should emit a collection-if expression', () {
final expr = ControlFlow.collectionIf(
condition: literalTrue,
value: refer('value'),
);
expect(expr, equalsDart('if (true) value'));
});
test('should emit a collection-else expression', () {
final expr = ControlFlow.collectionElse(value: refer('value'));
expect(expr, equalsDart('else value'));
});
test('should emit a collection-else-if expression', () {
final expr = ControlFlow.collectionElse(
condition: literalTrue,
value: refer('value'),
);
expect(expr, equalsDart('else if (true) value'));
});
test('should chain collection-if and else in list', () {
Expression expr(bool includeStatic) => literalList([
if (includeStatic) refer('always'),
ControlFlow.collectionIf(condition: literalTrue, value: refer('value')),
ControlFlow.collectionElse(value: refer('other')),
if (includeStatic) refer('here'),
]);
expect(expr(false), equalsDart('[if (true) value else other]'));
expect(
expr(true),
equalsDart('[always, if (true) value else other, here, ]'),
);
});
test('should chain collection-if and else in set', () {
Expression expr(bool includeStatic) => literalSet({
if (includeStatic) refer('always'),
ControlFlow.collectionIf(condition: literalTrue, value: refer('value')),
ControlFlow.collectionElse(
condition: literalFalse,
value: refer('thing'),
),
ControlFlow.collectionElse(value: refer('other')),
if (includeStatic) refer('here'),
});
expect(
expr(false),
equalsDart('{if (true) value else if (false) thing else other}'),
);
expect(
expr(true),
equalsDart('''
{always, if (true) value else if (false) thing else other, here, }'''),
);
});
test('should chain collection-if and else in map', () {
Expression expr(bool includeStatic) => literalMap({
if (includeStatic) refer('always'): refer('here'),
ControlFlow.collectionIf(
condition: literalTrue,
value: refer('key'),
): refer('value'),
ControlFlow.collectionElse(
condition: literalFalse,
value: refer('key2'),
): refer('value2'),
ControlFlow.collectionElse(value: refer('key3')): refer('value3'),
if (includeStatic) refer('also'): refer('here'),
});
expect(
expr(false),
equalsDart('''
{if (true) key: value else if (false) key2: value2 else key3: value3}'''),
);
expect(
expr(true),
equalsDart('''
{always: here,
if (true) key: value
else if (false) key2: value2
else key3: value3,
also: here,
}'''),
);
});
test('should emit a collection-for loop', () {
final expr = ControlFlow.collectionFor(
value: refer('i'),
initialize: declareVar('i').assign(literal(0)),
condition: refer('i').lessThan(literal(5)),
advance: refer('i').operatorUnaryPostfixIncrement(),
);
expect(expr, equalsDart('for (var i = 0; i < 5; i++) i'));
});
test('should emit a collection-for-in loop', () {
final expr = ControlFlow.collectionForIn(
value: refer('i'),
identifier: declareFinal('i'),
expression: refer('iterable'),
);
expect(expr, equalsDart('for (final i in iterable) i'));
});
test('should emit a list with nested collection-for/if', () {
final expr = literalList([
ControlFlow.collectionForIn(
identifier: declareFinal('i'),
expression: refer('iterable'),
value: ControlFlow.collectionIf(
condition: refer('i').property('something'),
value: refer('i'),
),
),
]);
expect(
expr,
equalsDart('[for (final i in iterable) if (i.something) i]'),
);
});
test('should emit a set with nested for-in and if/else', () {
final expr = literalSet({
ControlFlow.collectionForIn(
identifier: declareFinal('x'),
expression: refer('items'),
value: ControlFlow.collectionIf(
condition: refer('x').property('valid'),
value: refer('x'),
),
),
ControlFlow.collectionElse(value: refer('fallback')),
});
expect(
expr,
equalsDart('{for (final x in items) if (x.valid) x else fallback}'),
);
});
test('should emit a map wth nested for-in and if/else', () {
final expr = literalMap({
ControlFlow.collectionForIn(
identifier: declareFinal('x'),
expression: refer('items'),
value: ControlFlow.collectionIf(
condition: refer('x').property('valid'),
value: refer('key'),
),
): refer('x'),
ControlFlow.collectionElse(value: refer('key2')): refer(
'fix',
).call([refer('x')]),
});
expect(
expr,
equalsDart('''
{for (final x in items) if (x.valid) key: x else key2: fix(x)}'''),
);
});
test('should not chain else if used after static value', () {
final expr = literalList([
refer('static'),
ControlFlow.collectionElse(value: refer('shouldNotChain')),
]);
expect(expr, equalsDart('[static, else shouldNotChain, ]'));
});
test('should not chain for(in) if value is not chainable', () {
final expr = literalList([
ControlFlow.collectionForIn(
identifier: declareFinal('x'),
expression: refer('items'),
value: refer('x'),
),
ControlFlow.collectionElse(value: refer('shouldNotChain')),
]);
expect(
expr,
equalsDart('[for (final x in items) x, else shouldNotChain, ]'),
);
});
});
group('expression helpers', () {
test('should build a while loop with loopWhile', () {
final expr = refer('isRunning').loopWhile((b) {
b.addExpression(refer('tick').call([]));
});
expect(
expr,
equalsDart('''
while (isRunning) {
tick();
}'''),
);
});
test('should build a do-while loop with loopDoWhile', () {
final expr = refer('conditionMet').loopDoWhile((b) {
b.addExpression(refer('step').call([]));
});
expect(
expr,
equalsDart('''
do {
step();
} while (conditionMet);'''),
);
});
test('should build a for-in loop with loopForIn', () {
final expr = refer('item').loopForIn(refer('items'), (b) {
b.addExpression(refer('print').call([refer('item')]));
});
expect(
expr,
equalsDart('''
for (item in items) {
print(item);
}'''),
);
});
test('should build if statement with ifThen', () {
final tree = refer('isTrue').ifThen((b) {
b.addExpression(refer('execute').call([]));
});
expect(
tree,
equalsDart('''
if (isTrue) {
execute();
}'''),
);
});
test('should support ifThenReturn', () {
final expr = refer('isTrue').ifThenReturn();
expect(
expr,
equalsDart('''
if (isTrue) return'''),
);
});
test('should support ifThenReturn with value', () {
final expr = refer('isTrue').ifThenReturn(refer('value'));
expect(
expr,
equalsDart('''
if (isTrue) return value'''),
);
});
test('should support chaining', () {
final tree = literal(1)
.equalTo(literal(2))
.ifThen((b) {
b.addExpression(refer('print').call([literal('Bad')]));
})
.elseIf((b) {
b
..condition = literal(2).equalTo(literal(2))
..body.addExpression(refer('print').call([literal('Good')]));
})
.orElse((b) {
b.addExpression(refer('print').call([literal('What?')]));
});
expect(
tree,
equalsDart('''
if (1 == 2) {
print('Bad');
} else if (2 == 2) {
print('Good');
} else {
print('What?');
}'''),
);
});
});
}