| // 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:test/test.dart'; |
| |
| import '../common.dart'; |
| |
| void main() { |
| useDartfmt(); |
| |
| group('for loop', () { |
| test('should emit a full for loop with body', () { |
| final loop = ForLoop((b) { |
| b |
| ..initialize = declareVar('i', type: refer('int')).assign(literal(0)) |
| ..condition = refer('i').lessThan(literal(5)) |
| ..advance = refer('i').operatorUnaryPostfixIncrement() |
| ..body.addExpression(refer('print').call([refer('i')])); |
| }); |
| |
| expect(loop, equalsDart('for (int i = 0; i < 5; i++) {\n print(i);\n}')); |
| }); |
| |
| test('should emit a for loop with only init', () { |
| final loop = ForLoop((b) { |
| b.initialize = declareVar('i').assign(literal(1)); |
| }); |
| |
| expect(loop, equalsDart('for (var i = 1;;) {}')); |
| }); |
| |
| test('should emit a for loop with only condition', () { |
| final loop = ForLoop((b) { |
| b.condition = refer('keepGoing'); |
| }); |
| |
| expect(loop, equalsDart('for (; keepGoing;) {}')); |
| }); |
| |
| test('should emit a for loop with only advance', () { |
| final loop = ForLoop((b) { |
| b.advance = refer('i').operatorUnaryPostfixIncrement(); |
| }); |
| |
| expect(loop, equalsDart('for (;; i++) {}')); |
| }); |
| |
| test('should emit a for loop with label', () { |
| final loop = ForLoop((b) { |
| b.label = 'outer'; |
| }); |
| |
| expect(loop, equalsDart('outer: for (;;) {}')); |
| }); |
| }); |
| |
| group('for-in loop', () { |
| test('should emit a basic for-in loop', () { |
| final loop = ForInLoop((b) { |
| b |
| ..variable = refer('item') |
| ..object = refer('items'); |
| }); |
| |
| expect(loop, equalsDart('for (item in items) {}')); |
| }); |
| |
| test('should emit a labeled for-in loop', () { |
| final loop = ForInLoop((b) { |
| b |
| ..label = 'each' |
| ..variable = refer('item') |
| ..object = refer('items'); |
| }); |
| |
| expect(loop, equalsDart('each: for (item in items) {}')); |
| }); |
| |
| test('should emit an async for-in loop', () { |
| final loop = ForInLoop((b) { |
| b |
| ..async = true |
| ..variable = refer('event') |
| ..object = refer('stream'); |
| }); |
| |
| expect(loop, equalsDart('await for (event in stream) {}')); |
| }); |
| }); |
| |
| group('while loop', () { |
| test('should emit a basic while loop', () { |
| final loop = WhileLoop((b) { |
| b.condition = refer('running'); |
| }); |
| |
| expect(loop, equalsDart('while (running) {}')); |
| }); |
| |
| test('should emit a labeled while loop', () { |
| final loop = WhileLoop((b) { |
| b |
| ..label = 'mainLoop' |
| ..condition = refer('true'); |
| }); |
| |
| expect(loop, equalsDart('mainLoop: while (true) {}')); |
| }); |
| |
| test('should emit a do-while loop', () { |
| final loop = WhileLoop((b) { |
| b |
| ..doWhile = true |
| ..condition = refer('keepGoing') |
| ..body.addExpression(refer('process').call([])); |
| }); |
| |
| expect(loop, equalsDart('do {\n process();\n} while (keepGoing);')); |
| }); |
| |
| test('should emit a labeled do-while loop', () { |
| final loop = WhileLoop((b) { |
| b |
| ..doWhile = true |
| ..label = 'mainLoop' |
| ..condition = refer('keepGoing') |
| ..body.addExpression(refer('process').call([])); |
| }); |
| |
| expect( |
| loop, |
| equalsDart('mainLoop: do {\n process();\n} while (keepGoing);'), |
| ); |
| }); |
| }); |
| |
| group('conditional', () { |
| test('should emit a single if block', () { |
| final tree = Conditional( |
| (tree) => tree.add( |
| (b) => |
| b |
| ..condition = refer('x').equalTo(literal(1)) |
| ..body.addExpression(refer('print').call([literal('one')])), |
| ), |
| ); |
| |
| expect(tree, equalsDart('if (x == 1) {\n print(\'one\');\n}')); |
| }); |
| |
| test('should emit a single if-case block', () { |
| final tree = Conditional( |
| (tree) => tree.add( |
| (b) => |
| b |
| ..ifCase(object: refer('x'), pattern: refer('y')) |
| ..body.addExpression(ControlFlow.returnVoid), |
| ), |
| ); |
| |
| expect(tree, equalsDart('if (x case y) {\n return;\n}')); |
| }); |
| |
| test('should emit a single if-case block with guard', () { |
| final tree = Conditional( |
| (tree) => tree.add( |
| (b) => |
| b |
| ..ifCase( |
| object: refer('x'), |
| pattern: refer('y'), |
| guard: refer('z'), |
| ) |
| ..body.addExpression(ControlFlow.returnVoid), |
| ), |
| ); |
| |
| expect(tree, equalsDart('if (x case y when z) {\n return;\n}')); |
| }); |
| |
| test('should emit if-else if-else chain', () { |
| final tree = Conditional((tree) { |
| tree |
| ..add((b) { |
| b |
| ..condition = refer('x').equalTo(literal(1)) |
| ..body.addExpression(refer('print').call([literal('one')])); |
| }) |
| ..add((b) { |
| b |
| ..condition = refer('x').equalTo(literal(2)) |
| ..body.addExpression(refer('print').call([literal('two')])); |
| }) |
| ..addElse((body) { |
| body.addExpression(refer('print').call([literal('other')])); |
| }); |
| }); |
| |
| expect( |
| tree, |
| equalsDart(''' |
| if (x == 1) { |
| print('one'); |
| } else if (x == 2) { |
| print('two'); |
| } else { |
| print('other'); |
| }'''), |
| ); |
| }); |
| |
| test('should support elseIf', () { |
| final tree = Conditional((tree) { |
| tree.add((b) { |
| b |
| ..condition = refer('loggedIn') |
| ..body.addExpression(refer('showDashboard').call([])); |
| }); |
| }).elseIf((b) { |
| b |
| ..condition = refer('isGuest') |
| ..body.addExpression(refer('showGuest').call([])); |
| }); |
| |
| expect( |
| tree, |
| equalsDart(''' |
| if (loggedIn) { |
| showDashboard(); |
| } else if (isGuest) { |
| showGuest(); |
| }'''), |
| ); |
| }); |
| |
| test('should support orElse', () { |
| final tree = Conditional((b) { |
| b.add((b) { |
| b |
| ..condition = refer('ready') |
| ..body.addExpression(refer('start').call([])); |
| }); |
| }).orElse((body) { |
| body.addExpression(refer('log').call([literal('not ready')])); |
| }); |
| |
| expect( |
| tree, |
| equalsDart(''' |
| if (ready) { |
| start(); |
| } else { |
| log('not ready'); |
| }'''), |
| ); |
| }); |
| |
| test('should throw an argument error', () { |
| final tree = Conditional((tree) => tree.add((branch) {})); |
| |
| expect(() => tree.accept(DartEmitter()), throwsArgumentError); |
| }); |
| }); |
| |
| group('catch block', () { |
| test('should emit catch with default exception name', () { |
| final catchBlock = CatchBlock((b) => b..body.addExpression(literal(1))); |
| expect(catchBlock, equalsDart('catch (_) {\n 1;\n}')); |
| }); |
| |
| test('should emit catch with custom exception name', () { |
| final catchBlock = CatchBlock( |
| (b) => |
| b |
| ..exception = 'err' |
| ..body.addExpression(literal(2)), |
| ); |
| expect(catchBlock, equalsDart('catch (err) {\n 2;\n}')); |
| }); |
| |
| test('should emit catch with exception and stacktrace', () { |
| final catchBlock = CatchBlock( |
| (b) => |
| b |
| ..exception = 'e' |
| ..stacktrace = 's' |
| ..body.addExpression(refer('log').call([refer('s')])), |
| ); |
| expect(catchBlock, equalsDart('catch (e, s) {\n log(s);\n}')); |
| }); |
| |
| test('should emit an on block', () { |
| final catchBlock = CatchBlock( |
| (b) => |
| b |
| ..type = refer('FormatException') |
| ..body.addExpression(refer('print').call([refer('e')])), |
| ); |
| expect(catchBlock, equalsDart('on FormatException {\n print(e);\n}')); |
| }); |
| |
| test('should emit on-type catch block', () { |
| final catchBlock = CatchBlock( |
| (b) => |
| b |
| ..type = refer('FormatException') |
| ..stacktrace = 's' |
| ..body.addExpression(refer('print').call([refer('e')])), |
| ); |
| expect( |
| catchBlock, |
| equalsDart('on FormatException catch (_, s) {\n print(e);\n}'), |
| ); |
| }); |
| }); |
| |
| group('try-catch', () { |
| test('should throw if no catch handlers are defined', () { |
| expect( |
| () => TryCatch((b) => b.body.addExpression(literal(1))), |
| throwsArgumentError, |
| ); |
| }); |
| |
| test('should emit try/catch block', () { |
| final block = TryCatch((b) { |
| b.body.addExpression(refer('mightFail').call([])); |
| b.addCatch( |
| (cb) => cb.body.addExpression(refer('handleError').call([])), |
| ); |
| }); |
| |
| expect( |
| block, |
| equalsDart(''' |
| try { |
| mightFail(); |
| } catch (_) { |
| handleError(); |
| }'''), |
| ); |
| }); |
| |
| test('should emit an on block', () { |
| final block = TryCatch( |
| (b) => |
| b |
| ..body.addExpression(refer('mightFail').call([])) |
| ..addCatch( |
| (c) => |
| c |
| ..type = refer('HttpException') |
| ..body.addExpression(ControlFlow.rethrowVoid), |
| ), |
| ); |
| |
| expect( |
| block, |
| equalsDart(''' |
| try { |
| mightFail(); |
| } on HttpException { |
| rethrow; |
| } |
| '''), |
| ); |
| }); |
| |
| test('should emit try/on-type/catch with finally', () { |
| final block = TryCatch((b) { |
| b.body.addExpression(refer('mightFail').call([])); |
| b |
| ..addCatch( |
| (cb) => |
| cb |
| ..type = refer('HttpException') |
| ..exception = 'e' |
| ..stacktrace = 's' |
| ..body.addExpression(refer('print').call([refer('s')])), |
| ) |
| ..addFinally((fb) => fb.addExpression(refer('cleanup').call([]))); |
| }); |
| |
| expect( |
| block, |
| equalsDart(''' |
| try { |
| mightFail(); |
| } on HttpException catch (e, s) { |
| print(s); |
| } finally { |
| cleanup(); |
| }'''), |
| ); |
| }); |
| |
| test('should emit try with multiple catch clauses', () { |
| final block = TryCatch((b) { |
| b |
| ..body.addExpression(refer('foo').call([])) |
| ..addCatch( |
| (cb) => |
| cb |
| ..type = refer('FormatException') |
| ..exception = 'e1' |
| ..body.addExpression(refer('handleFormat').call([])), |
| ) |
| ..addCatch( |
| (cb) => |
| cb |
| ..type = refer('SocketException') |
| ..exception = 'e2' |
| ..body.addExpression(refer('handleSocket').call([])), |
| ) |
| ..addCatch((cb) => cb.body.addExpression(ControlFlow.rethrowVoid)); |
| }); |
| |
| expect( |
| block, |
| equalsDart(''' |
| try { |
| foo(); |
| } on FormatException catch (e1) { |
| handleFormat(); |
| } on SocketException catch (e2) { |
| handleSocket(); |
| } catch (_) { |
| rethrow; |
| }'''), |
| ); |
| }); |
| }); |
| |
| group('try-catch builder', () { |
| test('addCatch should append to handlers', () { |
| final builder = TryCatchBuilder(); |
| builder.body.addExpression(literal(0)); |
| builder.addCatch((cb) => cb.body.addExpression(literal(1))); |
| |
| final result = builder.build(); |
| expect(result.handlers, hasLength(1)); |
| expect( |
| result, |
| equalsDart(''' |
| try { |
| 0; |
| } catch (_) { |
| 1; |
| } |
| '''), |
| ); |
| }); |
| |
| test('addFinally should update handleAll', () { |
| final builder = |
| TryCatchBuilder() |
| ..body.addExpression(literal(0)) |
| ..addCatch((cb) => cb.body.addExpression(literal(1))) |
| ..addFinally((fb) => fb.addExpression(refer('done'))); |
| |
| final result = builder.build(); |
| expect(result.handleAll, isNotNull); |
| expect( |
| result, |
| equalsDart(''' |
| try { |
| 0; |
| } catch (_) { |
| 1; |
| } finally { |
| done; |
| } |
| '''), |
| ); |
| }); |
| }); |
| |
| group('switch statement', () { |
| test('should emit basic case with single statement body', () { |
| final stmt = SwitchStatement((b) { |
| b.value = refer('x'); |
| b.cases.add( |
| Case((cb) { |
| cb |
| ..pattern = literal(1) |
| ..body = refer('print').call([literal('one')]).statement; |
| }), |
| ); |
| }); |
| |
| expect( |
| stmt, |
| equalsDart(''' |
| switch (x) { |
| case 1: |
| print('one'); |
| }'''), |
| ); |
| }); |
| |
| test('should emit multiline case', () { |
| final stmt = SwitchStatement((b) { |
| b.value = refer('x'); |
| b.cases.add( |
| Case((cb) { |
| cb |
| ..pattern = literal(1) |
| ..body = Block.of([ |
| refer('print').call([literal('one')]).statement, |
| refer('print').call([literal('two')]).statement, |
| ControlFlow.breakVoid.statement, |
| ]); |
| }), |
| ); |
| }); |
| |
| expect( |
| stmt, |
| equalsDart(''' |
| switch (x) { |
| case 1: |
| print('one'); |
| print('two'); |
| break; |
| }'''), |
| ); |
| }); |
| |
| test('should emit multiple cases with separate bodies', () { |
| final stmt = SwitchStatement((b) { |
| b.value = refer('val'); |
| b.cases.addAll([ |
| Case( |
| (cb) => |
| cb |
| ..pattern = literal(1) |
| ..body = refer('print').call([literal('first')]).statement, |
| ), |
| Case( |
| (cb) => |
| cb |
| ..pattern = literal(2) |
| ..body = refer('print').call([literal('second')]).statement, |
| ), |
| ]); |
| }); |
| |
| expect( |
| stmt, |
| equalsDart(''' |
| switch (val) { |
| case 1: |
| print('first'); |
| case 2: |
| print('second'); |
| }'''), |
| ); |
| }); |
| |
| test('should emit fallthrough with null body', () { |
| final stmt = SwitchStatement((b) { |
| b.value = refer('foo'); |
| b.cases.addAll([ |
| Case( |
| (cb) => |
| cb |
| ..pattern = literal(0) |
| ..body = null, |
| ), |
| Case( |
| (cb) => |
| cb |
| ..pattern = literal(1) |
| ..body = refer('handleOne').call([]).statement, |
| ), |
| ]); |
| }); |
| |
| expect( |
| stmt, |
| equalsDart(''' |
| switch (foo) { |
| case 0: |
| case 1: |
| handleOne(); |
| }'''), |
| ); |
| }); |
| |
| test('should emit case with guard clause', () { |
| final stmt = SwitchStatement((b) { |
| b.value = refer('value'); |
| b.cases.add( |
| Case( |
| (cb) => |
| cb |
| ..pattern = literal(5) |
| ..guard = refer('value').greaterThan(literal(2)) |
| ..body = refer('print').call([literal('guarded')]).statement, |
| ), |
| ); |
| }); |
| |
| expect( |
| stmt, |
| equalsDart(''' |
| switch (value) { |
| case 5 when value > 2: |
| print('guarded'); |
| }'''), |
| ); |
| }); |
| |
| test('should emit case with label and body', () { |
| final stmt = SwitchStatement((b) { |
| b.value = refer('n'); |
| b.cases.add( |
| Case( |
| (cb) => |
| cb |
| ..label = 'start' |
| ..pattern = literal(0) |
| ..body = refer('begin').call([]).statement, |
| ), |
| ); |
| }); |
| |
| expect( |
| stmt, |
| equalsDart(''' |
| switch (n) { |
| start: |
| case 0: |
| begin(); |
| }'''), |
| ); |
| }); |
| |
| test('should emit labeled case fallthrough to another', () { |
| final stmt = SwitchStatement((b) { |
| b.value = refer('step'); |
| b.cases.addAll([ |
| Case( |
| (cb) => |
| cb |
| ..label = 'init' |
| ..pattern = literal('A') |
| ..body = null, |
| ), |
| Case( |
| (cb) => |
| cb |
| ..pattern = literal('B') |
| ..body = refer('continueProcess').call([]).statement, |
| ), |
| ]); |
| }); |
| |
| expect( |
| stmt, |
| equalsDart(''' |
| switch (step) { |
| init: |
| case 'A': |
| case 'B': |
| continueProcess(); |
| }'''), |
| ); |
| }); |
| |
| test('should emit default case', () { |
| final stmt = SwitchStatement((b) { |
| b.value = refer('cmd'); |
| b.cases.add( |
| Case.any(refer('log').call([literal('default')]).statement), |
| ); |
| }); |
| |
| expect( |
| stmt, |
| equalsDart(''' |
| switch (cmd) { |
| default: |
| log('default'); |
| }'''), |
| ); |
| }); |
| |
| test('should emit labeled default case', () { |
| final stmt = SwitchStatement((b) { |
| b.value = refer('cmd'); |
| b.cases.add( |
| Case.any( |
| refer('log').call([literal('default')]).statement, |
| label: 'label', |
| ), |
| ); |
| }); |
| |
| expect( |
| stmt, |
| equalsDart(''' |
| switch (cmd) { |
| label: |
| default: |
| log('default'); |
| }'''), |
| ); |
| }); |
| |
| test('should emit wildcard case', () { |
| final stmt = SwitchStatement((b) { |
| b.value = refer('cmd'); |
| b.cases.add( |
| Case( |
| (cb) => |
| cb |
| ..pattern = Expression.wildcard |
| ..body = refer('log').call([literal('wildcard')]).statement, |
| ), |
| ); |
| }); |
| |
| expect( |
| stmt, |
| equalsDart(''' |
| switch (cmd) { |
| case _: |
| log('wildcard'); |
| }'''), |
| ); |
| }); |
| |
| test( |
| 'should emit full mixed case block with guard, label, and default', |
| () { |
| final stmt = SwitchStatement((b) { |
| b.value = refer('x'); |
| b.cases.addAll([ |
| Case((cb) => cb..pattern = literal(-1)), |
| Case( |
| (cb) => |
| cb |
| ..pattern = literal(0) |
| ..body = ControlFlow.continueLabel('other').statement, |
| ), |
| Case( |
| (cb) => |
| cb |
| ..pattern = literal(1) |
| ..guard = refer('x').equalTo(literal(1)) |
| ..body = refer('handleOne').call([]).statement, |
| ), |
| Case( |
| (cb) => |
| cb |
| ..pattern = literal(2) |
| ..label = 'other' |
| ..body = Block.of([ |
| refer('printWarning').call([]).statement, |
| refer('handleNotOne').call([]).statement, |
| ]), |
| ), |
| Case.any(refer('defaultCase').call([]).statement), |
| ]); |
| }); |
| |
| expect( |
| stmt, |
| equalsDart(''' |
| switch (x) { |
| case -1: |
| case 0: |
| continue other; |
| case 1 when x == 1: |
| handleOne(); |
| other: |
| case 2: |
| printWarning(); |
| handleNotOne(); |
| default: |
| defaultCase(); |
| }'''), |
| ); |
| }, |
| ); |
| }); |
| |
| group('switch expression', () { |
| final matchValue = refer('value'); |
| |
| test('should generate a single-case switch expression', () { |
| final expr = SwitchExpression( |
| (b) => |
| b |
| ..value = matchValue |
| ..cases.add( |
| Case( |
| (b) => |
| b |
| ..pattern = refer('1') |
| ..body = refer("'one'"), |
| ), |
| ), |
| ); |
| |
| expect( |
| expr, |
| equalsDart(''' |
| switch (value) { |
| 1 => 'one', |
| } |
| '''), |
| ); |
| }); |
| |
| test('should support guard expressions in cases', () { |
| final expr = SwitchExpression( |
| (b) => |
| b |
| ..value = matchValue |
| ..cases.add( |
| Case( |
| (b) => |
| b |
| ..pattern = refer('x') |
| ..guard = refer('x > 5') |
| ..body = refer("'greater than 5'"), |
| ), |
| ), |
| ); |
| |
| expect( |
| expr, |
| equalsDart(''' |
| switch (value) { |
| x when x > 5 => 'greater than 5', |
| } |
| '''), |
| ); |
| }); |
| |
| test('should ignore label in switch expressions', () { |
| final expr = SwitchExpression( |
| (b) => |
| b |
| ..value = matchValue |
| ..cases.add( |
| Case( |
| (b) => |
| b |
| ..pattern = refer('2') |
| ..label = 'ignoredLabel' |
| ..body = refer("'two'"), |
| ), |
| ), |
| ); |
| |
| expect( |
| expr, |
| equalsDart(''' |
| switch (value) { |
| 2 => 'two', |
| } |
| '''), |
| ); |
| }); |
| |
| test('should generate wildcard case using Case.any', () { |
| final expr = SwitchExpression( |
| (b) => |
| b |
| ..value = matchValue |
| ..cases.add(Case.any(refer("'default'"))), |
| ); |
| |
| expect( |
| expr, |
| equalsDart(''' |
| switch (value) { |
| _ => 'default', |
| } |
| '''), |
| ); |
| }); |
| |
| test('should throw if case body is null', () { |
| expect( |
| () => SwitchExpression( |
| (b) => |
| b |
| ..value = matchValue |
| ..cases.add(Case((b) => b..pattern = refer('1'))), |
| ).accept(DartEmitter()), |
| throwsArgumentError, |
| ); |
| }); |
| |
| test('should generate multiple cases with mixed guards and default', () { |
| final expr = SwitchExpression( |
| (b) => |
| b |
| ..value = matchValue |
| ..cases.addAll([ |
| Case( |
| (b) => |
| b |
| ..pattern = refer('1') |
| ..body = refer("'one'"), |
| ), |
| Case( |
| (b) => |
| b |
| ..pattern = refer('2') |
| ..guard = refer('checkTwo()') |
| ..body = refer("'two'"), |
| ), |
| Case.any(refer("'fallback'")), |
| ]), |
| ); |
| |
| expect( |
| expr, |
| equalsDart(''' |
| switch (value) { |
| 1 => 'one', |
| 2 when checkTwo() => 'two', |
| _ => 'fallback', |
| } |
| '''), |
| ); |
| }); |
| |
| test('should work as an expression', () { |
| final expr = SwitchExpression( |
| (b) => |
| b |
| ..value = refer('otherValue') |
| ..cases.addAll([ |
| Case( |
| (c) => |
| c |
| ..pattern = refer('Enum').property('someType') |
| ..body = refer('someFunction').call([]), |
| ), |
| Case( |
| (c) => |
| c |
| ..pattern = refer('Enum').property('otherType') |
| ..body = refer('otherFunction').call([]), |
| ), |
| ]), |
| ); |
| |
| final variable = declareFinal('variable').assign(expr); |
| final parenthesized = expr.parenthesized; |
| final operation = expr.operatorAdd(refer('otherResult')); |
| |
| expect( |
| Block( |
| (b) => |
| b |
| ..addExpression(variable) |
| ..addExpression(parenthesized) |
| ..addExpression(operation), |
| ), |
| equalsDart(''' |
| final variable = switch (otherValue) { |
| Enum.someType => someFunction(), |
| Enum.otherType => otherFunction(), |
| }; |
| (switch (otherValue) { |
| Enum.someType => someFunction(), |
| Enum.otherType => otherFunction(), |
| }); |
| switch (otherValue) { |
| Enum.someType => someFunction(), |
| Enum.otherType => otherFunction(), |
| } + otherResult; |
| '''), |
| ); |
| }); |
| }); |
| } |