diff --git a/pkgs/code_builder/.gitignore b/pkgs/code_builder/.gitignore index feb089dd9e..ae7af3fc02 100644 --- a/pkgs/code_builder/.gitignore +++ b/pkgs/code_builder/.gitignore @@ -2,4 +2,4 @@ .dart_tool .packages .pub -pubspec.lock +pubspec.lock \ No newline at end of file diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index f8459a3226..c4e1a7c34b 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -1,9 +1,58 @@ -## 4.10.2-wip +## 4.11.0-wip * Upgrade `dart_style` and `source_gen` to remove `package:macros` dependency. + * Require Dart `^3.6.0` due to the upgrades. -* Support `Expression.newInstanceNamed` with empty name -* Fixed bug: Fields declared with `static` and `external` now produce code with correct order + +* Support `Expression.newInstanceNamed` with empty name. + +* Fixed bug: Fields declared with `static` and `external` now produce code with correct order. + +* Add `ControlFlow` extension on `Expression` to support control-flow helper functions. + * Add `Expression.yielded` (via ext.) + * Add `Expression.yieldStarred` (via ext.) + * Add `Expression.ifThen` (via ext.) + * Add `Expression.ifThenReturn` (via ext.) + * Add `Expression.loopWhile` (via ext.) + * Add `Expression.loopDoWhile` (via ext.) + * Add `Expression.loopForIn` (via ext.) + +* Add static keyword helper functions to `ControlFlow`. + * Add `ControlFlow.breakVoid` + * Add `ControlFlow.breakLabel` + * Add `ControlFlow.continueVoid` + * Add `ControlFlow.continueLabel` + * Add `ControlFlow.returnVoid` + * Add `ControlFlow.rethrowVoid` + +* Support emitting collection control-flow expressions via static methods on `ControlFlow`. + * Add `ControlFlow.collectionIf` + * Add `ControlFlow.collectionElse` + * Add `ControlFlow.collectionFor` + * Add `ControlFlow.collectionForIn` + * Add `ControlFlow.ifCase` + * Update literal collection visitors to support chaining. + +* Support emitting control-flow loops. + * Add `ForLoop` and `ForLoopBuilder` for traditional `for` loops. + * Add `ForInLoop` and `ForInLoopBuilder` for `for-in` and `await-for` loops. + * Add `WhileLoop` and `WhileLoopBuilder` for `while` and `do-while` loops. + +* Support emitting `if` statements and `if`/`else if`/`else` trees. + * Add `Conditional` and `ConditionalBuilder` for conditional trees. + * Add `BranchBuilder` for builder `Conditional` branches. + +* Support emitting `try`/`catch`/`finally` blocks. + * Add `CatchBlock` and `CatchBlockBuilder` for catch clauses. + * Add `TryCatch` and `TryCatchBuilder` for try/catch blocks. + +* Support emitting `switch` statements and expressions. + * Add `Case` and `CaseBuilder` for creating `switch` cases. + * Add `SwitchExpression` and `SwitchExpressionBuilder` for `switch` expressions. + * Add `SwitchStatement` and `SwitchStatementBuilder` for `switch` statements. + * Add `Expression.wildcard` static constant for wildcard (`_`) expressions. + +* Simplify usage examples on the README. ## 4.10.1 @@ -156,7 +205,7 @@ void main() { ## 3.4.1 * Fix confusing mismatch description from `equalsDart`. - https://github.com/dart-lang/code_builder/issues/293 + ## 3.4.0 @@ -209,7 +258,6 @@ void main() { * `Expression.asA` is now wrapped with parenthesis so that further calls may be made on it as an expression. - ## 3.1.0 * Added `Expression.asA` for creating explicit casts: @@ -256,7 +304,7 @@ void main() { ## 2.4.0 -* Add `equalTo`, `notEqualTo`, `greaterThan`, `lessThan`, `greateOrEqualTo`, and +* Add `equalTo`, `notEqualTo`, `greaterThan`, `lessThan`, `greaterOrEqualTo`, and `lessOrEqualTo` to `Expression`. ## 2.3.0 @@ -346,7 +394,7 @@ void main() { ``` * Added `nullSafeProperty` to `Expression` to access properties with `?.` -* Added `conditional` to `Expression` to use the ternary operator `? : ` +* Added `conditional` to `Expression` to use the ternary operator `? :` * Methods taking `positionalArguments` accept `Iterable` * **BUG FIX**: Parameters can take a `FunctionType` as a `type`. `Reference.type` now returns a `Reference`. Note that this change is @@ -572,7 +620,7 @@ final animal = new Class((b) => b ## 2.0.0-alpha * Complete re-write to not use `package:analyzer`. -* Code generation now properly uses the _builder_ pattern (via `built_value`). +* Code generation now properly uses the *builder* pattern (via `built_value`). * See examples and tests for details. ## 1.0.4 @@ -606,38 +654,38 @@ that the entire Dart language is buildable with our API, though. **Contributions are welcome.** -- Exposed `uri` in `ImportBuilder`, `ExportBuilder`, and `Part[Of]Builder`. +* Exposed `uri` in `ImportBuilder`, `ExportBuilder`, and `Part[Of]Builder`. ## 1.0.0-beta+7 -- Added `ExpressionBuilder#ternary`. +* Added `ExpressionBuilder#ternary`. ## 1.0.0-beta+6 -- Added `TypeDefBuilder`. -- Added `FunctionParameterBuilder`. -- Added `asAbstract` to various `MethodBuilder` constructors. +* Added `TypeDefBuilder`. +* Added `FunctionParameterBuilder`. +* Added `asAbstract` to various `MethodBuilder` constructors. ## 1.0.0-beta+5 -- Re-published the package without merge conflicts. +* Re-published the package without merge conflicts. ## 1.0.0-beta+4 -- Renamed `PartBuilder` to `PartOfBuilder`. -- Added a new class, `PartBuilder`, to represent `part '...dart'` directives. -- Added the `HasAnnotations` interface to all library/part/directive builders. -- Added `asFactory` and `asConst` to `ConstructorBuilder`. -- Added `ConstructorBuilder.redirectTo` for a redirecting factory constructor. -- Added a `name` getter to `ReferenceBuilder`. -- Supplying an empty constructor name (`''`) is equivalent to `null` (default). -- Automatically encodes string literals with multiple lines as `'''`. -- Added `asThrow` to `ExpressionBuilder`. -- Fixed a bug that prevented `FieldBuilder` from being used at the top-level. +* Renamed `PartBuilder` to `PartOfBuilder`. +* Added a new class, `PartBuilder`, to represent `part '...dart'` directives. +* Added the `HasAnnotations` interface to all library/part/directive builders. +* Added `asFactory` and `asConst` to `ConstructorBuilder`. +* Added `ConstructorBuilder.redirectTo` for a redirecting factory constructor. +* Added a `name` getter to `ReferenceBuilder`. +* Supplying an empty constructor name (`''`) is equivalent to `null` (default). +* Automatically encodes string literals with multiple lines as `'''`. +* Added `asThrow` to `ExpressionBuilder`. +* Fixed a bug that prevented `FieldBuilder` from being used at the top-level. ## 1.0.0-beta+3 -- Added support for `genericTypes` parameter for `ExpressionBuilder#invoke`: +* Added support for `genericTypes` parameter for `ExpressionBuilder#invoke`: ```dart expect( @@ -650,7 +698,7 @@ expect( ); ``` -- Added a `castAs` method to `ExpressionBuilder`: +* Added a `castAs` method to `ExpressionBuilder`: ```dart expect( @@ -663,7 +711,7 @@ expect( ### BREAKING CHANGES -- Removed `namedNewInstance` and `namedConstInstance`, replaced with `constructor: `: +* Removed `namedNewInstance` and `namedConstInstance`, replaced with `constructor:`: ```dart expect( @@ -674,7 +722,7 @@ expect( ); ``` -- Renamed `named` parameter to `namedArguments`: +* Renamed `named` parameter to `namedArguments`: ```dart expect( @@ -696,25 +744,25 @@ expect( Avoid creating symbols that can collide with the Dart language: -- `MethodModifier.async` -> `MethodModifier.asAsync` -- `MethodModifier.asyncStar` -> `MethodModifier.asAsyncStar` -- `MethodModifier.syncStar` -> `MethodModifier.asSyncStar` +* `MethodModifier.async` -> `MethodModifier.asAsync` +* `MethodModifier.asyncStar` -> `MethodModifier.asAsyncStar` +* `MethodModifier.syncStar` -> `MethodModifier.asSyncStar` ## 1.0.0-beta+1 -- Add support for `switch` statements -- Add support for a raw expression and statement - - `new ExpressionBuilder.raw(...)` - - `new StatemnetBuilder.raw(...)` +* Add support for `switch` statements +* Add support for a raw expression and statement + * `new ExpressionBuilder.raw(...)` + * `new StatementBuilder.raw(...)` This should help cover any cases not covered with builders today. -- Allow referring to a `ClassBuilder` and `TypeBuilder` as an expression -- Add support for accessing the index `[]` operator on an expression +* Allow referring to a `ClassBuilder` and `TypeBuilder` as an expression +* Add support for accessing the index `[]` operator on an expression ### BREAKING CHANGES -- Changed `ExpressionBuilder.asAssign` to always take an `ExpressionBuilder` as +* Changed `ExpressionBuilder.asAssign` to always take an `ExpressionBuilder` as target and removed the `value` property. Most changes are pretty simple, and involve just using `reference(...)`. For example: @@ -726,26 +774,26 @@ literal(true).asAssign(reference('flag')) ## 1.0.0-beta -- Add support for `async`, `sync`, `sync*` functions -- Add support for expression `asAwait`, `asYield`, `asYieldStar` -- Add `toExportBuilder` and `toImportBuilder` to types and references -- Fix an import scoping bug in `return` statements and named constructor invocations. -- Added constructor initializer support -- Add `while` and `do {} while` loop support -- Add `for` and `for-in` support -- Added a `name` getter for `ParameterBuilder` +* Add support for `async`, `sync`, `sync*` functions +* Add support for expression `asAwait`, `asYield`, `asYieldStar` +* Add `toExportBuilder` and `toImportBuilder` to types and references +* Fix an import scoping bug in `return` statements and named constructor invocations. +* Added constructor initializer support +* Add `while` and `do {} while` loop support +* Add `for` and `for-in` support +* Added a `name` getter for `ParameterBuilder` ## 1.0.0-alpha+7 -- Make use of the new analyzer APIs in preparation for analyzer version 0.30. +* Make use of the new analyzer APIs in preparation for analyzer version 0.30. ## 1.0.0-alpha+6 -- `MethodBuilder.closure` emits properly as a top-level function +* `MethodBuilder.closure` emits properly as a top-level function ## 1.0.0-alpha+5 -- MethodBuilder with no statements will create an empty block instead of +* MethodBuilder with no statements will create an empty block instead of a semicolon. ```dart @@ -753,7 +801,7 @@ literal(true).asAssign(reference('flag')) method('main') ``` -- Fix lambdas and closures to not include a trailing semicolon when used +* Fix lambdas and closures to not include a trailing semicolon when used as an expression. ```dart @@ -763,13 +811,13 @@ method('main') ## 1.0.0-alpha+4 -- Add support for the latest `pkg/analyzer`. +* Add support for the latest `pkg/analyzer`. ## 1.0.0-alpha+3 -- BREAKING CHANGE: Added generics support to `TypeBuilder`: +* BREAKING CHANGE: Added generics support to `TypeBuilder`: -`importFrom` becomes a _named_, not a positional argument, and the named +`importFrom` becomes a *named*, not a positional argument, and the named argument `genericTypes` is added (`Iterable`). ```dart @@ -777,15 +825,15 @@ argument `genericTypes` is added (`Iterable`). new TypeBuilder('List', genericTypes: [reference('String')]) ``` -- Added generic support to `ReferenceBuilder`: +* Added generic support to `ReferenceBuilder`: ```dart // List reference('List').toTyped([reference('String')]) ``` -- Fixed a bug where `ReferenceBuilder.buildAst` was not implemented -- Added `and` and `or` methods to `ExpressionBuilder`: +* Fixed a bug where `ReferenceBuilder.buildAst` was not implemented +* Added `and` and `or` methods to `ExpressionBuilder`: ```dart // true || false @@ -795,7 +843,7 @@ literal(true).or(literal(false)); literal(true).and(literal(false)); ``` -- Added support for creating closures - `MethodBuilder.closure`: +* Added support for creating closures - `MethodBuilder.closure`: ```dart // () => true @@ -807,21 +855,21 @@ new MethodBuilder.closure( ## 1.0.0-alpha+2 -- Added `returnVoid` to well, `return;` -- Added support for top-level field assignments: +* Added `returnVoid` to well, `return;` +* Added support for top-level field assignments: ```dart new LibraryBuilder()..addMember(literal(false).asConst('foo')) ``` -- Added support for specifying a `target` when using `asAssign`: +* Added support for specifying a `target` when using `asAssign`: ```dart // Outputs bank.bar = goldBar reference('goldBar').asAssign('bar', target: reference('bank')) ``` -- Added support for the cascade operator: +* Added support for the cascade operator: ```dart // Outputs foo..doThis()..doThat() @@ -831,7 +879,7 @@ reference('foo').cascade((c) => [ ]); ``` -- Added support for accessing a property +* Added support for accessing a property ```dart // foo.bar @@ -840,20 +888,20 @@ reference('foo').property('bar'); ## 1.0.0-alpha+1 -- Slight updates to confusing documentation. -- Added support for null-aware assignments. -- Added `show` and `hide` support to `ImportBuilder` -- Added `deferred` support to `ImportBuilder` -- Added `ExportBuilder` -- Added `list` and `map` literals that support generic types +* Slight updates to confusing documentation. +* Added support for null-aware assignments. +* Added `show` and `hide` support to `ImportBuilder` +* Added `deferred` support to `ImportBuilder` +* Added `ExportBuilder` +* Added `list` and `map` literals that support generic types ## 1.0.0-alpha -- Large refactor that makes the library more feature complete. +* Large refactor that makes the library more feature complete. ## 0.1.1 -- Add the concept of `Scope` and change `toAst` to support it +* Add the concept of `Scope` and change `toAst` to support it Now your entire AST tree can be scoped and import directives automatically added to a `LibraryBuilder` for you if you use @@ -861,4 +909,4 @@ automatically added to a `LibraryBuilder` for you if you use ## 0.1.0 -- Initial version +* Initial version diff --git a/pkgs/code_builder/README.md b/pkgs/code_builder/README.md index 86decb5941..3ff069eb81 100644 --- a/pkgs/code_builder/README.md +++ b/pkgs/code_builder/README.md @@ -5,34 +5,41 @@ A fluent, builder-based library for generating valid Dart code. -## Usage +## Basic Usage `code_builder` has a narrow and user-friendly API. -See the `example` and `test` folders for additional examples. +Most Dart syntax structures are created using builders. For example, an empty class: -For example creating a class with a method: +```dart +final animal = Class((builder) => builder + ..name = 'Animal' + ..extend = refer('Organism') +); +``` + +Will produce: ```dart -import 'package:code_builder/code_builder.dart'; -import 'package:dart_style/dart_style.dart'; +class Animal extends Organism {} +``` -void main() { - final animal = Class((b) => b +If you're not a fan of nesting, you can create a builder directly and then call the `build` function. For example, adding a method to our class: + +```dart +final method = MethodBuilder() + ..name = 'eat' + ..body = refer('print').call([literal('Yum!')]).statement // print('Yum!'); + ..lambda = true; + +final animal = Class((builder) => builder ..name = 'Animal' ..extend = refer('Organism') - ..methods.add(Method.returnsVoid((b) => b - ..name = 'eat' - ..body = const Code("print('Yum!');")))); - final emitter = DartEmitter(); - print( - DartFormatter(languageVersion: DartFormatter.latestLanguageVersion) - .format('${animal.accept(emitter)}'), - ); -} + ..methods.add(method.build()) // MethodBuilder -> Method +); ``` -Outputs: +Will produce: ```dart class Animal extends Organism { @@ -40,6 +47,40 @@ class Animal extends Organism { } ``` +Then, when finished, use a `DartEmitter` and the `accept` method to build the `code_builder` structures into valid Dart code. For example: + +```dart +import 'package:dart_style/dart_style.dart'; + +// ... // + +final emitter = DartEmitter(); + +// Generate code for 'animal' into a new StringBuffer +final StringSink result = animal.accept(emitter); + +// or, add it to an existing one +final buffer = StringBuffer(); +animal.accept(emitter, buffer); + +// format the output using package:dart_style +final String formatted = DartFormatter( + languageVersion: DartFormatter.latestLanguageVersion) + .format(result.toString()); + +// voilĂ  +print(formatted); +``` + +Will output the code from above. + +For more usage examples see the [example] and [test] folders. + +[example]: https://github.com/dart-lang/tools/tree/main/pkgs/code_builder/example +[test]: https://github.com/dart-lang/tools/tree/main/pkgs/code_builder/test + +## Automatic Scoping + Have a complicated set of dependencies for your generated code? `code_builder` supports automatic scoping of your ASTs to automatically use prefixes to avoid symbol conflicts: @@ -59,15 +100,20 @@ void main() { ..name = 'doOther' ..returns = refer('Other', 'package:b/b.dart')), ])); + + // use a scoped DartEmitter to enable automatic prefixing + // using Allocator.simplePrefixing final emitter = DartEmitter.scoped(); - print( - DartFormatter(languageVersion: DartFormatter.latestLanguageVersion) - .format('${library.accept(emitter)}'), - ); + + final formatted = DartFormatter( + languageVersion: DartFormatter.latestLanguageVersion) + .format(library.accept(emitter).toString()); + + print(formatted); } ``` -Outputs: +Will output: ```dart import 'package:a/a.dart' as _i1; @@ -91,11 +137,12 @@ will be on a best-effort basis. > format this repository. You can run it simply from the command-line: > > ```sh -> $ dart run dart_style:format -w . +> dart run dart_style:format -w . > ``` [issue]: https://github.com/dart-lang/tools/issues [pull]: https://github.com/dart-lang/tools/pulls +[docs]: https://pub.dev/documentation/code_builder/latest/ ### Updating generated (`.g.dart`) files diff --git a/pkgs/code_builder/lib/code_builder.dart b/pkgs/code_builder/lib/code_builder.dart index 9cd15b958e..63e39e09fd 100644 --- a/pkgs/code_builder/lib/code_builder.dart +++ b/pkgs/code_builder/lib/code_builder.dart @@ -10,6 +10,27 @@ export 'src/specs/class.dart' show Class, ClassBuilder, ClassModifier; export 'src/specs/code.dart' show Block, BlockBuilder, Code, ScopedCode, StaticCode, lazyCode; export 'src/specs/constructor.dart' show Constructor, ConstructorBuilder; +export 'src/specs/control.dart' + show + BranchBuilder, + Case, + CaseBuilder, + CatchBlock, + CatchBlockBuilder, + Conditional, + ConditionalBuilder, + ForInLoop, + ForInLoopBuilder, + ForLoop, + ForLoopBuilder, + SwitchExpression, + SwitchExpressionBuilder, + SwitchStatement, + SwitchStatementBuilder, + TryCatch, + TryCatchBuilder, + WhileLoop, + WhileLoopBuilder; export 'src/specs/directive.dart' show Directive, DirectiveBuilder, DirectiveType; export 'src/specs/enum.dart' @@ -18,6 +39,7 @@ export 'src/specs/expression.dart' show BinaryExpression, CodeExpression, + ControlFlow, Expression, ExpressionEmitter, ExpressionVisitor, diff --git a/pkgs/code_builder/lib/src/emitter.dart b/pkgs/code_builder/lib/src/emitter.dart index 64e2aa46cb..21b3a320e5 100644 --- a/pkgs/code_builder/lib/src/emitter.dart +++ b/pkgs/code_builder/lib/src/emitter.dart @@ -7,6 +7,7 @@ import 'base.dart'; import 'specs/class.dart'; import 'specs/code.dart'; import 'specs/constructor.dart'; +import 'specs/control.dart'; import 'specs/directive.dart'; import 'specs/enum.dart'; import 'specs/expression.dart'; @@ -41,17 +42,30 @@ StringSink visitAll( if (elements.isEmpty) { return output; } + final iterator = elements.iterator..moveNext(); - visit(iterator.current); + + var prev = iterator.current; + visit(prev); + while (iterator.moveNext()) { - output.write(separator); - visit(iterator.current); + final curr = iterator.current; + + final chain = prev is CollectionExpression && + curr is CollectionExpression && + prev.chainTarget && + curr.chain; + + output.write(chain ? ' ' : separator); + visit(curr); + prev = curr; } + return output; } class DartEmitter extends Object - with CodeEmitter, ExpressionEmitter + with CodeEmitter, ExpressionEmitter, ControlBlockEmitter implements SpecVisitor { @override final Allocator allocator; diff --git a/pkgs/code_builder/lib/src/mixins/control.dart b/pkgs/code_builder/lib/src/mixins/control.dart new file mode 100644 index 0000000000..2aa27edb62 --- /dev/null +++ b/pkgs/code_builder/lib/src/mixins/control.dart @@ -0,0 +1,58 @@ +// 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 + +part of '../specs/control.dart'; + +/// Root class for control-flow blocks. +/// +/// {@category controlFlow} +@internal +@immutable +abstract mixin class ControlBlock implements Code, Spec { + /// The full control-flow expression that precedes this block. + ControlExpression get _expression; + + /// The body of this block. + /// + /// *Note: will always be wrapped in `{`braces`}`*. + Block get body; + + @override + R accept(covariant ControlBlockVisitor visitor, [R? context]) => + visitor.visitControlBlock(this, context); +} + +/// Adds label support to a [ControlBlock]. +/// +/// {@category controlFlow} +@internal +@immutable +mixin LabeledControlBlock on ControlBlock { + /// An (optional) label for this block. + /// + /// ```dart + /// label: {block} + /// ``` + /// + /// https://dart.dev/language/loops#labels + String? get label; + + @override + R accept(covariant ControlBlockVisitor visitor, [R? context]) => + visitor.visitLabeledBlock(this); +} + +/// A tree of [ControlBlock]s +/// +/// {@category controlFlow} +@internal +@immutable +abstract mixin class ControlTree implements Code, Spec { + /// The items in this tree. + List get _blocks; + + @override + R accept(covariant ControlBlockVisitor visitor, [R? context]) => + visitor.visitControlTree(this, context); +} diff --git a/pkgs/code_builder/lib/src/specs/class.g.dart b/pkgs/code_builder/lib/src/specs/class.g.dart index 423f576ce6..11da402a12 100644 --- a/pkgs/code_builder/lib/src/specs/class.g.dart +++ b/pkgs/code_builder/lib/src/specs/class.g.dart @@ -37,7 +37,7 @@ class _$Class extends Class { final String name; factory _$Class([void Function(ClassBuilder)? updates]) => - (new ClassBuilder()..update(updates)).build() as _$Class; + (ClassBuilder()..update(updates)).build() as _$Class; _$Class._( {required this.abstract, @@ -54,28 +54,13 @@ class _$Class extends Class { required this.methods, required this.fields, required this.name}) - : super._() { - BuiltValueNullFieldError.checkNotNull(abstract, r'Class', 'abstract'); - BuiltValueNullFieldError.checkNotNull(sealed, r'Class', 'sealed'); - BuiltValueNullFieldError.checkNotNull(mixin, r'Class', 'mixin'); - BuiltValueNullFieldError.checkNotNull(annotations, r'Class', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'Class', 'docs'); - BuiltValueNullFieldError.checkNotNull(implements, r'Class', 'implements'); - BuiltValueNullFieldError.checkNotNull(mixins, r'Class', 'mixins'); - BuiltValueNullFieldError.checkNotNull(types, r'Class', 'types'); - BuiltValueNullFieldError.checkNotNull( - constructors, r'Class', 'constructors'); - BuiltValueNullFieldError.checkNotNull(methods, r'Class', 'methods'); - BuiltValueNullFieldError.checkNotNull(fields, r'Class', 'fields'); - BuiltValueNullFieldError.checkNotNull(name, r'Class', 'name'); - } - + : super._(); @override Class rebuild(void Function(ClassBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$ClassBuilder toBuilder() => new _$ClassBuilder()..replace(this); + _$ClassBuilder toBuilder() => _$ClassBuilder()..replace(this); @override bool operator ==(Object other) { @@ -336,7 +321,6 @@ class _$ClassBuilder extends ClassBuilder { @override void replace(Class other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Class; } @@ -352,25 +336,25 @@ class _$ClassBuilder extends ClassBuilder { _$Class _$result; try { _$result = _$v ?? - new _$Class._( - abstract: BuiltValueNullFieldError.checkNotNull( - abstract, r'Class', 'abstract'), - sealed: BuiltValueNullFieldError.checkNotNull( - sealed, r'Class', 'sealed'), - mixin: BuiltValueNullFieldError.checkNotNull( - mixin, r'Class', 'mixin'), - modifier: modifier, - annotations: annotations.build(), - docs: docs.build(), - extend: extend, - implements: implements.build(), - mixins: mixins.build(), - types: types.build(), - constructors: constructors.build(), - methods: methods.build(), - fields: fields.build(), - name: BuiltValueNullFieldError.checkNotNull( - name, r'Class', 'name')); + _$Class._( + abstract: BuiltValueNullFieldError.checkNotNull( + abstract, r'Class', 'abstract'), + sealed: BuiltValueNullFieldError.checkNotNull( + sealed, r'Class', 'sealed'), + mixin: + BuiltValueNullFieldError.checkNotNull(mixin, r'Class', 'mixin'), + modifier: modifier, + annotations: annotations.build(), + docs: docs.build(), + extend: extend, + implements: implements.build(), + mixins: mixins.build(), + types: types.build(), + constructors: constructors.build(), + methods: methods.build(), + fields: fields.build(), + name: BuiltValueNullFieldError.checkNotNull(name, r'Class', 'name'), + ); } catch (_) { late String _$failedField; try { @@ -392,8 +376,7 @@ class _$ClassBuilder extends ClassBuilder { _$failedField = 'fields'; fields.build(); } catch (e) { - throw new BuiltValueNestedFieldError( - r'Class', _$failedField, e.toString()); + throw BuiltValueNestedFieldError(r'Class', _$failedField, e.toString()); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/code.g.dart b/pkgs/code_builder/lib/src/specs/code.g.dart index 7b5ba78258..47a301eb38 100644 --- a/pkgs/code_builder/lib/src/specs/code.g.dart +++ b/pkgs/code_builder/lib/src/specs/code.g.dart @@ -11,18 +11,15 @@ class _$Block extends Block { final BuiltList statements; factory _$Block([void Function(BlockBuilder)? updates]) => - (new BlockBuilder()..update(updates)).build() as _$Block; - - _$Block._({required this.statements}) : super._() { - BuiltValueNullFieldError.checkNotNull(statements, r'Block', 'statements'); - } + (BlockBuilder()..update(updates)).build() as _$Block; + _$Block._({required this.statements}) : super._(); @override Block rebuild(void Function(BlockBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$BlockBuilder toBuilder() => new _$BlockBuilder()..replace(this); + _$BlockBuilder toBuilder() => _$BlockBuilder()..replace(this); @override bool operator ==(Object other) { @@ -74,7 +71,6 @@ class _$BlockBuilder extends BlockBuilder { @override void replace(Block other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Block; } @@ -89,15 +85,17 @@ class _$BlockBuilder extends BlockBuilder { _$Block _build() { _$Block _$result; try { - _$result = _$v ?? new _$Block._(statements: statements.build()); + _$result = _$v ?? + _$Block._( + statements: statements.build(), + ); } catch (_) { late String _$failedField; try { _$failedField = 'statements'; statements.build(); } catch (e) { - throw new BuiltValueNestedFieldError( - r'Block', _$failedField, e.toString()); + throw BuiltValueNestedFieldError(r'Block', _$failedField, e.toString()); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/constructor.g.dart b/pkgs/code_builder/lib/src/specs/constructor.g.dart index 3f0693272d..91951a936a 100644 --- a/pkgs/code_builder/lib/src/specs/constructor.g.dart +++ b/pkgs/code_builder/lib/src/specs/constructor.g.dart @@ -33,7 +33,7 @@ class _$Constructor extends Constructor { final Reference? redirect; factory _$Constructor([void Function(ConstructorBuilder)? updates]) => - (new ConstructorBuilder()..update(updates)).build() as _$Constructor; + (ConstructorBuilder()..update(updates)).build() as _$Constructor; _$Constructor._( {required this.annotations, @@ -48,27 +48,13 @@ class _$Constructor extends Constructor { this.lambda, this.name, this.redirect}) - : super._() { - BuiltValueNullFieldError.checkNotNull( - annotations, r'Constructor', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'Constructor', 'docs'); - BuiltValueNullFieldError.checkNotNull( - optionalParameters, r'Constructor', 'optionalParameters'); - BuiltValueNullFieldError.checkNotNull( - requiredParameters, r'Constructor', 'requiredParameters'); - BuiltValueNullFieldError.checkNotNull( - initializers, r'Constructor', 'initializers'); - BuiltValueNullFieldError.checkNotNull(external, r'Constructor', 'external'); - BuiltValueNullFieldError.checkNotNull(constant, r'Constructor', 'constant'); - BuiltValueNullFieldError.checkNotNull(factory, r'Constructor', 'factory'); - } - + : super._(); @override Constructor rebuild(void Function(ConstructorBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$ConstructorBuilder toBuilder() => new _$ConstructorBuilder()..replace(this); + _$ConstructorBuilder toBuilder() => _$ConstructorBuilder()..replace(this); @override bool operator ==(Object other) { @@ -297,7 +283,6 @@ class _$ConstructorBuilder extends ConstructorBuilder { @override void replace(Constructor other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Constructor; } @@ -313,22 +298,23 @@ class _$ConstructorBuilder extends ConstructorBuilder { _$Constructor _$result; try { _$result = _$v ?? - new _$Constructor._( - annotations: annotations.build(), - docs: docs.build(), - optionalParameters: optionalParameters.build(), - requiredParameters: requiredParameters.build(), - initializers: initializers.build(), - body: body, - external: BuiltValueNullFieldError.checkNotNull( - external, r'Constructor', 'external'), - constant: BuiltValueNullFieldError.checkNotNull( - constant, r'Constructor', 'constant'), - factory: BuiltValueNullFieldError.checkNotNull( - factory, r'Constructor', 'factory'), - lambda: lambda, - name: name, - redirect: redirect); + _$Constructor._( + annotations: annotations.build(), + docs: docs.build(), + optionalParameters: optionalParameters.build(), + requiredParameters: requiredParameters.build(), + initializers: initializers.build(), + body: body, + external: BuiltValueNullFieldError.checkNotNull( + external, r'Constructor', 'external'), + constant: BuiltValueNullFieldError.checkNotNull( + constant, r'Constructor', 'constant'), + factory: BuiltValueNullFieldError.checkNotNull( + factory, r'Constructor', 'factory'), + lambda: lambda, + name: name, + redirect: redirect, + ); } catch (_) { late String _$failedField; try { @@ -343,7 +329,7 @@ class _$ConstructorBuilder extends ConstructorBuilder { _$failedField = 'initializers'; initializers.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'Constructor', _$failedField, e.toString()); } rethrow; diff --git a/pkgs/code_builder/lib/src/specs/control.dart b/pkgs/code_builder/lib/src/specs/control.dart new file mode 100644 index 0000000000..bbfae9e073 --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/control.dart @@ -0,0 +1,211 @@ +// 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:built_collection/built_collection.dart'; +import 'package:built_value/built_value.dart'; +import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; + +import '../base.dart'; +import 'code.dart'; +import 'expression.dart'; +import 'reference.dart'; + +part 'control.g.dart'; +part '../mixins/control.dart'; + +part './control/loops.dart'; +part './control/branches.dart'; +part './control/handling.dart'; +part './control/switch.dart'; + +/// Knowledge of different types of control blocks. +/// +@internal +abstract class ControlBlockVisitor + implements ExpressionVisitor, CodeVisitor { + T visitControlBlock(ControlBlock block, [T? context]); + T visitLabeledBlock(LabeledControlBlock block, [T? context]); + T visitWhileLoop(WhileLoop loop, [T? context]); + T visitControlTree(ControlTree tree, [T? context]); + T visitControlExpression(ControlExpression expression, [T? context]); + T visitSwitch(Switch statement, [T? context]); + // [context] is actually used, but the analyzer doesn't detect it. + // ignore: unused_element_parameter + T _visitCaseStatement(CaseStatement statement, [T? context]); + // [context] is actually used, but the analyzer doesn't detect it. + // ignore: unused_element_parameter + T _visitCaseExpression(CaseExpression expression, [T? context]); +} + +/// Knowledge of how to write valid Dart code from [ControlBlockVisitor]. +/// +@internal +abstract mixin class ControlBlockEmitter + implements ControlBlockVisitor { + @override + StringSink visitControlBlock(ControlBlock block, [StringSink? output]) { + output ??= StringBuffer(); + block._expression.accept(this, output); + output.writeln(' {'); + block.body.accept(this, output); + + output.write(' }'); + + return output; + } + + @override + StringSink visitLabeledBlock(LabeledControlBlock block, + [StringSink? output]) { + output ??= StringBuffer(); + if (block.label != null) { + output.writeln('${block.label!}:'); + } + + return visitControlBlock(block, output); + } + + @override + StringSink visitWhileLoop(WhileLoop loop, [StringSink? output]) { + output ??= StringBuffer(); + visitLabeledBlock(loop, output); + + if (loop.doWhile != true) return output; + + output.write(' '); + loop._statement.statement.accept(this, output); + output.writeln(); + return output; + } + + @override + StringSink visitControlTree(ControlTree tree, [StringSink? output]) { + output ??= StringBuffer(); + + for (final item in tree._blocks.nonNulls) { + item.accept(this, output); + output.write(' '); + } + + return output; + } + + @override + StringSink visitControlExpression(ControlExpression expression, + [StringSink? output]) { + output ??= StringBuffer(); + + output.write(expression.control); + + if (expression.body == null || expression.body!.isEmpty) { + return output; + } + + final body = expression.body!; // convenience + + output.write(' '); + if (expression.parenthesised) { + output.write('('); + } + + if (body.length == 1) { + body.first?.accept(this, output); + if (expression.parenthesised) { + output.write(')'); + } + + return output; + } + + if (expression.separator == null) { + throw ArgumentError( + 'A separator must be provided when body contains ' + 'multiple expressions.', + 'separator'); + } + + final separator = expression.separator!; // convenience + + for (var i = 0; i < body.length; i++) { + final expression = body[i]; + + if (i != 0 && expression != null) { + output.write(' '); + } + + expression?.accept(this, output); + + if (i == body.length - 1) continue; // no separator after last item + + output.write(separator); + } + + if (expression.parenthesised) { + output.write(')'); + } + + return output; + } + + @override + StringSink visitSwitch(Switch statement, [StringSink? output]) { + output ??= StringBuffer(); + + final buildable = + BuildableSwitch(value: statement.value, cases: statement._cases); + + return visitControlBlock(buildable, output); + } + + @override + StringSink _visitCaseStatement(CaseStatement statement, + [StringSink? output]) { + output ??= StringBuffer(); + + if (statement.label case final String label) { + output.writeln('$label:'); + } + + if (statement._default) { + output.writeln('default:'); + } else { + output.write('case '); + statement.pattern.accept(this, output); + + if (statement.guard case final Expression guard) { + output.write(' when '); + guard.accept(this, output); + } + + output.writeln(':'); + } + + if (statement.body case final Code body) { + body.accept(this, output); + } + + return output; + } + + @override + StringSink _visitCaseExpression(CaseExpression expression, + [StringSink? output]) { + output ??= StringBuffer(); + expression.pattern.accept(this, output); + + if (expression.guard case final Expression guard) { + output.write(' when '); + guard.accept(this, output); + } + + output.write(' => '); + // body will never be null; CaseExpression ensures a value is + // provided when it is constructed + expression.body!.accept(this, output); + output.writeln(','); + + return output; + } +} diff --git a/pkgs/code_builder/lib/src/specs/control.g.dart b/pkgs/code_builder/lib/src/specs/control.g.dart new file mode 100644 index 0000000000..d98dbdc701 --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/control.g.dart @@ -0,0 +1,1245 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'control.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +class _$ForLoop extends ForLoop { + @override + final Expression? initialize; + @override + final Expression? condition; + @override + final Expression? advance; + @override + final Block body; + @override + final String? label; + + factory _$ForLoop([void Function(ForLoopBuilder)? updates]) => + (ForLoopBuilder()..update(updates))._build(); + + _$ForLoop._( + {this.initialize, + this.condition, + this.advance, + required this.body, + this.label}) + : super._(); + @override + ForLoop rebuild(void Function(ForLoopBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + ForLoopBuilder toBuilder() => ForLoopBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is ForLoop && + initialize == other.initialize && + condition == other.condition && + advance == other.advance && + body == other.body && + label == other.label; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, initialize.hashCode); + _$hash = $jc(_$hash, condition.hashCode); + _$hash = $jc(_$hash, advance.hashCode); + _$hash = $jc(_$hash, body.hashCode); + _$hash = $jc(_$hash, label.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'ForLoop') + ..add('initialize', initialize) + ..add('condition', condition) + ..add('advance', advance) + ..add('body', body) + ..add('label', label)) + .toString(); + } +} + +class ForLoopBuilder implements Builder { + _$ForLoop? _$v; + + Expression? _initialize; + Expression? get initialize => _$this._initialize; + set initialize(Expression? initialize) => _$this._initialize = initialize; + + Expression? _condition; + Expression? get condition => _$this._condition; + set condition(Expression? condition) => _$this._condition = condition; + + Expression? _advance; + Expression? get advance => _$this._advance; + set advance(Expression? advance) => _$this._advance = advance; + + BlockBuilder? _body; + BlockBuilder get body => _$this._body ??= BlockBuilder(); + set body(BlockBuilder? body) => _$this._body = body; + + String? _label; + String? get label => _$this._label; + set label(String? label) => _$this._label = label; + + ForLoopBuilder(); + + ForLoopBuilder get _$this { + final $v = _$v; + if ($v != null) { + _initialize = $v.initialize; + _condition = $v.condition; + _advance = $v.advance; + _body = $v.body.toBuilder(); + _label = $v.label; + _$v = null; + } + return this; + } + + @override + void replace(ForLoop other) { + _$v = other as _$ForLoop; + } + + @override + void update(void Function(ForLoopBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + ForLoop build() => _build(); + + _$ForLoop _build() { + _$ForLoop _$result; + try { + _$result = _$v ?? + _$ForLoop._( + initialize: initialize, + condition: condition, + advance: advance, + body: body.build(), + label: label, + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'body'; + body.build(); + } catch (e) { + throw BuiltValueNestedFieldError( + r'ForLoop', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$ForInLoop extends ForInLoop { + @override + final bool? async; + @override + final Expression variable; + @override + final Expression object; + @override + final Block body; + @override + final String? label; + + factory _$ForInLoop([void Function(ForInLoopBuilder)? updates]) => + (ForInLoopBuilder()..update(updates))._build(); + + _$ForInLoop._( + {this.async, + required this.variable, + required this.object, + required this.body, + this.label}) + : super._(); + @override + ForInLoop rebuild(void Function(ForInLoopBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + ForInLoopBuilder toBuilder() => ForInLoopBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is ForInLoop && + async == other.async && + variable == other.variable && + object == other.object && + body == other.body && + label == other.label; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, async.hashCode); + _$hash = $jc(_$hash, variable.hashCode); + _$hash = $jc(_$hash, object.hashCode); + _$hash = $jc(_$hash, body.hashCode); + _$hash = $jc(_$hash, label.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'ForInLoop') + ..add('async', async) + ..add('variable', variable) + ..add('object', object) + ..add('body', body) + ..add('label', label)) + .toString(); + } +} + +class ForInLoopBuilder implements Builder { + _$ForInLoop? _$v; + + bool? _async; + bool? get async => _$this._async; + set async(bool? async) => _$this._async = async; + + Expression? _variable; + Expression? get variable => _$this._variable; + set variable(Expression? variable) => _$this._variable = variable; + + Expression? _object; + Expression? get object => _$this._object; + set object(Expression? object) => _$this._object = object; + + BlockBuilder? _body; + BlockBuilder get body => _$this._body ??= BlockBuilder(); + set body(BlockBuilder? body) => _$this._body = body; + + String? _label; + String? get label => _$this._label; + set label(String? label) => _$this._label = label; + + ForInLoopBuilder(); + + ForInLoopBuilder get _$this { + final $v = _$v; + if ($v != null) { + _async = $v.async; + _variable = $v.variable; + _object = $v.object; + _body = $v.body.toBuilder(); + _label = $v.label; + _$v = null; + } + return this; + } + + @override + void replace(ForInLoop other) { + _$v = other as _$ForInLoop; + } + + @override + void update(void Function(ForInLoopBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + ForInLoop build() => _build(); + + _$ForInLoop _build() { + _$ForInLoop _$result; + try { + _$result = _$v ?? + _$ForInLoop._( + async: async, + variable: BuiltValueNullFieldError.checkNotNull( + variable, r'ForInLoop', 'variable'), + object: BuiltValueNullFieldError.checkNotNull( + object, r'ForInLoop', 'object'), + body: body.build(), + label: label, + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'body'; + body.build(); + } catch (e) { + throw BuiltValueNestedFieldError( + r'ForInLoop', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$WhileLoop extends WhileLoop { + @override + final bool? doWhile; + @override + final Expression condition; + @override + final Block body; + @override + final String? label; + + factory _$WhileLoop([void Function(WhileLoopBuilder)? updates]) => + (WhileLoopBuilder()..update(updates))._build(); + + _$WhileLoop._( + {this.doWhile, required this.condition, required this.body, this.label}) + : super._(); + @override + WhileLoop rebuild(void Function(WhileLoopBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + WhileLoopBuilder toBuilder() => WhileLoopBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is WhileLoop && + doWhile == other.doWhile && + condition == other.condition && + body == other.body && + label == other.label; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, doWhile.hashCode); + _$hash = $jc(_$hash, condition.hashCode); + _$hash = $jc(_$hash, body.hashCode); + _$hash = $jc(_$hash, label.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'WhileLoop') + ..add('doWhile', doWhile) + ..add('condition', condition) + ..add('body', body) + ..add('label', label)) + .toString(); + } +} + +class WhileLoopBuilder implements Builder { + _$WhileLoop? _$v; + + bool? _doWhile; + bool? get doWhile => _$this._doWhile; + set doWhile(bool? doWhile) => _$this._doWhile = doWhile; + + Expression? _condition; + Expression? get condition => _$this._condition; + set condition(Expression? condition) => _$this._condition = condition; + + BlockBuilder? _body; + BlockBuilder get body => _$this._body ??= BlockBuilder(); + set body(BlockBuilder? body) => _$this._body = body; + + String? _label; + String? get label => _$this._label; + set label(String? label) => _$this._label = label; + + WhileLoopBuilder(); + + WhileLoopBuilder get _$this { + final $v = _$v; + if ($v != null) { + _doWhile = $v.doWhile; + _condition = $v.condition; + _body = $v.body.toBuilder(); + _label = $v.label; + _$v = null; + } + return this; + } + + @override + void replace(WhileLoop other) { + _$v = other as _$WhileLoop; + } + + @override + void update(void Function(WhileLoopBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + WhileLoop build() => _build(); + + _$WhileLoop _build() { + _$WhileLoop _$result; + try { + _$result = _$v ?? + _$WhileLoop._( + doWhile: doWhile, + condition: BuiltValueNullFieldError.checkNotNull( + condition, r'WhileLoop', 'condition'), + body: body.build(), + label: label, + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'body'; + body.build(); + } catch (e) { + throw BuiltValueNestedFieldError( + r'WhileLoop', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$Branch extends Branch { + @override + final Expression? condition; + @override + final Block body; + + factory _$Branch([void Function(BranchBuilder)? updates]) => + (BranchBuilder()..update(updates)).build() as _$Branch; + + _$Branch._({this.condition, required this.body}) : super._(); + @override + Branch rebuild(void Function(BranchBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + _$BranchBuilder toBuilder() => _$BranchBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Branch && + condition == other.condition && + body == other.body; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, condition.hashCode); + _$hash = $jc(_$hash, body.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Branch') + ..add('condition', condition) + ..add('body', body)) + .toString(); + } +} + +class _$BranchBuilder extends BranchBuilder { + _$Branch? _$v; + + @override + Expression? get condition { + _$this; + return super.condition; + } + + @override + set condition(Expression? condition) { + _$this; + super.condition = condition; + } + + @override + BlockBuilder get body { + _$this; + return super.body; + } + + @override + set body(BlockBuilder body) { + _$this; + super.body = body; + } + + _$BranchBuilder() : super._(); + + BranchBuilder get _$this { + final $v = _$v; + if ($v != null) { + super.condition = $v.condition; + super.body = $v.body.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(Branch other) { + _$v = other as _$Branch; + } + + @override + void update(void Function(BranchBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Branch build() => _build(); + + _$Branch _build() { + _$Branch _$result; + try { + _$result = _$v ?? + _$Branch._( + condition: condition, + body: body.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'body'; + body.build(); + } catch (e) { + throw BuiltValueNestedFieldError( + r'Branch', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$Conditional extends Conditional { + @override + final BuiltList branches; + + factory _$Conditional([void Function(ConditionalBuilder)? updates]) => + (ConditionalBuilder()..update(updates)).build() as _$Conditional; + + _$Conditional._({required this.branches}) : super._(); + @override + Conditional rebuild(void Function(ConditionalBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + _$ConditionalBuilder toBuilder() => _$ConditionalBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Conditional && branches == other.branches; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, branches.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Conditional') + ..add('branches', branches)) + .toString(); + } +} + +class _$ConditionalBuilder extends ConditionalBuilder { + _$Conditional? _$v; + + @override + ListBuilder get branches { + _$this; + return super.branches; + } + + @override + set branches(ListBuilder branches) { + _$this; + super.branches = branches; + } + + _$ConditionalBuilder() : super._(); + + ConditionalBuilder get _$this { + final $v = _$v; + if ($v != null) { + super.branches = $v.branches.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(Conditional other) { + _$v = other as _$Conditional; + } + + @override + void update(void Function(ConditionalBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Conditional build() => _build(); + + _$Conditional _build() { + _$Conditional _$result; + try { + _$result = _$v ?? + _$Conditional._( + branches: branches.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'branches'; + branches.build(); + } catch (e) { + throw BuiltValueNestedFieldError( + r'Conditional', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$CatchBlock extends CatchBlock { + @override + final Reference? type; + @override + final String? exception; + @override + final String? stacktrace; + @override + final Block body; + + factory _$CatchBlock([void Function(CatchBlockBuilder)? updates]) => + (CatchBlockBuilder()..update(updates))._build(); + + _$CatchBlock._( + {this.type, this.exception, this.stacktrace, required this.body}) + : super._(); + @override + CatchBlock rebuild(void Function(CatchBlockBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + CatchBlockBuilder toBuilder() => CatchBlockBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is CatchBlock && + type == other.type && + exception == other.exception && + stacktrace == other.stacktrace && + body == other.body; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, type.hashCode); + _$hash = $jc(_$hash, exception.hashCode); + _$hash = $jc(_$hash, stacktrace.hashCode); + _$hash = $jc(_$hash, body.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'CatchBlock') + ..add('type', type) + ..add('exception', exception) + ..add('stacktrace', stacktrace) + ..add('body', body)) + .toString(); + } +} + +class CatchBlockBuilder implements Builder { + _$CatchBlock? _$v; + + Reference? _type; + Reference? get type => _$this._type; + set type(Reference? type) => _$this._type = type; + + String? _exception; + String? get exception => _$this._exception; + set exception(String? exception) => _$this._exception = exception; + + String? _stacktrace; + String? get stacktrace => _$this._stacktrace; + set stacktrace(String? stacktrace) => _$this._stacktrace = stacktrace; + + BlockBuilder? _body; + BlockBuilder get body => _$this._body ??= BlockBuilder(); + set body(BlockBuilder? body) => _$this._body = body; + + CatchBlockBuilder(); + + CatchBlockBuilder get _$this { + final $v = _$v; + if ($v != null) { + _type = $v.type; + _exception = $v.exception; + _stacktrace = $v.stacktrace; + _body = $v.body.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(CatchBlock other) { + _$v = other as _$CatchBlock; + } + + @override + void update(void Function(CatchBlockBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + CatchBlock build() => _build(); + + _$CatchBlock _build() { + _$CatchBlock _$result; + try { + _$result = _$v ?? + _$CatchBlock._( + type: type, + exception: exception, + stacktrace: stacktrace, + body: body.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'body'; + body.build(); + } catch (e) { + throw BuiltValueNestedFieldError( + r'CatchBlock', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$TryCatch extends TryCatch { + @override + final Block body; + @override + final BuiltList handlers; + @override + final Block? handleAll; + + factory _$TryCatch([void Function(TryCatchBuilder)? updates]) => + (TryCatchBuilder()..update(updates)).build() as _$TryCatch; + + _$TryCatch._({required this.body, required this.handlers, this.handleAll}) + : super._(); + @override + TryCatch rebuild(void Function(TryCatchBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + _$TryCatchBuilder toBuilder() => _$TryCatchBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is TryCatch && + body == other.body && + handlers == other.handlers && + handleAll == other.handleAll; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, body.hashCode); + _$hash = $jc(_$hash, handlers.hashCode); + _$hash = $jc(_$hash, handleAll.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'TryCatch') + ..add('body', body) + ..add('handlers', handlers) + ..add('handleAll', handleAll)) + .toString(); + } +} + +class _$TryCatchBuilder extends TryCatchBuilder { + _$TryCatch? _$v; + + @override + BlockBuilder get body { + _$this; + return super.body; + } + + @override + set body(BlockBuilder body) { + _$this; + super.body = body; + } + + @override + ListBuilder get handlers { + _$this; + return super.handlers; + } + + @override + set handlers(ListBuilder handlers) { + _$this; + super.handlers = handlers; + } + + @override + BlockBuilder get handleAll { + _$this; + return super.handleAll ??= BlockBuilder(); + } + + @override + set handleAll(BlockBuilder? handleAll) { + _$this; + super.handleAll = handleAll; + } + + _$TryCatchBuilder() : super._(); + + TryCatchBuilder get _$this { + final $v = _$v; + if ($v != null) { + super.body = $v.body.toBuilder(); + super.handlers = $v.handlers.toBuilder(); + super.handleAll = $v.handleAll?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(TryCatch other) { + _$v = other as _$TryCatch; + } + + @override + void update(void Function(TryCatchBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + TryCatch build() => _build(); + + _$TryCatch _build() { + TryCatch._build(this); + _$TryCatch _$result; + try { + _$result = _$v ?? + _$TryCatch._( + body: body.build(), + handlers: handlers.build(), + handleAll: super.handleAll?.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'body'; + body.build(); + _$failedField = 'handlers'; + handlers.build(); + _$failedField = 'handleAll'; + super.handleAll?.build(); + } catch (e) { + throw BuiltValueNestedFieldError( + r'TryCatch', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$Case extends Case { + @override + final Expression pattern; + @override + final Expression? guard; + @override + final String? label; + @override + final T? body; + + factory _$Case([void Function(CaseBuilder)? updates]) => + (CaseBuilder()..update(updates))._build(); + + _$Case._({required this.pattern, this.guard, this.label, this.body}) + : super._(); + @override + Case rebuild(void Function(CaseBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + CaseBuilder toBuilder() => CaseBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Case && + pattern == other.pattern && + guard == other.guard && + label == other.label && + body == other.body; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, pattern.hashCode); + _$hash = $jc(_$hash, guard.hashCode); + _$hash = $jc(_$hash, label.hashCode); + _$hash = $jc(_$hash, body.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Case') + ..add('pattern', pattern) + ..add('guard', guard) + ..add('label', label) + ..add('body', body)) + .toString(); + } +} + +class CaseBuilder implements Builder, CaseBuilder> { + _$Case? _$v; + + Expression? _pattern; + Expression? get pattern => _$this._pattern; + set pattern(Expression? pattern) => _$this._pattern = pattern; + + Expression? _guard; + Expression? get guard => _$this._guard; + set guard(Expression? guard) => _$this._guard = guard; + + String? _label; + String? get label => _$this._label; + set label(String? label) => _$this._label = label; + + T? _body; + T? get body => _$this._body; + set body(T? body) => _$this._body = body; + + CaseBuilder(); + + CaseBuilder get _$this { + final $v = _$v; + if ($v != null) { + _pattern = $v.pattern; + _guard = $v.guard; + _label = $v.label; + _body = $v.body; + _$v = null; + } + return this; + } + + @override + void replace(Case other) { + _$v = other as _$Case; + } + + @override + void update(void Function(CaseBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Case build() => _build(); + + _$Case _build() { + final _$result = _$v ?? + _$Case._( + pattern: BuiltValueNullFieldError.checkNotNull( + pattern, r'Case', 'pattern'), + guard: guard, + label: label, + body: body, + ); + replace(_$result); + return _$result; + } +} + +class _$SwitchStatement extends SwitchStatement { + @override + final Expression value; + @override + final BuiltList> cases; + + factory _$SwitchStatement([void Function(SwitchStatementBuilder)? updates]) => + (SwitchStatementBuilder()..update(updates))._build(); + + _$SwitchStatement._({required this.value, required this.cases}) : super._(); + @override + SwitchStatement rebuild(void Function(SwitchStatementBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + SwitchStatementBuilder toBuilder() => SwitchStatementBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is SwitchStatement && + value == other.value && + cases == other.cases; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, value.hashCode); + _$hash = $jc(_$hash, cases.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'SwitchStatement') + ..add('value', value) + ..add('cases', cases)) + .toString(); + } +} + +class SwitchStatementBuilder + implements + Builder, + SwitchBuilder { + _$SwitchStatement? _$v; + + Expression? _value; + Expression? get value => _$this._value; + set value(covariant Expression? value) => _$this._value = value; + + ListBuilder>? _cases; + ListBuilder> get cases => + _$this._cases ??= ListBuilder>(); + set cases(covariant ListBuilder>? cases) => _$this._cases = cases; + + SwitchStatementBuilder(); + + SwitchStatementBuilder get _$this { + final $v = _$v; + if ($v != null) { + _value = $v.value; + _cases = $v.cases.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(covariant SwitchStatement other) { + _$v = other as _$SwitchStatement; + } + + @override + void update(void Function(SwitchStatementBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + SwitchStatement build() => _build(); + + _$SwitchStatement _build() { + _$SwitchStatement _$result; + try { + _$result = _$v ?? + _$SwitchStatement._( + value: BuiltValueNullFieldError.checkNotNull( + value, r'SwitchStatement', 'value'), + cases: cases.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'cases'; + cases.build(); + } catch (e) { + throw BuiltValueNestedFieldError( + r'SwitchStatement', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$SwitchExpression extends SwitchExpression { + @override + final Expression value; + @override + final BuiltList> cases; + + factory _$SwitchExpression( + [void Function(SwitchExpressionBuilder)? updates]) => + (SwitchExpressionBuilder()..update(updates))._build(); + + _$SwitchExpression._({required this.value, required this.cases}) : super._(); + @override + SwitchExpression rebuild(void Function(SwitchExpressionBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + SwitchExpressionBuilder toBuilder() => + SwitchExpressionBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is SwitchExpression && + value == other.value && + cases == other.cases; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, value.hashCode); + _$hash = $jc(_$hash, cases.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'SwitchExpression') + ..add('value', value) + ..add('cases', cases)) + .toString(); + } +} + +class SwitchExpressionBuilder + implements + Builder, + SwitchBuilder { + _$SwitchExpression? _$v; + + Expression? _value; + Expression? get value => _$this._value; + set value(covariant Expression? value) => _$this._value = value; + + ListBuilder>? _cases; + ListBuilder> get cases => + _$this._cases ??= ListBuilder>(); + set cases(covariant ListBuilder>? cases) => + _$this._cases = cases; + + SwitchExpressionBuilder(); + + SwitchExpressionBuilder get _$this { + final $v = _$v; + if ($v != null) { + _value = $v.value; + _cases = $v.cases.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(covariant SwitchExpression other) { + _$v = other as _$SwitchExpression; + } + + @override + void update(void Function(SwitchExpressionBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + SwitchExpression build() => _build(); + + _$SwitchExpression _build() { + _$SwitchExpression _$result; + try { + _$result = _$v ?? + _$SwitchExpression._( + value: BuiltValueNullFieldError.checkNotNull( + value, r'SwitchExpression', 'value'), + cases: cases.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'cases'; + cases.build(); + } catch (e) { + throw BuiltValueNestedFieldError( + r'SwitchExpression', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/pkgs/code_builder/lib/src/specs/control/branches.dart b/pkgs/code_builder/lib/src/specs/control/branches.dart new file mode 100644 index 0000000000..c15cac046d --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/control/branches.dart @@ -0,0 +1,141 @@ +// 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 + +part of '../control.dart'; + +/// A [Conditional] branch. +/// +/// Intentionally not exported to avoid confusion +/// with [Conditional]. +/// +@internal +abstract class Branch implements Built { + Branch._(); + factory Branch([void Function(BranchBuilder) updates]) = _$Branch; + + Expression? get condition; + Block get body; + + _Branch _buildable(bool isElse) => + _Branch(condition: condition, body: body, isElse: isElse); +} + +/// Builds a [Conditional] branch. +abstract class BranchBuilder implements Builder { + BranchBuilder._(); + factory BranchBuilder() = _$BranchBuilder; + + /// The `if` statement condition. + /// + /// If this is the first branch in a [Conditional], [condition] is + /// required. Otherwise, it may be left `null` to create an `else` statement. + Expression? condition; + + /// The branch body. + BlockBuilder body = BlockBuilder(); + + /// Set [condition] to an `if-case` expression, matching [object] against + /// [pattern]. + /// + /// Guard clause [guard] can also be specified. + /// + /// ```dart + /// if (object case pattern) + /// if (object case pattern when guard) + /// ``` + /// + /// Equivalent to using [ControlFlow.ifCase]. + void ifCase({ + required Expression object, + required Expression pattern, + Expression? guard, + }) => + condition = + ControlFlow.ifCase(object: object, pattern: pattern, guard: guard); +} + +/// Buildable version of [Branch] +class _Branch extends _$Branch with ControlBlock { + final bool isElse; + + _Branch({required super.condition, required super.body, required this.isElse}) + : super._(); + + ControlExpression? get _condition => + condition == null ? null : ControlExpression.ifStatement(condition!); + + @override + ControlExpression get _expression => isElse + ? ControlExpression.elseStatement(_condition) + : (_condition ?? throwError); + + Never get throwError { + throw ArgumentError( + 'The first branch in a conditional must specify a condition', + 'condition'); + } +} + +/// Represents a conditional (`if`/`else`) tree. +/// +/// The first added branch will be treated as an `if` block, with +/// all subsequent conditions being treated as `else`. +/// +/// {@category controlFlow} +abstract class Conditional + with ControlTree + implements Built { + Conditional._(); + + /// Build an [Conditional] + factory Conditional(void Function(ConditionalBuilder tree) builder) = + _$Conditional; + + @protected + @visibleForTesting + BuiltList get branches; + + @override + List get _blocks => branches + .mapIndexed( + (index, element) => element.build()._buildable(index != 0), + ) + .toList(); + + /// Builds a branch with [builder] and returns + /// a new [Conditional] with it added to the tree. + Conditional elseIf(void Function(BranchBuilder branch) builder) => + (toBuilder()..branches.add(BranchBuilder()..update(builder))).build(); + + /// Builds a block with [builder] and returns a new [Conditional] + /// with it added to the tree as an `else` [Branch]. + Conditional orElse(void Function(BlockBuilder body) builder) => + elseIf((block) => builder(block.body)); +} + +/// Builds a [Conditional]. +/// +/// The first added branch will be treated as an `if` block, with +/// all subsequent conditions being treated as `else`. +/// +/// {@category controlFlow} +abstract class ConditionalBuilder + implements Builder { + ConditionalBuilder._(); + factory ConditionalBuilder() = _$ConditionalBuilder; + + /// The items in this tree. + ListBuilder branches = ListBuilder(); + + /// Build a branch with [builder] and add it to the conditional tree. + /// + /// The first branch will be an `if` block, and all subsequent branches + /// will be `else if` or `else`. + void add(void Function(BranchBuilder branch) builder) => + branches.add(BranchBuilder()..update(builder)); + + /// Shorthand to build a block with no condition and add it to the tree. + void addElse(void Function(BlockBuilder body) builder) => + branches.add(BranchBuilder()..body.update(builder)); +} diff --git a/pkgs/code_builder/lib/src/specs/control/handling.dart b/pkgs/code_builder/lib/src/specs/control/handling.dart new file mode 100644 index 0000000000..9bb46f35a5 --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/control/handling.dart @@ -0,0 +1,168 @@ +// 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 + +part of '../control.dart'; + +/// Represents a `catch` block. +/// +/// {@category controlFlow} +abstract class CatchBlock + with ControlBlock + implements Built { + CatchBlock._(); + factory CatchBlock([void Function(CatchBlockBuilder) updates]) = _$CatchBlock; + + /// The optional type of exception to catch (`on` clause). + /// + /// When [type] is set, leave [exception] and [stacktrace] + /// `null` to omit the `catch` statement. + /// + /// ``` dart + /// on type + /// on type catch (exception) + /// on type catch (exception, stacktrace) + /// ``` + Reference? get type; + + /// The optional name of the exception parameter. + /// + /// If a [type] is specified, leaving this and [stacktrace] null + /// will omit the `catch` statement entirely. + /// + /// If left `null` otherwise, a wildcard (`_`) will be used + /// as the exception name. + /// + /// ```dart + /// catch (exception) + /// catch (exception, stacktrace) + /// ``` + String? get exception; + + /// The optional name of the stacktrace parameter. + /// + /// Will be excluded if left `null`. + /// + /// ```dart + /// catch (exception) + /// catch (exception, stacktrace) + /// ``` + String? get stacktrace; + + ControlExpression get _catch => + ControlExpression.catchStatement(exception ?? '_', stacktrace); + + @override + ControlExpression get _expression { + if (type == null) return _catch; + + // omit catch clause if exception and stacktrace are unspecified + if (exception == null && stacktrace == null) { + return ControlExpression.onStatement(type!); + } + + return ControlExpression.onStatement(type!, _catch); + } +} + +/// Represents a `try` or `finally` block. +/// +/// **INTERNAL ONLY**. +@internal +class TryBlock with ControlBlock { + @override + final Block body; + final bool isFinally; + + const TryBlock._(this.body) : isFinally = false; + const TryBlock._finally(this.body) : isFinally = true; + + @override + ControlExpression get _expression => isFinally + ? ControlExpression.finallyStatement + : ControlExpression.tryStatement; +} + +/// Represents a `try`/`catch` block. +/// +/// {@category controlFlow} +abstract class TryCatch + with ControlTree + implements Built { + TryCatch._(); + + /// Build a [TryCatch]. + factory TryCatch([void Function(TryCatchBuilder) updates]) = _$TryCatch; + + /// The body of the `try` clause. + /// + /// ```dart + /// try { + /// body + /// } + /// ``` + Block get body; + + /// The `catch` clauses for this block. + BuiltList get handlers; + + /// The optional `finally` clause body. + /// + /// ```dart + /// finally { + /// handleAll + /// } + /// ``` + Block? get handleAll; + + TryBlock get _try => TryBlock._(body); + TryBlock? get _finally => + handleAll == null ? null : TryBlock._finally(handleAll!); + + @override + List get _blocks => [_try, ...handlers, _finally]; + + /// Ensure [handlers] is not empty + @BuiltValueHook(finalizeBuilder: true) + static void _build(TryCatchBuilder builder) => + builder.handlers.isNotEmpty || + (throw ArgumentError( + 'One or more `catch` clauses must be specified.', 'handlers')); +} + +/// Builds a [TryCatch] block. +/// +/// {@category controlFlow} +abstract class TryCatchBuilder implements Builder { + TryCatchBuilder._(); + factory TryCatchBuilder() = _$TryCatchBuilder; + + /// The body of the `try` clause. + /// + /// ```dart + /// try { + /// body + /// } + /// ``` + BlockBuilder body = BlockBuilder(); + + /// The optional `finally` clause body. + /// + /// ```dart + /// finally { + /// handleAll + /// } + /// ``` + BlockBuilder? handleAll; + + /// The `catch` clauses for this block. + ListBuilder handlers = ListBuilder(); + + /// Build a `catch` clause and add it to [handlers]. + void addCatch(void Function(CatchBlockBuilder block) builder) => + handlers.add((CatchBlockBuilder()..update(builder)).build()); + + /// Build a `finally` clause and update [handleAll]. + void addFinally(void Function(BlockBuilder body) builder) => + handleAll = BlockBuilder()..update(builder); +} diff --git a/pkgs/code_builder/lib/src/specs/control/loops.dart b/pkgs/code_builder/lib/src/specs/control/loops.dart new file mode 100644 index 0000000000..3e0a085e0b --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/control/loops.dart @@ -0,0 +1,125 @@ +// 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 + +part of '../control.dart'; + +/// Represents a traditional `for` loop. +/// +/// ```dart +/// for (initialize; condition; advance) { +/// body +/// } +/// ``` +/// +/// https://dart.dev/language/loops#for-loops +/// +/// {@category controlFlow} +abstract class ForLoop + with ControlBlock, LabeledControlBlock + implements Built { + ForLoop._(); + + /// The initializer expression. + /// + /// Leave `null` to omit. + Expression? get initialize; + + /// The for loop condition. + /// + /// Leave `null` to omit. + Expression? get condition; + + /// The advancer expression. + /// + /// Leave `null` to omit. + Expression? get advance; + + @override + ControlExpression get _expression => + ControlExpression.forLoop(initialize, condition, advance); + + factory ForLoop(void Function(ForLoopBuilder loop) builder) = _$ForLoop; +} + +/// Represents a `for-in` loop. +/// +/// ```dart +/// for (variable in object) { +/// body +/// } +/// ``` +/// +/// If [async] is `true`, the loop will be asynchronous (`await for`): +/// ```dart +/// await for (variable in object) { +/// body +/// } +/// ``` +/// +/// https://dart.dev/language/loops#for-loops +/// +/// {@category controlFlow} +abstract class ForInLoop + with ControlBlock, LabeledControlBlock + implements Built { + ForInLoop._(); + factory ForInLoop(void Function(ForInLoopBuilder loop) builder) = _$ForInLoop; + + /// Whether or not this is an asynchronous (`await for`) loop. + bool? get async; + + /// The iterated variable (before `in`). + Expression get variable; + + /// The object being iterated on (after `in`). + Expression get object; + + @override + ControlExpression get _expression => async == true + ? ControlExpression.awaitForLoop(variable, object) + : ControlExpression.forInLoop(variable, object); +} + +/// Represents a `while` loop. +/// +/// ```dart +/// while (condition) { +/// body +/// } +/// ``` +/// +/// If [doWhile] is `true`, the loop will be in the `do-while` format: +/// ```dart +/// do { +/// body +/// } while (condition); +/// ``` +/// +/// https://dart.dev/language/loops#while-and-do-while +/// +/// {@category controlFlow} +abstract class WhileLoop + with ControlBlock, LabeledControlBlock + implements Built { + WhileLoop._(); + factory WhileLoop(void Function(WhileLoopBuilder loop) builder) = _$WhileLoop; + + /// Whether or not this is a `do-while` loop. + bool? get doWhile; + + /// The loop condition. + Expression get condition; + + /// Always returns the `while` statement, regardless + /// of the value of [doWhile]. + ControlExpression get _statement => ControlExpression.whileLoop(condition); + + @override + ControlExpression get _expression => + doWhile == true ? ControlExpression.doStatement : _statement; + + @override + R accept(covariant ControlBlockVisitor visitor, [R? context]) => + visitor.visitWhileLoop(this, context); +} diff --git a/pkgs/code_builder/lib/src/specs/control/switch.dart b/pkgs/code_builder/lib/src/specs/control/switch.dart new file mode 100644 index 0000000000..f59519c73b --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/control/switch.dart @@ -0,0 +1,283 @@ +// 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. + +part of '../control.dart'; + +/// A `switch` case used in either a [SwitchStatement] or a [SwitchExpression]. +/// +/// The case type is determined by the generic parameter [T], which defines +/// the type of [body] required. +/// +/// For `switch` *statements*, [T] should be [Code] (e.g. a [Block]). +/// Bodies may be multi-line, and may also be left `null` to use case +/// fall-through. Additionally, labels are supported via [label]. +/// +/// ```dart +/// case pattern: +/// case pattern when guard: +/// body; +/// +/// label: +/// case pattern: +/// body; +/// body; +/// ``` +/// +/// For `switch` *expressions*, [T] must be a non-null [Expression]. +/// Fall-though is not supported in `switch` expressions, nor are labels. +/// Attempting to use fall-through by leaving [body] `null` will throw an +/// [ArgumentError], and setting [label] will have no effect. +/// +/// ```dart +/// pattern => body, +/// pattern when guard => body, +/// ``` +/// +/// {@category controlFlow} +abstract class Case implements Built, CaseBuilder> { + Case._(); + + /// Build a new [Case]. + factory Case([void Function(CaseBuilder) updates]) = _$Case; + + /// Create a catch-all case, either `default` or wildcard (`_`). + /// + /// For [SwitchStatement], the `default` keyword will be used. [label] will be + /// respected, if provided. To force use of the wildcard expression + /// instead, use [Case.new] with [Expression.wildcard] as the pattern. + /// + /// For [SwitchExpression], a wildcard case will be created, as `switch` + /// expressions don't support `default`. [label] will be ignored. + factory Case.any(T body, {String? label}) = DefaultCase._; + + /// The pattern to match. + Expression get pattern; + + /// The optional guard (`when`) clause. + Expression? get guard; + + /// An optional label for this case. + /// + /// **NOTE:** Only `switch` *statements* ([SwitchStatement]) support labels. + /// Setting [label] with `switch` *expressions* ([SwitchExpression]) has no + /// effect; the label will be silently ignored. + String? get label; + + /// Whether or not to use the `default` keyword + bool get _default => false; + + /// The body of this case. + /// + /// May be left null when used in a [SwitchStatement] to use case + /// fall-through. + /// + /// May **not** be left null in a [SwitchExpression], as they do not support + /// fall-through. Will throw an [ArgumentError] if left unset. + //* T must be nullable, otherwise built_value will perform a check that it was + //* actually set, causing an error when left null to use fallthrough. Instead, + //* it is up to the buildable case implementations (see below) to validate + //* this value. + T? get body; +} + +/// **INTERNAL** +/// Case with `default` keyword +@internal +class DefaultCase extends _$Case { + DefaultCase._(T body, {super.label}) + : super._(body: body, pattern: Expression.wildcard); + + @override + bool get _default => true; +} + +/// **INTERNAL** +/// Buildable case statement +@internal +class CaseStatement extends _$Case implements Code { + final Case item; + + CaseStatement._(this.item) + : super._( + pattern: item.pattern, + body: item.body, + guard: item.guard, + label: item.label); + + @override + bool get _default => item._default; + + @override + R accept(covariant ControlBlockVisitor visitor, [R? context]) => + visitor._visitCaseStatement(this, context); +} + +/// **INTERNAL** +/// Buildable case expression +@internal +class CaseExpression extends _$Case implements Code { + // no need to store item, as _default functionality is not needed + // (case/switch expressions don't support the `default` keyword) + + CaseExpression._(Case item) + : super._( + pattern: item.pattern, + body: item.body ?? + (throw ArgumentError( + 'Cases in `switch` expressions must provide ' + 'a non-null body.', + 'body')), + guard: item.guard); + + @override + R accept(covariant ControlBlockVisitor visitor, [R? context]) => + visitor._visitCaseExpression(this, context); +} + +/// **INTERNAL** +/// Base class for `switch` types. +@internal +@optionalTypeArgs +@BuiltValue(instantiable: false) +abstract class Switch implements Code, Spec { + /// The value being matched against. + /// + /// ```dart + /// switch (value) { + /// ... + /// } + /// ``` + Expression get value; + + /// The cases in the `switch` body. + BuiltList> get cases; + + /// Convert generic [Case] into an implementation-specific buildable + /// subtype. + //* ignore from test coverage as there is no way to call this. + //* it can't be abstract, otherwise built_value will treat it + //* as a field on the class and throw an error because it's private. + // coverage:ignore-start + Iterable get _cases => + throw UnsupportedError('Must be implemented by subclasses'); + // coverage:ignore-end +} + +/// **INTERNAL** +/// Base class for `switch` builders +@internal +@optionalTypeArgs +abstract class SwitchBuilder { + /// The value being matched against. + /// + /// ```dart + /// switch (value) { + /// ... + /// } + /// ``` + Expression? value; + + /// The cases in the `switch` body. + ListBuilder> cases = ListBuilder(); +} + +/// **INTERNAL** +/// +/// A buildable switch class. [Switch] subtypes are converted into +/// this by [ControlBlockVisitor.visitSwitch] in order to be built. +/// +@internal +@optionalTypeArgs +class BuildableSwitch with ControlBlock { + final Expression value; + final Iterable cases; + + BuildableSwitch({required this.value, required this.cases}); + + @override + ControlExpression get _expression => ControlExpression.switchStatement(value); + + @override + Block get body => Block.of(cases); +} + +/// Represents a `switch` statement. +/// +/// `switch` *statements* are standalone `switch` blocks that +/// use the `case` keyword and execute the case body when matched. Unlike +/// `switch` *expressions*, they do not return any value. See +/// https://dart.dev/language/branches#switch-statements. +/// +/// ```dart +/// switch (value) { +/// case value: +/// case otherValue: +/// body; +/// +/// case value when guard: +/// body; +/// continue label; +/// +/// label: +/// default: +/// body; +/// } +/// ``` +/// [Case]s used in a [SwitchStatement] may leave their [Case.body] `null` in +/// order to use case fall-through. They can also specify labels ([Case.label]) +/// and guard clauses ([Case.guard]). +/// +abstract class SwitchStatement + implements Switch, Built { + SwitchStatement._(); + + /// Build a [SwitchStatement]. + factory SwitchStatement([void Function(SwitchStatementBuilder) updates]) = + _$SwitchStatement; + + @override + Iterable get _cases => cases.map(CaseStatement._); + + @override + T accept(covariant ControlBlockVisitor visitor, [T? context]) => + visitor.visitSwitch(this, context); +} + +/// Represents a `switch` expression. +/// +/// `switch` *expressions* are `switch` blocks that return the body of the +/// matched case and use the arrow (`=>`) syntax. Unlike `switch` statements, +/// they do not use the `case` keyword, and case bodies can only consist of +/// a single expression. See +/// https://dart.dev/language/branches#switch-expressions. +/// +/// ```dart +/// final variable = switch (value) { +/// value => body, +/// value when guard => body, +/// _ => body, +/// }; +/// ``` +/// +/// [Case]s used in a [SwitchExpression] must have a non-`null` body. They may +/// contain guard clauses ([Case.guard]) but not labels ([Case.label]). If a +/// label is specified, it will be ignored. +/// +abstract class SwitchExpression extends Expression + implements + Switch, + Built { + SwitchExpression._(); + + /// Build a [SwitchExpression]. + factory SwitchExpression([void Function(SwitchExpressionBuilder) updates]) = + _$SwitchExpression; + + @override + T accept(covariant ControlBlockVisitor visitor, [T? context]) => + visitor.visitSwitch(this, context); + + @override + Iterable get _cases => cases.map(CaseExpression._); +} diff --git a/pkgs/code_builder/lib/src/specs/directive.g.dart b/pkgs/code_builder/lib/src/specs/directive.g.dart index b28158e075..9f45b85445 100644 --- a/pkgs/code_builder/lib/src/specs/directive.g.dart +++ b/pkgs/code_builder/lib/src/specs/directive.g.dart @@ -21,7 +21,7 @@ class _$Directive extends Directive { final bool deferred; factory _$Directive([void Function(DirectiveBuilder)? updates]) => - (new DirectiveBuilder()..update(updates)).build() as _$Directive; + (DirectiveBuilder()..update(updates)).build() as _$Directive; _$Directive._( {this.as, @@ -30,20 +30,13 @@ class _$Directive extends Directive { required this.show, required this.hide, required this.deferred}) - : super._() { - BuiltValueNullFieldError.checkNotNull(url, r'Directive', 'url'); - BuiltValueNullFieldError.checkNotNull(type, r'Directive', 'type'); - BuiltValueNullFieldError.checkNotNull(show, r'Directive', 'show'); - BuiltValueNullFieldError.checkNotNull(hide, r'Directive', 'hide'); - BuiltValueNullFieldError.checkNotNull(deferred, r'Directive', 'deferred'); - } - + : super._(); @override Directive rebuild(void Function(DirectiveBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$DirectiveBuilder toBuilder() => new _$DirectiveBuilder()..replace(this); + _$DirectiveBuilder toBuilder() => _$DirectiveBuilder()..replace(this); @override bool operator ==(Object other) { @@ -176,7 +169,6 @@ class _$DirectiveBuilder extends DirectiveBuilder { @override void replace(Directive other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Directive; } @@ -190,18 +182,18 @@ class _$DirectiveBuilder extends DirectiveBuilder { _$Directive _build() { final _$result = _$v ?? - new _$Directive._( - as: as, - url: - BuiltValueNullFieldError.checkNotNull(url, r'Directive', 'url'), - type: BuiltValueNullFieldError.checkNotNull( - type, r'Directive', 'type'), - show: BuiltValueNullFieldError.checkNotNull( - show, r'Directive', 'show'), - hide: BuiltValueNullFieldError.checkNotNull( - hide, r'Directive', 'hide'), - deferred: BuiltValueNullFieldError.checkNotNull( - deferred, r'Directive', 'deferred')); + _$Directive._( + as: as, + url: BuiltValueNullFieldError.checkNotNull(url, r'Directive', 'url'), + type: + BuiltValueNullFieldError.checkNotNull(type, r'Directive', 'type'), + show: + BuiltValueNullFieldError.checkNotNull(show, r'Directive', 'show'), + hide: + BuiltValueNullFieldError.checkNotNull(hide, r'Directive', 'hide'), + deferred: BuiltValueNullFieldError.checkNotNull( + deferred, r'Directive', 'deferred'), + ); replace(_$result); return _$result; } diff --git a/pkgs/code_builder/lib/src/specs/enum.g.dart b/pkgs/code_builder/lib/src/specs/enum.g.dart index 651cc610a7..eb7407222f 100644 --- a/pkgs/code_builder/lib/src/specs/enum.g.dart +++ b/pkgs/code_builder/lib/src/specs/enum.g.dart @@ -29,7 +29,7 @@ class _$Enum extends Enum { final BuiltList fields; factory _$Enum([void Function(EnumBuilder)? updates]) => - (new EnumBuilder()..update(updates)).build() as _$Enum; + (EnumBuilder()..update(updates)).build() as _$Enum; _$Enum._( {required this.name, @@ -42,26 +42,13 @@ class _$Enum extends Enum { required this.constructors, required this.methods, required this.fields}) - : super._() { - BuiltValueNullFieldError.checkNotNull(name, r'Enum', 'name'); - BuiltValueNullFieldError.checkNotNull(values, r'Enum', 'values'); - BuiltValueNullFieldError.checkNotNull(annotations, r'Enum', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'Enum', 'docs'); - BuiltValueNullFieldError.checkNotNull(implements, r'Enum', 'implements'); - BuiltValueNullFieldError.checkNotNull(mixins, r'Enum', 'mixins'); - BuiltValueNullFieldError.checkNotNull(types, r'Enum', 'types'); - BuiltValueNullFieldError.checkNotNull( - constructors, r'Enum', 'constructors'); - BuiltValueNullFieldError.checkNotNull(methods, r'Enum', 'methods'); - BuiltValueNullFieldError.checkNotNull(fields, r'Enum', 'fields'); - } - + : super._(); @override Enum rebuild(void Function(EnumBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$EnumBuilder toBuilder() => new _$EnumBuilder()..replace(this); + _$EnumBuilder toBuilder() => _$EnumBuilder()..replace(this); @override bool operator ==(Object other) { @@ -258,7 +245,6 @@ class _$EnumBuilder extends EnumBuilder { @override void replace(Enum other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Enum; } @@ -274,18 +260,18 @@ class _$EnumBuilder extends EnumBuilder { _$Enum _$result; try { _$result = _$v ?? - new _$Enum._( - name: - BuiltValueNullFieldError.checkNotNull(name, r'Enum', 'name'), - values: values.build(), - annotations: annotations.build(), - docs: docs.build(), - implements: implements.build(), - mixins: mixins.build(), - types: types.build(), - constructors: constructors.build(), - methods: methods.build(), - fields: fields.build()); + _$Enum._( + name: BuiltValueNullFieldError.checkNotNull(name, r'Enum', 'name'), + values: values.build(), + annotations: annotations.build(), + docs: docs.build(), + implements: implements.build(), + mixins: mixins.build(), + types: types.build(), + constructors: constructors.build(), + methods: methods.build(), + fields: fields.build(), + ); } catch (_) { late String _$failedField; try { @@ -308,8 +294,7 @@ class _$EnumBuilder extends EnumBuilder { _$failedField = 'fields'; fields.build(); } catch (e) { - throw new BuiltValueNestedFieldError( - r'Enum', _$failedField, e.toString()); + throw BuiltValueNestedFieldError(r'Enum', _$failedField, e.toString()); } rethrow; } @@ -335,7 +320,7 @@ class _$EnumValue extends EnumValue { final BuiltMap namedArguments; factory _$EnumValue([void Function(EnumValueBuilder)? updates]) => - (new EnumValueBuilder()..update(updates)).build() as _$EnumValue; + (EnumValueBuilder()..update(updates)).build() as _$EnumValue; _$EnumValue._( {required this.name, @@ -345,23 +330,13 @@ class _$EnumValue extends EnumValue { required this.types, required this.arguments, required this.namedArguments}) - : super._() { - BuiltValueNullFieldError.checkNotNull(name, r'EnumValue', 'name'); - BuiltValueNullFieldError.checkNotNull( - annotations, r'EnumValue', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'EnumValue', 'docs'); - BuiltValueNullFieldError.checkNotNull(types, r'EnumValue', 'types'); - BuiltValueNullFieldError.checkNotNull(arguments, r'EnumValue', 'arguments'); - BuiltValueNullFieldError.checkNotNull( - namedArguments, r'EnumValue', 'namedArguments'); - } - + : super._(); @override EnumValue rebuild(void Function(EnumValueBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$EnumValueBuilder toBuilder() => new _$EnumValueBuilder()..replace(this); + _$EnumValueBuilder toBuilder() => _$EnumValueBuilder()..replace(this); @override bool operator ==(Object other) { @@ -510,7 +485,6 @@ class _$EnumValueBuilder extends EnumValueBuilder { @override void replace(EnumValue other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$EnumValue; } @@ -526,15 +500,16 @@ class _$EnumValueBuilder extends EnumValueBuilder { _$EnumValue _$result; try { _$result = _$v ?? - new _$EnumValue._( - name: BuiltValueNullFieldError.checkNotNull( - name, r'EnumValue', 'name'), - annotations: annotations.build(), - docs: docs.build(), - constructorName: constructorName, - types: types.build(), - arguments: arguments.build(), - namedArguments: namedArguments.build()); + _$EnumValue._( + name: BuiltValueNullFieldError.checkNotNull( + name, r'EnumValue', 'name'), + annotations: annotations.build(), + docs: docs.build(), + constructorName: constructorName, + types: types.build(), + arguments: arguments.build(), + namedArguments: namedArguments.build(), + ); } catch (_) { late String _$failedField; try { @@ -550,7 +525,7 @@ class _$EnumValueBuilder extends EnumValueBuilder { _$failedField = 'namedArguments'; namedArguments.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'EnumValue', _$failedField, e.toString()); } rethrow; diff --git a/pkgs/code_builder/lib/src/specs/expression.dart b/pkgs/code_builder/lib/src/specs/expression.dart index aa06de2833..9afcf7c666 100644 --- a/pkgs/code_builder/lib/src/specs/expression.dart +++ b/pkgs/code_builder/lib/src/specs/expression.dart @@ -8,6 +8,7 @@ import '../base.dart'; import '../emitter.dart'; import '../visitors.dart'; import 'code.dart'; +import 'control.dart'; import 'method.dart'; import 'reference.dart'; import 'type_function.dart'; @@ -18,6 +19,7 @@ part 'expression/code.dart'; part 'expression/invoke.dart'; part 'expression/literal.dart'; part 'expression/parenthesized.dart'; +part 'expression/control.dart'; /// Represents a [code] block that wraps an [Expression]. @@ -436,6 +438,9 @@ abstract class Expression implements Spec { /// Returns this expression wrapped in parenthesis. ParenthesizedExpression get parenthesized => ParenthesizedExpression._(this); + + /// Wildcard expression (`_`). + static const wildcard = LiteralExpression._('_'); } /// Declare a const variable named [variableName]. @@ -504,6 +509,27 @@ class ToCodeExpression implements Code { String toString() => code.toString(); } +extension on Iterable { + /// Get the length of the iterable counting any chained + /// [CollectionExpression]s as a single item. + int get adjustedLength { + var chain = false; + return where( + (element) { + if (element is! CollectionExpression) { + chain = false; + return true; + } + + final skip = element.chain && chain; + chain = element.chainTarget; + + return !skip; + }, + ).length; + } +} + /// Knowledge of different types of expressions in Dart. /// /// **INTERNAL ONLY**. @@ -647,7 +673,7 @@ abstract mixin class ExpressionEmitter visitAll(expression.values, out, (value) { _acceptLiteral(value, out); }); - if (expression.values.length > 1) { + if (expression.values.adjustedLength > 1) { out.write(', '); } return out..write(']'); @@ -671,7 +697,7 @@ abstract mixin class ExpressionEmitter visitAll(expression.values, out, (value) { _acceptLiteral(value, out); }); - if (expression.values.length > 1) { + if (expression.values.adjustedLength > 1) { out.write(', '); } return out..write('}'); @@ -705,7 +731,7 @@ abstract mixin class ExpressionEmitter } _acceptLiteral(value, out); }); - if (expression.values.length > 1) { + if (expression.values.keys.adjustedLength > 1) { out.write(', '); } return out..write('}'); diff --git a/pkgs/code_builder/lib/src/specs/expression/control.dart b/pkgs/code_builder/lib/src/specs/expression/control.dart new file mode 100644 index 0000000000..99147e9bc4 --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/expression/control.dart @@ -0,0 +1,406 @@ +// 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. + +part of '../expression.dart'; + +/// **INTERNAL** +/// +/// Represents a control expression. +/// +/// {@category controlFlow} +@internal +class ControlExpression extends Expression { + /// The control statement (e.g. `if`, `for`). + final String control; + + /// Zero or more expressions that make up this control + /// expression's body. + /// + /// If multiple expressions are provided, they will be + /// separated with [separator]. + /// + /// If [parenthesised] is `true`, the whole body will be + /// wrapped in parenthesis. + /// + /// If [body] is `null` or empty, the body will be omitted. + /// If individual items are `null`, they will be omitted, + /// but separators will still be inserted. + final List? body; + + /// Inserted between expressions in [body]. + /// + /// If body contains multiple items, a non-`null` separator is required. + /// An [ArgumentError] will be thrown if one is not provided. + /// + /// A space (" ") will be appended to the separator if it is followed + /// by an expression. If an item in [body] is `null` (resulting in a + /// blank string), no space will be inserted before it. + final String? separator; + + /// Whether or not the body should be wrapped in parenthesis (default: `true`) + final bool parenthesised; + + @visibleForTesting + const ControlExpression(this.control, + {this.body, this.separator, this.parenthesised = true}); + + @override + R accept(covariant ControlBlockVisitor visitor, [R? context]) => + visitor.visitControlExpression(this, context); + + factory ControlExpression.ifStatement(Expression condition) => + ControlExpression('if', body: [condition]); + + factory ControlExpression.elseStatement(Expression? condition) => + ControlExpression('else', + body: condition != null ? [condition] : null, parenthesised: false); + + factory ControlExpression.forLoop( + Expression? initialize, Expression? condition, Expression? advance) => + ControlExpression('for', + body: [initialize, condition, advance], separator: ';'); + + factory ControlExpression.forInLoop( + Expression identifier, Expression expression) => + ControlExpression('for', + body: [identifier, expression], separator: ' in'); + + factory ControlExpression.awaitForLoop( + Expression identifier, Expression expression) => + ControlExpression('await for', + body: [identifier, expression], separator: ' in'); + + factory ControlExpression.whileLoop(Expression condition) => + ControlExpression('while', body: [condition]); + + static const doStatement = ControlExpression('do'); + + static const tryStatement = ControlExpression('try'); + + factory ControlExpression.catchStatement(String error, + [String? stacktrace]) => + ControlExpression('catch', + body: [refer(error), if (stacktrace != null) refer(stacktrace)], + separator: ','); + + factory ControlExpression.onStatement(Reference type, + [ControlExpression? statement]) => + ControlExpression('on', + body: [type, if (statement != null) statement], + parenthesised: false, + separator: ''); + + static const finallyStatement = ControlExpression('finally'); + + factory ControlExpression.switchStatement(Expression value) => + ControlExpression('switch', body: [value]); +} + +/// **INTERNAL** +/// +/// A collection control-flow expression +/// +/// Supports chaining when used in collections via [chainTarget] and [chain]. +/// These fields have no effect when used outside of collections. +/// +@internal +class CollectionExpression extends BinaryExpression { + /// Whether the [CollectionExpression] that follows this in a collection + /// may chain with it. Chained expressions will not have a comma between + /// them. + final bool chainTarget; + + /// Whether this [CollectionExpression] should try to chain with its + /// antecedent in collections. + final bool chain; + + const CollectionExpression._({ + required Expression control, + required Expression value, + this.chain = false, + this.chainTarget = false, + }) : super._(control, value, ''); +} + +/// Provides control-flow utilities for [Expression]. +/// +/// {@category controlFlow} +extension ControlFlow on Expression { + /// Returns `yield {this}` + Expression get yielded => + BinaryExpression._(Expression._empty, this, 'yield'); + + /// Returns `yield* {this}` + Expression get yieldStarred => + BinaryExpression._(Expression._empty, this, 'yield*'); + + /// Build a `while` loop from this expression. + /// + /// ```dart + /// while (this) { + /// body + /// } + /// ``` + WhileLoop loopWhile(void Function(BlockBuilder block) builder) => + WhileLoop((loop) => loop + ..condition = this + ..body.update(builder)); + + /// Build a `do-while` loop from this expression. + /// + /// ```dart + /// do { + /// body + /// } while (this); + /// ``` + WhileLoop loopDoWhile(void Function(BlockBuilder block) builder) => + WhileLoop((loop) => loop + ..doWhile = true + ..condition = this + ..body.update(builder)); + + /// Build a `for-in` loop from this expression in [object]. + /// + /// ```dart + /// for (this in object) { + /// body + /// } + /// ``` + ForInLoop loopForIn( + Expression object, void Function(BlockBuilder block) builder) => + ForInLoop((loop) => loop + ..object = object + ..variable = this + ..body.update(builder)); + + /// Build this expression into a [Conditional] + /// + /// ```dart + /// if (this) { + /// body + /// } + /// ``` + /// + /// Chain [Conditional.elseIf] and [Conditional.orElse] to + /// easily create a full `if-elseif-else` tree: + /// + /// ```dart + /// literal(1) + /// .equalTo(literal(2)) + /// .ifThen( + /// (body) => body.addExpression( + /// refer('print').call([literal('Bad')])) + /// ).elseIf( + /// (block) => block + /// ..condition = literal(2).equalTo(literal(2)) + /// ..body.addExpression(refer('print').call([literal('Good')])), + /// ).orElse( + /// (body) => body.addExpression( + /// refer('print').call([literal('What?')])), + /// ); + /// ``` + /// + /// Outputs: + /// ```dart + /// if (1 == 2) { + /// print('Bad'); + /// } else if (2 == 2) { + /// print('Good'); + /// } else { + /// print('What?'); + /// } + /// ``` + Conditional ifThen(void Function(BlockBuilder body) builder) => Conditional( + (tree) => tree + ..add( + (branch) => branch + ..condition = this + ..body.update(builder), + ), + ); + + /// Returns `if (this) return value` + Expression ifThenReturn([Expression? value]) => BinaryExpression._( + ControlExpression.ifStatement(this), + value == null ? ControlFlow.returnVoid : value.returned, + ''); + + /// `return` + static const returnVoid = LiteralExpression._('return'); + + /// `break` + /// + /// For usage with a label, use [breakLabel]. + static const breakVoid = LiteralExpression._('break'); + + /// `continue` + /// + /// For usage with a label, use [continueLabel]. + static const continueVoid = LiteralExpression._('continue'); + + /// `rethrow` + static const rethrowVoid = LiteralExpression._('rethrow'); + + /// Returns a labeled `break` statement. + /// + /// ```dart + /// break label + /// ``` + /// + /// For usage without a label, use [breakVoid]. + static Expression breakLabel(String label) => + BinaryExpression._(breakVoid, refer(label), ''); + + /// Returns a labeled `continue` statement. + /// + /// ```dart + /// continue label + /// ``` + /// + /// For usage without a label, use [continueVoid]. + static Expression continueLabel(String label) => + BinaryExpression._(continueVoid, refer(label), ''); + + /// Returns a case match expression for an `if-case` statement, + /// matching [object] against [pattern] with optional guard clause [guard]. + /// + /// ```dart + /// object case pattern + /// object case pattern when guard + /// ``` + /// + /// See https://dart.dev/language/branches#if-case + static Expression ifCase( + {required Expression object, + required Expression pattern, + Expression? guard}) { + final first = BinaryExpression._(object, pattern, 'case'); + if (guard == null) return first; + return BinaryExpression._(first, guard, 'when'); + } + + /// Returns a collection-`if` expression. + /// + /// ```dart + /// if (condition) value + /// ``` + /// + /// {@template controlflow.collection.chaining.if} + /// [collectionIf] expressions followed by [collectionElse] expressions + /// in a [literal] list, set, or map will be chained together, e.g: + /// + /// ```dart + /// literalMap({ + /// 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'), + /// }); + /// ``` + /// Outputs: + /// ```dart + /// { + /// if (true) key: value + /// else if (false) key2: value2 + /// else key3: value3 + /// } + /// ``` + /// {@endtemplate} + static Expression collectionIf({ + required Expression condition, + required Expression value, + }) => + CollectionExpression._( + chainTarget: true, + control: ControlExpression.ifStatement(condition), + value: value); + + /// Returns a collection-`else` expression. + /// + /// If [condition] is specified, returns a collection-`else if` expression. + /// + /// ```dart + /// else value + /// else if (condition) value + /// ``` + /// + /// {@macro controlflow.collection.chaining.if} + static Expression collectionElse({ + Expression? condition, + required Expression value, + }) => + CollectionExpression._( + chain: true, + chainTarget: condition != null, + // only chainable if this is an else-if statement + control: ControlExpression.elseStatement(condition == null + ? null + : ControlExpression.ifStatement(condition)), + value: value); + + /// Returns a collection-`for` expression. + /// + /// ```dart + /// for (initialize; condition; advance) value + /// ``` + /// + /// {@template controlflow.collection.chaining.for} + /// If [value] is chainable (a [collectionIf], [collectionElse] with a + /// condition, or a collection-`for`/`for-in` containing one of those), + /// this will also be chainable. If this is followed by a collection-`else` + /// in a [literal] collection, they will be chained together. + /// + /// ```dart + /// 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')]) + /// }); + /// ``` + /// Outputs: + /// ```dart + /// { + /// for (final x in items) if (x.valid) key: x + /// else key2: fix + /// } + /// ``` + /// {@endtemplate} + static Expression collectionFor( + {Expression? initialize, + Expression? condition, + Expression? advance, + required Expression value}) => + CollectionExpression._( + chainTarget: true, + control: ControlExpression.forLoop(initialize, condition, advance), + value: value); + + /// Returns a collection-`for-in` expression + /// + /// ```dart + /// for (identifier in expression) value + /// ``` + /// + /// {@macro controlflow.collection.chaining.for} + static Expression collectionForIn({ + required Expression identifier, + required Expression expression, + required Expression value, + }) => + CollectionExpression._( + chainTarget: value is CollectionExpression && value.chainTarget, + control: ControlExpression.forInLoop(identifier, expression), + value: value); +} diff --git a/pkgs/code_builder/lib/src/specs/extension.g.dart b/pkgs/code_builder/lib/src/specs/extension.g.dart index 83de17614e..e66fb1b827 100644 --- a/pkgs/code_builder/lib/src/specs/extension.g.dart +++ b/pkgs/code_builder/lib/src/specs/extension.g.dart @@ -23,7 +23,7 @@ class _$Extension extends Extension { final String? name; factory _$Extension([void Function(ExtensionBuilder)? updates]) => - (new ExtensionBuilder()..update(updates)).build() as _$Extension; + (ExtensionBuilder()..update(updates)).build() as _$Extension; _$Extension._( {required this.annotations, @@ -33,21 +33,13 @@ class _$Extension extends Extension { required this.methods, required this.fields, this.name}) - : super._() { - BuiltValueNullFieldError.checkNotNull( - annotations, r'Extension', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'Extension', 'docs'); - BuiltValueNullFieldError.checkNotNull(types, r'Extension', 'types'); - BuiltValueNullFieldError.checkNotNull(methods, r'Extension', 'methods'); - BuiltValueNullFieldError.checkNotNull(fields, r'Extension', 'fields'); - } - + : super._(); @override Extension rebuild(void Function(ExtensionBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$ExtensionBuilder toBuilder() => new _$ExtensionBuilder()..replace(this); + _$ExtensionBuilder toBuilder() => _$ExtensionBuilder()..replace(this); @override bool operator ==(Object other) { @@ -196,7 +188,6 @@ class _$ExtensionBuilder extends ExtensionBuilder { @override void replace(Extension other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Extension; } @@ -212,14 +203,15 @@ class _$ExtensionBuilder extends ExtensionBuilder { _$Extension _$result; try { _$result = _$v ?? - new _$Extension._( - annotations: annotations.build(), - docs: docs.build(), - on: on, - types: types.build(), - methods: methods.build(), - fields: fields.build(), - name: name); + _$Extension._( + annotations: annotations.build(), + docs: docs.build(), + on: on, + types: types.build(), + methods: methods.build(), + fields: fields.build(), + name: name, + ); } catch (_) { late String _$failedField; try { @@ -235,7 +227,7 @@ class _$ExtensionBuilder extends ExtensionBuilder { _$failedField = 'fields'; fields.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'Extension', _$failedField, e.toString()); } rethrow; diff --git a/pkgs/code_builder/lib/src/specs/extension_type.g.dart b/pkgs/code_builder/lib/src/specs/extension_type.g.dart index 5769840328..5c02e7ff5b 100644 --- a/pkgs/code_builder/lib/src/specs/extension_type.g.dart +++ b/pkgs/code_builder/lib/src/specs/extension_type.g.dart @@ -31,7 +31,7 @@ class _$ExtensionType extends ExtensionType { final BuiltList methods; factory _$ExtensionType([void Function(ExtensionTypeBuilder)? updates]) => - (new ExtensionTypeBuilder()..update(updates)).build() as _$ExtensionType; + (ExtensionTypeBuilder()..update(updates)).build() as _$ExtensionType; _$ExtensionType._( {required this.annotations, @@ -45,33 +45,13 @@ class _$ExtensionType extends ExtensionType { required this.constructors, required this.fields, required this.methods}) - : super._() { - BuiltValueNullFieldError.checkNotNull( - annotations, r'ExtensionType', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'ExtensionType', 'docs'); - BuiltValueNullFieldError.checkNotNull( - constant, r'ExtensionType', 'constant'); - BuiltValueNullFieldError.checkNotNull(name, r'ExtensionType', 'name'); - BuiltValueNullFieldError.checkNotNull(types, r'ExtensionType', 'types'); - BuiltValueNullFieldError.checkNotNull( - primaryConstructorName, r'ExtensionType', 'primaryConstructorName'); - BuiltValueNullFieldError.checkNotNull(representationDeclaration, - r'ExtensionType', 'representationDeclaration'); - BuiltValueNullFieldError.checkNotNull( - implements, r'ExtensionType', 'implements'); - BuiltValueNullFieldError.checkNotNull( - constructors, r'ExtensionType', 'constructors'); - BuiltValueNullFieldError.checkNotNull(fields, r'ExtensionType', 'fields'); - BuiltValueNullFieldError.checkNotNull(methods, r'ExtensionType', 'methods'); - } - + : super._(); @override ExtensionType rebuild(void Function(ExtensionTypeBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$ExtensionTypeBuilder toBuilder() => - new _$ExtensionTypeBuilder()..replace(this); + _$ExtensionTypeBuilder toBuilder() => _$ExtensionTypeBuilder()..replace(this); @override bool operator ==(Object other) { @@ -285,7 +265,6 @@ class _$ExtensionTypeBuilder extends ExtensionTypeBuilder { @override void replace(ExtensionType other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$ExtensionType; } @@ -301,26 +280,27 @@ class _$ExtensionTypeBuilder extends ExtensionTypeBuilder { _$ExtensionType _$result; try { _$result = _$v ?? - new _$ExtensionType._( - annotations: annotations.build(), - docs: docs.build(), - constant: BuiltValueNullFieldError.checkNotNull( - constant, r'ExtensionType', 'constant'), - name: BuiltValueNullFieldError.checkNotNull( - name, r'ExtensionType', 'name'), - types: types.build(), - primaryConstructorName: BuiltValueNullFieldError.checkNotNull( - primaryConstructorName, - r'ExtensionType', - 'primaryConstructorName'), - representationDeclaration: BuiltValueNullFieldError.checkNotNull( - representationDeclaration, - r'ExtensionType', - 'representationDeclaration'), - implements: implements.build(), - constructors: constructors.build(), - fields: fields.build(), - methods: methods.build()); + _$ExtensionType._( + annotations: annotations.build(), + docs: docs.build(), + constant: BuiltValueNullFieldError.checkNotNull( + constant, r'ExtensionType', 'constant'), + name: BuiltValueNullFieldError.checkNotNull( + name, r'ExtensionType', 'name'), + types: types.build(), + primaryConstructorName: BuiltValueNullFieldError.checkNotNull( + primaryConstructorName, + r'ExtensionType', + 'primaryConstructorName'), + representationDeclaration: BuiltValueNullFieldError.checkNotNull( + representationDeclaration, + r'ExtensionType', + 'representationDeclaration'), + implements: implements.build(), + constructors: constructors.build(), + fields: fields.build(), + methods: methods.build(), + ); } catch (_) { late String _$failedField; try { @@ -341,7 +321,7 @@ class _$ExtensionTypeBuilder extends ExtensionTypeBuilder { _$failedField = 'methods'; methods.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'ExtensionType', _$failedField, e.toString()); } rethrow; @@ -363,7 +343,7 @@ class _$RepresentationDeclaration extends RepresentationDeclaration { factory _$RepresentationDeclaration( [void Function(RepresentationDeclarationBuilder)? updates]) => - (new RepresentationDeclarationBuilder()..update(updates)).build() + (RepresentationDeclarationBuilder()..update(updates)).build() as _$RepresentationDeclaration; _$RepresentationDeclaration._( @@ -371,17 +351,7 @@ class _$RepresentationDeclaration extends RepresentationDeclaration { required this.docs, required this.declaredRepresentationType, required this.name}) - : super._() { - BuiltValueNullFieldError.checkNotNull( - annotations, r'RepresentationDeclaration', 'annotations'); - BuiltValueNullFieldError.checkNotNull( - docs, r'RepresentationDeclaration', 'docs'); - BuiltValueNullFieldError.checkNotNull(declaredRepresentationType, - r'RepresentationDeclaration', 'declaredRepresentationType'); - BuiltValueNullFieldError.checkNotNull( - name, r'RepresentationDeclaration', 'name'); - } - + : super._(); @override RepresentationDeclaration rebuild( void Function(RepresentationDeclarationBuilder) updates) => @@ -389,7 +359,7 @@ class _$RepresentationDeclaration extends RepresentationDeclaration { @override _$RepresentationDeclarationBuilder toBuilder() => - new _$RepresentationDeclarationBuilder()..replace(this); + _$RepresentationDeclarationBuilder()..replace(this); @override bool operator ==(Object other) { @@ -491,7 +461,6 @@ class _$RepresentationDeclarationBuilder @override void replace(RepresentationDeclaration other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$RepresentationDeclaration; } @@ -507,15 +476,16 @@ class _$RepresentationDeclarationBuilder _$RepresentationDeclaration _$result; try { _$result = _$v ?? - new _$RepresentationDeclaration._( - annotations: annotations.build(), - docs: docs.build(), - declaredRepresentationType: BuiltValueNullFieldError.checkNotNull( - declaredRepresentationType, - r'RepresentationDeclaration', - 'declaredRepresentationType'), - name: BuiltValueNullFieldError.checkNotNull( - name, r'RepresentationDeclaration', 'name')); + _$RepresentationDeclaration._( + annotations: annotations.build(), + docs: docs.build(), + declaredRepresentationType: BuiltValueNullFieldError.checkNotNull( + declaredRepresentationType, + r'RepresentationDeclaration', + 'declaredRepresentationType'), + name: BuiltValueNullFieldError.checkNotNull( + name, r'RepresentationDeclaration', 'name'), + ); } catch (_) { late String _$failedField; try { @@ -524,7 +494,7 @@ class _$RepresentationDeclarationBuilder _$failedField = 'docs'; docs.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'RepresentationDeclaration', _$failedField, e.toString()); } rethrow; diff --git a/pkgs/code_builder/lib/src/specs/field.g.dart b/pkgs/code_builder/lib/src/specs/field.g.dart index d15f1c7dcb..18e1cf85c1 100644 --- a/pkgs/code_builder/lib/src/specs/field.g.dart +++ b/pkgs/code_builder/lib/src/specs/field.g.dart @@ -27,7 +27,7 @@ class _$Field extends Field { final FieldModifier modifier; factory _$Field([void Function(FieldBuilder)? updates]) => - (new FieldBuilder()..update(updates)).build() as _$Field; + (FieldBuilder()..update(updates)).build() as _$Field; _$Field._( {required this.annotations, @@ -39,22 +39,13 @@ class _$Field extends Field { required this.name, this.type, required this.modifier}) - : super._() { - BuiltValueNullFieldError.checkNotNull(annotations, r'Field', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'Field', 'docs'); - BuiltValueNullFieldError.checkNotNull(static, r'Field', 'static'); - BuiltValueNullFieldError.checkNotNull(late, r'Field', 'late'); - BuiltValueNullFieldError.checkNotNull(external, r'Field', 'external'); - BuiltValueNullFieldError.checkNotNull(name, r'Field', 'name'); - BuiltValueNullFieldError.checkNotNull(modifier, r'Field', 'modifier'); - } - + : super._(); @override Field rebuild(void Function(FieldBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$FieldBuilder toBuilder() => new _$FieldBuilder()..replace(this); + _$FieldBuilder toBuilder() => _$FieldBuilder()..replace(this); @override bool operator ==(Object other) { @@ -235,7 +226,6 @@ class _$FieldBuilder extends FieldBuilder { @override void replace(Field other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Field; } @@ -251,21 +241,20 @@ class _$FieldBuilder extends FieldBuilder { _$Field _$result; try { _$result = _$v ?? - new _$Field._( - annotations: annotations.build(), - docs: docs.build(), - assignment: assignment, - static: BuiltValueNullFieldError.checkNotNull( - static, r'Field', 'static'), - late: - BuiltValueNullFieldError.checkNotNull(late, r'Field', 'late'), - external: BuiltValueNullFieldError.checkNotNull( - external, r'Field', 'external'), - name: - BuiltValueNullFieldError.checkNotNull(name, r'Field', 'name'), - type: type, - modifier: BuiltValueNullFieldError.checkNotNull( - modifier, r'Field', 'modifier')); + _$Field._( + annotations: annotations.build(), + docs: docs.build(), + assignment: assignment, + static: BuiltValueNullFieldError.checkNotNull( + static, r'Field', 'static'), + late: BuiltValueNullFieldError.checkNotNull(late, r'Field', 'late'), + external: BuiltValueNullFieldError.checkNotNull( + external, r'Field', 'external'), + name: BuiltValueNullFieldError.checkNotNull(name, r'Field', 'name'), + type: type, + modifier: BuiltValueNullFieldError.checkNotNull( + modifier, r'Field', 'modifier'), + ); } catch (_) { late String _$failedField; try { @@ -274,8 +263,7 @@ class _$FieldBuilder extends FieldBuilder { _$failedField = 'docs'; docs.build(); } catch (e) { - throw new BuiltValueNestedFieldError( - r'Field', _$failedField, e.toString()); + throw BuiltValueNestedFieldError(r'Field', _$failedField, e.toString()); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/library.g.dart b/pkgs/code_builder/lib/src/specs/library.g.dart index 63cfc200bf..f96557ad72 100644 --- a/pkgs/code_builder/lib/src/specs/library.g.dart +++ b/pkgs/code_builder/lib/src/specs/library.g.dart @@ -25,7 +25,7 @@ class _$Library extends Library { final String? name; factory _$Library([void Function(LibraryBuilder)? updates]) => - (new LibraryBuilder()..update(updates)).build() as _$Library; + (LibraryBuilder()..update(updates)).build() as _$Library; _$Library._( {required this.annotations, @@ -36,23 +36,13 @@ class _$Library extends Library { this.generatedByComment, required this.ignoreForFile, this.name}) - : super._() { - BuiltValueNullFieldError.checkNotNull( - annotations, r'Library', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'Library', 'docs'); - BuiltValueNullFieldError.checkNotNull(directives, r'Library', 'directives'); - BuiltValueNullFieldError.checkNotNull(body, r'Library', 'body'); - BuiltValueNullFieldError.checkNotNull(comments, r'Library', 'comments'); - BuiltValueNullFieldError.checkNotNull( - ignoreForFile, r'Library', 'ignoreForFile'); - } - + : super._(); @override Library rebuild(void Function(LibraryBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$LibraryBuilder toBuilder() => new _$LibraryBuilder()..replace(this); + _$LibraryBuilder toBuilder() => _$LibraryBuilder()..replace(this); @override bool operator ==(Object other) { @@ -217,7 +207,6 @@ class _$LibraryBuilder extends LibraryBuilder { @override void replace(Library other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Library; } @@ -233,15 +222,16 @@ class _$LibraryBuilder extends LibraryBuilder { _$Library _$result; try { _$result = _$v ?? - new _$Library._( - annotations: annotations.build(), - docs: docs.build(), - directives: directives.build(), - body: body.build(), - comments: comments.build(), - generatedByComment: generatedByComment, - ignoreForFile: ignoreForFile.build(), - name: name); + _$Library._( + annotations: annotations.build(), + docs: docs.build(), + directives: directives.build(), + body: body.build(), + comments: comments.build(), + generatedByComment: generatedByComment, + ignoreForFile: ignoreForFile.build(), + name: name, + ); } catch (_) { late String _$failedField; try { @@ -259,7 +249,7 @@ class _$LibraryBuilder extends LibraryBuilder { _$failedField = 'ignoreForFile'; ignoreForFile.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'Library', _$failedField, e.toString()); } rethrow; diff --git a/pkgs/code_builder/lib/src/specs/method.g.dart b/pkgs/code_builder/lib/src/specs/method.g.dart index 214f6b214f..19799206ec 100644 --- a/pkgs/code_builder/lib/src/specs/method.g.dart +++ b/pkgs/code_builder/lib/src/specs/method.g.dart @@ -35,7 +35,7 @@ class _$Method extends Method { final Reference? returns; factory _$Method([void Function(MethodBuilder)? updates]) => - (new MethodBuilder()..update(updates)).build() as _$Method; + (MethodBuilder()..update(updates)).build() as _$Method; _$Method._( {required this.annotations, @@ -51,25 +51,13 @@ class _$Method extends Method { this.type, this.modifier, this.returns}) - : super._() { - BuiltValueNullFieldError.checkNotNull( - annotations, r'Method', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'Method', 'docs'); - BuiltValueNullFieldError.checkNotNull(types, r'Method', 'types'); - BuiltValueNullFieldError.checkNotNull( - optionalParameters, r'Method', 'optionalParameters'); - BuiltValueNullFieldError.checkNotNull( - requiredParameters, r'Method', 'requiredParameters'); - BuiltValueNullFieldError.checkNotNull(external, r'Method', 'external'); - BuiltValueNullFieldError.checkNotNull(static, r'Method', 'static'); - } - + : super._(); @override Method rebuild(void Function(MethodBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$MethodBuilder toBuilder() => new _$MethodBuilder()..replace(this); + _$MethodBuilder toBuilder() => _$MethodBuilder()..replace(this); @override bool operator ==(Object other) { @@ -314,7 +302,6 @@ class _$MethodBuilder extends MethodBuilder { @override void replace(Method other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Method; } @@ -330,22 +317,23 @@ class _$MethodBuilder extends MethodBuilder { _$Method _$result; try { _$result = _$v ?? - new _$Method._( - annotations: annotations.build(), - docs: docs.build(), - types: types.build(), - optionalParameters: optionalParameters.build(), - requiredParameters: requiredParameters.build(), - body: body, - external: BuiltValueNullFieldError.checkNotNull( - external, r'Method', 'external'), - lambda: lambda, - static: BuiltValueNullFieldError.checkNotNull( - static, r'Method', 'static'), - name: name, - type: type, - modifier: modifier, - returns: returns); + _$Method._( + annotations: annotations.build(), + docs: docs.build(), + types: types.build(), + optionalParameters: optionalParameters.build(), + requiredParameters: requiredParameters.build(), + body: body, + external: BuiltValueNullFieldError.checkNotNull( + external, r'Method', 'external'), + lambda: lambda, + static: BuiltValueNullFieldError.checkNotNull( + static, r'Method', 'static'), + name: name, + type: type, + modifier: modifier, + returns: returns, + ); } catch (_) { late String _$failedField; try { @@ -360,7 +348,7 @@ class _$MethodBuilder extends MethodBuilder { _$failedField = 'requiredParameters'; requiredParameters.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'Method', _$failedField, e.toString()); } rethrow; @@ -395,7 +383,7 @@ class _$Parameter extends Parameter { final bool covariant; factory _$Parameter([void Function(ParameterBuilder)? updates]) => - (new ParameterBuilder()..update(updates)).build() as _$Parameter; + (ParameterBuilder()..update(updates)).build() as _$Parameter; _$Parameter._( {this.defaultTo, @@ -409,25 +397,13 @@ class _$Parameter extends Parameter { this.type, required this.required, required this.covariant}) - : super._() { - BuiltValueNullFieldError.checkNotNull(name, r'Parameter', 'name'); - BuiltValueNullFieldError.checkNotNull(named, r'Parameter', 'named'); - BuiltValueNullFieldError.checkNotNull(toThis, r'Parameter', 'toThis'); - BuiltValueNullFieldError.checkNotNull(toSuper, r'Parameter', 'toSuper'); - BuiltValueNullFieldError.checkNotNull( - annotations, r'Parameter', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'Parameter', 'docs'); - BuiltValueNullFieldError.checkNotNull(types, r'Parameter', 'types'); - BuiltValueNullFieldError.checkNotNull(required, r'Parameter', 'required'); - BuiltValueNullFieldError.checkNotNull(covariant, r'Parameter', 'covariant'); - } - + : super._(); @override Parameter rebuild(void Function(ParameterBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$ParameterBuilder toBuilder() => new _$ParameterBuilder()..replace(this); + _$ParameterBuilder toBuilder() => _$ParameterBuilder()..replace(this); @override bool operator ==(Object other) { @@ -640,7 +616,6 @@ class _$ParameterBuilder extends ParameterBuilder { @override void replace(Parameter other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Parameter; } @@ -656,24 +631,25 @@ class _$ParameterBuilder extends ParameterBuilder { _$Parameter _$result; try { _$result = _$v ?? - new _$Parameter._( - defaultTo: defaultTo, - name: BuiltValueNullFieldError.checkNotNull( - name, r'Parameter', 'name'), - named: BuiltValueNullFieldError.checkNotNull( - named, r'Parameter', 'named'), - toThis: BuiltValueNullFieldError.checkNotNull( - toThis, r'Parameter', 'toThis'), - toSuper: BuiltValueNullFieldError.checkNotNull( - toSuper, r'Parameter', 'toSuper'), - annotations: annotations.build(), - docs: docs.build(), - types: types.build(), - type: type, - required: BuiltValueNullFieldError.checkNotNull( - required, r'Parameter', 'required'), - covariant: BuiltValueNullFieldError.checkNotNull( - covariant, r'Parameter', 'covariant')); + _$Parameter._( + defaultTo: defaultTo, + name: BuiltValueNullFieldError.checkNotNull( + name, r'Parameter', 'name'), + named: BuiltValueNullFieldError.checkNotNull( + named, r'Parameter', 'named'), + toThis: BuiltValueNullFieldError.checkNotNull( + toThis, r'Parameter', 'toThis'), + toSuper: BuiltValueNullFieldError.checkNotNull( + toSuper, r'Parameter', 'toSuper'), + annotations: annotations.build(), + docs: docs.build(), + types: types.build(), + type: type, + required: BuiltValueNullFieldError.checkNotNull( + required, r'Parameter', 'required'), + covariant: BuiltValueNullFieldError.checkNotNull( + covariant, r'Parameter', 'covariant'), + ); } catch (_) { late String _$failedField; try { @@ -684,7 +660,7 @@ class _$ParameterBuilder extends ParameterBuilder { _$failedField = 'types'; types.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'Parameter', _$failedField, e.toString()); } rethrow; diff --git a/pkgs/code_builder/lib/src/specs/mixin.g.dart b/pkgs/code_builder/lib/src/specs/mixin.g.dart index 28c7356c4e..f0beabef7f 100644 --- a/pkgs/code_builder/lib/src/specs/mixin.g.dart +++ b/pkgs/code_builder/lib/src/specs/mixin.g.dart @@ -27,7 +27,7 @@ class _$Mixin extends Mixin { final String name; factory _$Mixin([void Function(MixinBuilder)? updates]) => - (new MixinBuilder()..update(updates)).build() as _$Mixin; + (MixinBuilder()..update(updates)).build() as _$Mixin; _$Mixin._( {required this.base, @@ -39,23 +39,13 @@ class _$Mixin extends Mixin { required this.methods, required this.fields, required this.name}) - : super._() { - BuiltValueNullFieldError.checkNotNull(base, r'Mixin', 'base'); - BuiltValueNullFieldError.checkNotNull(annotations, r'Mixin', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'Mixin', 'docs'); - BuiltValueNullFieldError.checkNotNull(implements, r'Mixin', 'implements'); - BuiltValueNullFieldError.checkNotNull(types, r'Mixin', 'types'); - BuiltValueNullFieldError.checkNotNull(methods, r'Mixin', 'methods'); - BuiltValueNullFieldError.checkNotNull(fields, r'Mixin', 'fields'); - BuiltValueNullFieldError.checkNotNull(name, r'Mixin', 'name'); - } - + : super._(); @override Mixin rebuild(void Function(MixinBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$MixinBuilder toBuilder() => new _$MixinBuilder()..replace(this); + _$MixinBuilder toBuilder() => _$MixinBuilder()..replace(this); @override bool operator ==(Object other) { @@ -236,7 +226,6 @@ class _$MixinBuilder extends MixinBuilder { @override void replace(Mixin other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Mixin; } @@ -252,18 +241,17 @@ class _$MixinBuilder extends MixinBuilder { _$Mixin _$result; try { _$result = _$v ?? - new _$Mixin._( - base: - BuiltValueNullFieldError.checkNotNull(base, r'Mixin', 'base'), - annotations: annotations.build(), - docs: docs.build(), - on: on, - implements: implements.build(), - types: types.build(), - methods: methods.build(), - fields: fields.build(), - name: BuiltValueNullFieldError.checkNotNull( - name, r'Mixin', 'name')); + _$Mixin._( + base: BuiltValueNullFieldError.checkNotNull(base, r'Mixin', 'base'), + annotations: annotations.build(), + docs: docs.build(), + on: on, + implements: implements.build(), + types: types.build(), + methods: methods.build(), + fields: fields.build(), + name: BuiltValueNullFieldError.checkNotNull(name, r'Mixin', 'name'), + ); } catch (_) { late String _$failedField; try { @@ -281,8 +269,7 @@ class _$MixinBuilder extends MixinBuilder { _$failedField = 'fields'; fields.build(); } catch (e) { - throw new BuiltValueNestedFieldError( - r'Mixin', _$failedField, e.toString()); + throw BuiltValueNestedFieldError(r'Mixin', _$failedField, e.toString()); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/type_function.g.dart b/pkgs/code_builder/lib/src/specs/type_function.g.dart index d09f59b03c..0125b806c2 100644 --- a/pkgs/code_builder/lib/src/specs/type_function.g.dart +++ b/pkgs/code_builder/lib/src/specs/type_function.g.dart @@ -23,7 +23,7 @@ class _$FunctionType extends FunctionType { final bool? isNullable; factory _$FunctionType([void Function(FunctionTypeBuilder)? updates]) => - (new FunctionTypeBuilder()..update(updates)).build() as _$FunctionType; + (FunctionTypeBuilder()..update(updates)).build() as _$FunctionType; _$FunctionType._( {this.returnType, @@ -33,25 +33,13 @@ class _$FunctionType extends FunctionType { required this.namedParameters, required this.namedRequiredParameters, this.isNullable}) - : super._() { - BuiltValueNullFieldError.checkNotNull(types, r'FunctionType', 'types'); - BuiltValueNullFieldError.checkNotNull( - requiredParameters, r'FunctionType', 'requiredParameters'); - BuiltValueNullFieldError.checkNotNull( - optionalParameters, r'FunctionType', 'optionalParameters'); - BuiltValueNullFieldError.checkNotNull( - namedParameters, r'FunctionType', 'namedParameters'); - BuiltValueNullFieldError.checkNotNull( - namedRequiredParameters, r'FunctionType', 'namedRequiredParameters'); - } - + : super._(); @override FunctionType rebuild(void Function(FunctionTypeBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$FunctionTypeBuilder toBuilder() => - new _$FunctionTypeBuilder()..replace(this); + _$FunctionTypeBuilder toBuilder() => _$FunctionTypeBuilder()..replace(this); @override bool operator ==(Object other) { @@ -201,7 +189,6 @@ class _$FunctionTypeBuilder extends FunctionTypeBuilder { @override void replace(FunctionType other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$FunctionType; } @@ -217,14 +204,15 @@ class _$FunctionTypeBuilder extends FunctionTypeBuilder { _$FunctionType _$result; try { _$result = _$v ?? - new _$FunctionType._( - returnType: returnType, - types: types.build(), - requiredParameters: requiredParameters.build(), - optionalParameters: optionalParameters.build(), - namedParameters: namedParameters.build(), - namedRequiredParameters: namedRequiredParameters.build(), - isNullable: isNullable); + _$FunctionType._( + returnType: returnType, + types: types.build(), + requiredParameters: requiredParameters.build(), + optionalParameters: optionalParameters.build(), + namedParameters: namedParameters.build(), + namedRequiredParameters: namedRequiredParameters.build(), + isNullable: isNullable, + ); } catch (_) { late String _$failedField; try { @@ -239,7 +227,7 @@ class _$FunctionTypeBuilder extends FunctionTypeBuilder { _$failedField = 'namedRequiredParameters'; namedRequiredParameters.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'FunctionType', _$failedField, e.toString()); } rethrow; diff --git a/pkgs/code_builder/lib/src/specs/type_record.g.dart b/pkgs/code_builder/lib/src/specs/type_record.g.dart index b1d47dfbdb..253ddfce69 100644 --- a/pkgs/code_builder/lib/src/specs/type_record.g.dart +++ b/pkgs/code_builder/lib/src/specs/type_record.g.dart @@ -15,25 +15,19 @@ class _$RecordType extends RecordType { final bool? isNullable; factory _$RecordType([void Function(RecordTypeBuilder)? updates]) => - (new RecordTypeBuilder()..update(updates)).build() as _$RecordType; + (RecordTypeBuilder()..update(updates)).build() as _$RecordType; _$RecordType._( {required this.positionalFieldTypes, required this.namedFieldTypes, this.isNullable}) - : super._() { - BuiltValueNullFieldError.checkNotNull( - positionalFieldTypes, r'RecordType', 'positionalFieldTypes'); - BuiltValueNullFieldError.checkNotNull( - namedFieldTypes, r'RecordType', 'namedFieldTypes'); - } - + : super._(); @override RecordType rebuild(void Function(RecordTypeBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$RecordTypeBuilder toBuilder() => new _$RecordTypeBuilder()..replace(this); + _$RecordTypeBuilder toBuilder() => _$RecordTypeBuilder()..replace(this); @override bool operator ==(Object other) { @@ -118,7 +112,6 @@ class _$RecordTypeBuilder extends RecordTypeBuilder { @override void replace(RecordType other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$RecordType; } @@ -134,10 +127,11 @@ class _$RecordTypeBuilder extends RecordTypeBuilder { _$RecordType _$result; try { _$result = _$v ?? - new _$RecordType._( - positionalFieldTypes: positionalFieldTypes.build(), - namedFieldTypes: namedFieldTypes.build(), - isNullable: isNullable); + _$RecordType._( + positionalFieldTypes: positionalFieldTypes.build(), + namedFieldTypes: namedFieldTypes.build(), + isNullable: isNullable, + ); } catch (_) { late String _$failedField; try { @@ -146,7 +140,7 @@ class _$RecordTypeBuilder extends RecordTypeBuilder { _$failedField = 'namedFieldTypes'; namedFieldTypes.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'RecordType', _$failedField, e.toString()); } rethrow; diff --git a/pkgs/code_builder/lib/src/specs/type_reference.g.dart b/pkgs/code_builder/lib/src/specs/type_reference.g.dart index 124e8b4f04..4e45c24859 100644 --- a/pkgs/code_builder/lib/src/specs/type_reference.g.dart +++ b/pkgs/code_builder/lib/src/specs/type_reference.g.dart @@ -19,7 +19,7 @@ class _$TypeReference extends TypeReference { final bool? isNullable; factory _$TypeReference([void Function(TypeReferenceBuilder)? updates]) => - (new TypeReferenceBuilder()..update(updates)).build() as _$TypeReference; + (TypeReferenceBuilder()..update(updates)).build() as _$TypeReference; _$TypeReference._( {required this.symbol, @@ -27,18 +27,13 @@ class _$TypeReference extends TypeReference { this.bound, required this.types, this.isNullable}) - : super._() { - BuiltValueNullFieldError.checkNotNull(symbol, r'TypeReference', 'symbol'); - BuiltValueNullFieldError.checkNotNull(types, r'TypeReference', 'types'); - } - + : super._(); @override TypeReference rebuild(void Function(TypeReferenceBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$TypeReferenceBuilder toBuilder() => - new _$TypeReferenceBuilder()..replace(this); + _$TypeReferenceBuilder toBuilder() => _$TypeReferenceBuilder()..replace(this); @override bool operator ==(Object other) { @@ -155,7 +150,6 @@ class _$TypeReferenceBuilder extends TypeReferenceBuilder { @override void replace(TypeReference other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$TypeReference; } @@ -171,20 +165,21 @@ class _$TypeReferenceBuilder extends TypeReferenceBuilder { _$TypeReference _$result; try { _$result = _$v ?? - new _$TypeReference._( - symbol: BuiltValueNullFieldError.checkNotNull( - symbol, r'TypeReference', 'symbol'), - url: url, - bound: bound, - types: types.build(), - isNullable: isNullable); + _$TypeReference._( + symbol: BuiltValueNullFieldError.checkNotNull( + symbol, r'TypeReference', 'symbol'), + url: url, + bound: bound, + types: types.build(), + isNullable: isNullable, + ); } catch (_) { late String _$failedField; try { _$failedField = 'types'; types.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'TypeReference', _$failedField, e.toString()); } rethrow; diff --git a/pkgs/code_builder/lib/src/specs/typedef.g.dart b/pkgs/code_builder/lib/src/specs/typedef.g.dart index 8c2a16c2d7..61e8e63ae5 100644 --- a/pkgs/code_builder/lib/src/specs/typedef.g.dart +++ b/pkgs/code_builder/lib/src/specs/typedef.g.dart @@ -19,7 +19,7 @@ class _$TypeDef extends TypeDef { final BuiltList types; factory _$TypeDef([void Function(TypeDefBuilder)? updates]) => - (new TypeDefBuilder()..update(updates)).build() as _$TypeDef; + (TypeDefBuilder()..update(updates)).build() as _$TypeDef; _$TypeDef._( {required this.name, @@ -27,21 +27,13 @@ class _$TypeDef extends TypeDef { required this.annotations, required this.docs, required this.types}) - : super._() { - BuiltValueNullFieldError.checkNotNull(name, r'TypeDef', 'name'); - BuiltValueNullFieldError.checkNotNull(definition, r'TypeDef', 'definition'); - BuiltValueNullFieldError.checkNotNull( - annotations, r'TypeDef', 'annotations'); - BuiltValueNullFieldError.checkNotNull(docs, r'TypeDef', 'docs'); - BuiltValueNullFieldError.checkNotNull(types, r'TypeDef', 'types'); - } - + : super._(); @override TypeDef rebuild(void Function(TypeDefBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$TypeDefBuilder toBuilder() => new _$TypeDefBuilder()..replace(this); + _$TypeDefBuilder toBuilder() => _$TypeDefBuilder()..replace(this); @override bool operator ==(Object other) { @@ -158,7 +150,6 @@ class _$TypeDefBuilder extends TypeDefBuilder { @override void replace(TypeDef other) { - ArgumentError.checkNotNull(other, 'other'); _$v = other as _$TypeDef; } @@ -174,14 +165,15 @@ class _$TypeDefBuilder extends TypeDefBuilder { _$TypeDef _$result; try { _$result = _$v ?? - new _$TypeDef._( - name: BuiltValueNullFieldError.checkNotNull( - name, r'TypeDef', 'name'), - definition: BuiltValueNullFieldError.checkNotNull( - definition, r'TypeDef', 'definition'), - annotations: annotations.build(), - docs: docs.build(), - types: types.build()); + _$TypeDef._( + name: + BuiltValueNullFieldError.checkNotNull(name, r'TypeDef', 'name'), + definition: BuiltValueNullFieldError.checkNotNull( + definition, r'TypeDef', 'definition'), + annotations: annotations.build(), + docs: docs.build(), + types: types.build(), + ); } catch (_) { late String _$failedField; try { @@ -192,7 +184,7 @@ class _$TypeDefBuilder extends TypeDefBuilder { _$failedField = 'types'; types.build(); } catch (e) { - throw new BuiltValueNestedFieldError( + throw BuiltValueNestedFieldError( r'TypeDef', _$failedField, e.toString()); } rethrow; diff --git a/pkgs/code_builder/pubspec.yaml b/pkgs/code_builder/pubspec.yaml index 6bf096579f..302b03f419 100644 --- a/pkgs/code_builder/pubspec.yaml +++ b/pkgs/code_builder/pubspec.yaml @@ -1,5 +1,5 @@ name: code_builder -version: 4.10.2-wip +version: 4.11.0-wip description: A fluent, builder-based library for generating valid Dart code. repository: https://github.com/dart-lang/tools/tree/main/pkgs/code_builder issue_tracker: https://github.com/dart-lang/tools/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Acode_builder @@ -15,7 +15,7 @@ dependencies: meta: ^1.3.0 dev_dependencies: - build: ^2.0.0 + build: ^2.5.4 build_runner: ^2.0.3 built_value_generator: ^8.0.0 dart_flutter_team_lints: ^3.0.0 diff --git a/pkgs/code_builder/test/specs/code/control_test.dart b/pkgs/code_builder/test/specs/code/control_test.dart new file mode 100644 index 0000000000..f904f19c32 --- /dev/null +++ b/pkgs/code_builder/test/specs/code/control_test.dart @@ -0,0 +1,587 @@ +// 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?'); +}'''), + ); + }); + }); +} diff --git a/pkgs/code_builder/test/specs/code/expression_test.dart b/pkgs/code_builder/test/specs/code/expression_test.dart index 7a424fd8e6..a36e27a0ca 100644 --- a/pkgs/code_builder/test/specs/code/expression_test.dart +++ b/pkgs/code_builder/test/specs/code/expression_test.dart @@ -867,7 +867,7 @@ void main() { equalsDart('late String foo = bar')); }); - test('should emit a perenthesized epression', () { + test('should emit a parenthesized expression', () { expect( refer('foo').ifNullThen(refer('FormatException') .newInstance([literalString('missing foo')]) @@ -876,87 +876,102 @@ void main() { equalsDart('foo ?? (throw FormatException(\'missing foo\'))')); }); - test('should emit an addition assigment expression', () { + test('should emit an addition assignment expression', () { expect( refer('foo').addAssign(refer('bar')), equalsDart('foo += bar'), ); }); - test('should emit a subtraction assigment expression', () { + test('should emit a subtraction assignment expression', () { expect( refer('foo').subtractAssign(refer('bar')), equalsDart('foo -= bar'), ); }); - test('should emit a multiplication assigment expression', () { + test('should emit a multiplication assignment expression', () { expect( refer('foo').multiplyAssign(refer('bar')), equalsDart('foo *= bar'), ); }); - test('should emit a division assigment expression', () { + test('should emit a division assignment expression', () { expect( refer('foo').divideAssign(refer('bar')), equalsDart('foo /= bar'), ); }); - test('should emit an int division assigment expression', () { + test('should emit an int division assignment expression', () { expect( refer('foo').intDivideAssign(refer('bar')), equalsDart('foo ~/= bar'), ); }); - test('should emit a euclidean modulo assigment expression', () { + test('should emit a euclidean modulo assignment expression', () { expect( refer('foo').euclideanModuloAssign(refer('bar')), equalsDart('foo %= bar'), ); }); - test('should emit a shift left assigment expression', () { + test('should emit a shift left assignment expression', () { expect( refer('foo').shiftLeftAssign(refer('bar')), equalsDart('foo <<= bar'), ); }); - test('should emit a shift right assigment expression', () { + test('should emit a shift right assignment expression', () { expect( refer('foo').shiftRightAssign(refer('bar')), equalsDart('foo >>= bar'), ); }); - test('should emit a shift right unsigned assigment expression', () { + test('should emit a shift right unsigned assignment expression', () { expect( refer('foo').shiftRightUnsignedAssign(refer('bar')), equalsDart('foo >>>= bar'), ); }); - test('should emit a bitwise AND assigment expression', () { + test('should emit a bitwise AND assignment expression', () { expect( refer('foo').bitwiseAndAssign(refer('bar')), equalsDart('foo &= bar'), ); }); - test('should emit a bitwise XOR assigment expression', () { + test('should emit a bitwise XOR assignment expression', () { expect( refer('foo').bitwiseXorAssign(refer('bar')), equalsDart('foo ^= bar'), ); }); - test('should emit a bitwise OR assigment expression', () { + test('should emit a bitwise OR assignment expression', () { expect( refer('foo').bitwiseOrAssign(refer('bar')), equalsDart('foo |= bar'), ); }); + + test('should emit a yielded expression', () { + expect(refer('foo').yielded, equalsDart('yield foo')); + }); + + test('should emit a yield starred expression', () { + expect(refer('foo').yieldStarred, equalsDart('yield* foo')); + }); + + test( + 'should emit a wildcard expression', + () { + expect(Expression.wildcard, equalsDart('_')); + }, + ); } diff --git a/pkgs/code_builder/test/specs/control_test.dart b/pkgs/code_builder/test/specs/control_test.dart new file mode 100644 index 0000000000..94ff5c2b53 --- /dev/null +++ b/pkgs/code_builder/test/specs/control_test.dart @@ -0,0 +1,863 @@ +// 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; +''')); + }, + ); + }); +}