From 16e8e347cdab891643cc513f649e446c1e84f1ef Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Wed, 23 Jul 2025 19:08:33 -0500 Subject: [PATCH 01/17] [code_builder] add control-flow expressions via `ControlExpression` --- pkgs/code_builder/CHANGELOG.md | 138 +++++----- pkgs/code_builder/lib/code_builder.dart | 3 +- .../lib/src/specs/expression.dart | 80 +++++- .../lib/src/specs/expression/control.dart | 244 ++++++++++++++++++ .../test/specs/code/expression_test.dart | 224 +++++++++++++++- 5 files changed, 619 insertions(+), 70 deletions(-) create mode 100644 pkgs/code_builder/lib/src/specs/expression/control.dart diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index f8459a322..d6bc95605 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -4,6 +4,11 @@ * 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 +* Add more helpers on `Expression`: + * Add `Expression.matchCase` + * Add `Expression.yielded` + * Add `Expression.yieldStarred` +* Add support for control-flow expressions via `ControlExpression` ## 4.10.1 @@ -156,7 +161,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 +214,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: @@ -346,7 +350,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 +576,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 +610,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 +654,7 @@ expect( ); ``` -- Added a `castAs` method to `ExpressionBuilder`: +* Added a `castAs` method to `ExpressionBuilder`: ```dart expect( @@ -663,7 +667,7 @@ expect( ### BREAKING CHANGES -- Removed `namedNewInstance` and `namedConstInstance`, replaced with `constructor: `: +* Removed `namedNewInstance` and `namedConstInstance`, replaced with `constructor:`: ```dart expect( @@ -674,7 +678,7 @@ expect( ); ``` -- Renamed `named` parameter to `namedArguments`: +* Renamed `named` parameter to `namedArguments`: ```dart expect( @@ -696,25 +700,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 StatemnetBuilder.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 +730,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 +757,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 +767,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 +781,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 +799,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 +811,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 +835,7 @@ reference('foo').cascade((c) => [ ]); ``` -- Added support for accessing a property +* Added support for accessing a property ```dart // foo.bar @@ -840,20 +844,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 +865,4 @@ automatically added to a `LibraryBuilder` for you if you use ## 0.1.0 -- Initial version +* Initial version diff --git a/pkgs/code_builder/lib/code_builder.dart b/pkgs/code_builder/lib/code_builder.dart index 9cd15b958..1f680379f 100644 --- a/pkgs/code_builder/lib/code_builder.dart +++ b/pkgs/code_builder/lib/code_builder.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// 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. @@ -18,6 +18,7 @@ export 'src/specs/expression.dart' show BinaryExpression, CodeExpression, + ControlExpression, Expression, ExpressionEmitter, ExpressionVisitor, diff --git a/pkgs/code_builder/lib/src/specs/expression.dart b/pkgs/code_builder/lib/src/specs/expression.dart index aa06de283..4e806b58b 100644 --- a/pkgs/code_builder/lib/src/specs/expression.dart +++ b/pkgs/code_builder/lib/src/specs/expression.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// 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. @@ -18,6 +18,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]. @@ -195,6 +196,12 @@ abstract class Expression implements Spec { 'await', ); + /// Returns `yield {this}` + Expression get yielded => BinaryExpression._(_empty, this, 'yield'); + + /// Returns `yield* {this}` + Expression get yieldStarred => BinaryExpression._(_empty, this, 'yield*'); + /// Returns the result of `++this`. Expression operatorUnaryPrefixIncrement() => BinaryExpression._(_empty, expression, '++', addSpace: false); @@ -309,6 +316,15 @@ abstract class Expression implements Spec { '??=', ); + /// Returns `{this} case {pattern}`. + /// + /// For use in [if-case](https://dart.dev/language/branches#if-case) + /// statements. + /// + /// {@category controlFlow} + Expression matchCase(Expression pattern) => + BinaryExpression._(this, pattern, 'case'); + /// Return `var {name} = {this}`. @Deprecated('Use `declareVar(name).assign(expression)`') Expression assignVar(String name, [Reference? type]) => BinaryExpression._( @@ -521,6 +537,7 @@ abstract class ExpressionVisitor implements SpecVisitor { [T? context]); T visitParenthesizedExpression(ParenthesizedExpression expression, [T? context]); + T visitControlExpression(ControlExpression expression, [T? context]); } /// Knowledge of how to write valid Dart code from [ExpressionVisitor]. @@ -751,6 +768,67 @@ abstract mixin class ExpressionEmitter return output; } + @override + StringSink visitControlExpression(ControlExpression expression, + [StringSink? output]) { + output ??= StringBuffer(); + + if (expression.label case final String label) { + output.write('$label: '); + } + + 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; + } + /// Executes [visit] within a context which may alter the output if [isConst] /// is `true`. /// 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 000000000..af5937c5e --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/expression/control.dart @@ -0,0 +1,244 @@ +// Copyright (c) 20125, 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'; + +/// Represents a control expression, such as an `if` or `for` +/// statement. +/// +/// The expression consists of the control statement ([control]), +/// followed by the expression body (if provided), which consists of +/// all expressions in [body] joined by [separator], optionally +/// enclosed in parenthesis ([parenthesised]). +/// +/// {@category controlFlow} +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`) + /// + /// If [body] is `null` or empty, [parenthesised] will have no effect. + final bool parenthesised; + + /// This expression's label. + /// + /// https://dart.dev/language/loops#labels + final String? label; + + const ControlExpression._(this.control, + {this.body, this.separator, this.parenthesised = true, this.label}); + + @override + R accept(covariant ExpressionVisitor visitor, [R? context]) => + visitor.visitControlExpression(this, context); + + /// Returns an `if` statement. + /// + /// ```dart + /// if (condition) + /// ``` + /// + /// https://dart.dev/language/branches#if + /// + + factory ControlExpression.ifStatement(Expression condition) => + ControlExpression._('if', body: [condition]); + + /// An `else` statement + /// + /// ```dart + /// else + /// ``` + /// + /// https://dart.dev/language/branches#if + /// + + static const elseStatement = ControlExpression._('else'); + + /// Returns an `else if` statement + /// + /// ```dart + /// else if (condition) + /// ``` + /// + /// https://dart.dev/language/branches#if + /// + + factory ControlExpression.elseIfStatement(Expression condition) => + ControlExpression._('else if', body: [condition]); + + /// Returns a traditional `for` loop. + /// + /// ```dart + /// for (initialize; condition; advance) + /// ``` + /// + /// https://dart.dev/language/loops#for-loops + /// + + factory ControlExpression.forLoop( + Expression? initialize, Expression? condition, Expression? advance) => + ControlExpression._('for', + body: [initialize, condition, advance], separator: ';'); + + /// Returns a `for-in` loop. + /// + /// ```dart + /// for (identifier in expression) + /// ``` + /// + /// https://dart.dev/language/loops#for-loops + /// + + factory ControlExpression.forInLoop( + Expression identifier, Expression expression) => + ControlExpression._('for', + body: [identifier, expression], separator: ' in'); + + /// Returns an asynchronous `for` loop. + /// + /// ```dart + /// await for (identifier in expression) + /// ``` + /// + /// https://dart.dev/language/async#handling-streams + /// + + factory ControlExpression.awaitForLoop( + Expression identifier, Expression expression) => + ControlExpression._('await for', + body: [identifier, expression], separator: ' in'); + + /// Returns a `while` loop. + /// + /// ```dart + /// while (condition) + /// ``` + /// + /// https://dart.dev/language/loops#while-and-do-while + /// + + factory ControlExpression.whileLoop(Expression condition) => + ControlExpression._('while', body: [condition]); + + /// A `do` statement. + /// + /// ```dart + /// do + /// ``` + /// + /// https://dart.dev/language/loops#while-and-do-while + /// + + static const doStatement = ControlExpression._('do'); + + /// Returns a `break` statement. + /// + /// ```dart + /// break label + /// ``` + /// + /// https://dart.dev/language/loops#break-and-continue + /// + + factory ControlExpression.breakStatement([String? label]) => + ControlExpression._('break', + body: [if (label != null) refer(label)], parenthesised: false); + + /// Returns a `continue` statement. + /// + /// ```dart + /// continue label + /// ``` + /// + /// https://dart.dev/language/loops#break-and-continue + /// + + factory ControlExpression.continueStatement([String? label]) => + ControlExpression._('continue', + body: [if (label != null) refer(label)], parenthesised: false); + + /// A `try` statement. + /// + /// ```dart + /// try + /// ``` + /// + /// https://dart.dev/language/error-handling#catch + /// + + static const tryStatement = ControlExpression._('try'); + + /// Returns a `catch` statement. + /// + /// ```dart + /// catch (error) + /// catch (error, stacktrace) + /// ``` + /// + /// https://dart.dev/language/error-handling#catch + /// + + factory ControlExpression.catchStatement(Expression error, + [Expression? stacktrace]) => + ControlExpression._('catch', + body: [error, if (stacktrace != null) stacktrace], separator: ','); + + /// Returns an `on` statement. + /// + /// ```dart + /// on error + /// ``` + /// + /// https://dart.dev/language/error-handling#catch + /// + + factory ControlExpression.onStatement(Expression error) => + ControlExpression._('on', body: [error], parenthesised: false); + + /// A `finally` statement. + /// + /// ```dart + /// finally + /// ``` + /// + /// https://dart.dev/language/error-handling#finally + /// + + static const finallyStatement = ControlExpression._('finally'); + + /// Returns `label: {this}` + /// + /// https://dart.dev/language/loops#labels + ControlExpression labeled(String label) => ControlExpression._(control, + body: body, + label: label, + parenthesised: parenthesised, + separator: separator); +} diff --git a/pkgs/code_builder/test/specs/code/expression_test.dart b/pkgs/code_builder/test/specs/code/expression_test.dart index 7a424fd8e..dac2c068c 100644 --- a/pkgs/code_builder/test/specs/code/expression_test.dart +++ b/pkgs/code_builder/test/specs/code/expression_test.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// 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. @@ -959,4 +959,226 @@ void main() { equalsDart('foo |= bar'), ); }); + + test('should emit a case match expression', () { + expect(refer('foo').matchCase(refer('bar')), equalsDart('foo case 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')); + }); + + group( + 'ControlExpression', + () { + 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, equalsDart('else')); + }, + ); + + test( + 'should emit an else-if statement', + () { + expect(ControlExpression.elseIfStatement(literal(true)), + equalsDart('else if (true)')); + }, + ); + + 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 a labeled for-in loop', + () { + expect( + ControlExpression.forInLoop(refer('x'), refer('list')) + .labeled('foo'), + equalsDart('foo: 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 break statement without label', + () { + expect(ControlExpression.breakStatement(), equalsDart('break')); + }, + ); + + test( + 'should emit a break statement with label', + () { + expect(ControlExpression.breakStatement('loop1'), + equalsDart('break loop1')); + }, + ); + + test( + 'should emit a continue statement without label', + () { + expect(ControlExpression.continueStatement(), equalsDart('continue')); + }, + ); + + test( + 'should emit a continue statement with label', + () { + expect(ControlExpression.continueStatement('loop1'), + equalsDart('continue loop1')); + }, + ); + + test( + 'should emit a try statement', + () { + expect(ControlExpression.tryStatement, equalsDart('try')); + }, + ); + + test( + 'should emit a catch statement with only error', + () { + expect(ControlExpression.catchStatement(refer('e')), + equalsDart('catch (e)')); + }, + ); + + test( + 'should emit a catch statement with error and stacktrace', + () { + expect(ControlExpression.catchStatement(refer('e'), refer('s')), + equalsDart('catch (e, s)')); + }, + ); + + test( + 'should emit an on statement', + () { + expect(ControlExpression.onStatement(refer('FormatException')), + equalsDart('on FormatException')); + }, + ); + + test( + 'should emit a finally statement', + () { + expect(ControlExpression.finallyStatement, equalsDart('finally')); + }, + ); + }, + ); } From cbbb7eeb1ba69250582669913351fdccdc14fcd8 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Wed, 23 Jul 2025 19:28:17 -0500 Subject: [PATCH 02/17] [code_builder] fix typos in `expression_test` and changelog --- pkgs/code_builder/.vscode/settings.json | 9 +++++++ pkgs/code_builder/CHANGELOG.md | 4 +-- .../test/specs/code/expression_test.dart | 26 +++++++++---------- 3 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 pkgs/code_builder/.vscode/settings.json diff --git a/pkgs/code_builder/.vscode/settings.json b/pkgs/code_builder/.vscode/settings.json new file mode 100644 index 000000000..ba038420b --- /dev/null +++ b/pkgs/code_builder/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "cSpell.words": [ + "dartfmt", + "Gitter", + "Nikolas", + "Rimikis", + "Substract" + ] +} \ No newline at end of file diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index d6bc95605..92c293f8e 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -260,7 +260,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 @@ -709,7 +709,7 @@ Avoid creating symbols that can collide with the Dart language: * Add support for `switch` statements * Add support for a raw expression and statement * `new ExpressionBuilder.raw(...)` - * `new StatemnetBuilder.raw(...)` + * `new StatementBuilder.raw(...)` This should help cover any cases not covered with builders today. diff --git a/pkgs/code_builder/test/specs/code/expression_test.dart b/pkgs/code_builder/test/specs/code/expression_test.dart index dac2c068c..8ed8df3cb 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,84 +876,84 @@ 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'), From 17549649ddbbb1fa40ac1356fab6b2d24a49ef87 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:36:46 -0500 Subject: [PATCH 03/17] [code_builder] feat: add control-flow loops * add `ForLoop`, `ForInLoop`, `WhileLoop` classes and associated builders, as well as relevant tests. * add `ControlBlockVisitor` and `ControlBlockEmitter` classes for control block emitting knowledge. * mix `ControlBlockEmitter` into `DartEmitter` to add general control-block emitting capabilities. * mark `ControlExpression` as internal and stop exporting it. move `ControlExpression` tests to a dedicated file in `tests/specs/code`. *still todo*: support emitting if and try/catch blocks and trees --- pkgs/code_builder/CHANGELOG.md | 10 +- pkgs/code_builder/lib/code_builder.dart | 11 +- pkgs/code_builder/lib/src/emitter.dart | 5 +- pkgs/code_builder/lib/src/specs/control.dart | 213 +++++++++ .../code_builder/lib/src/specs/control.g.dart | 423 ++++++++++++++++++ .../lib/src/specs/expression.dart | 4 - .../lib/src/specs/expression/control.dart | 19 +- .../test/specs/code/control_test.dart | 209 +++++++++ .../test/specs/code/expression_test.dart | 210 --------- .../code_builder/test/specs/control_test.dart | 131 ++++++ 10 files changed, 1001 insertions(+), 234 deletions(-) create mode 100644 pkgs/code_builder/lib/src/specs/control.dart create mode 100644 pkgs/code_builder/lib/src/specs/control.g.dart create mode 100644 pkgs/code_builder/test/specs/code/control_test.dart create mode 100644 pkgs/code_builder/test/specs/control_test.dart diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index 92c293f8e..019b48128 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -1,14 +1,22 @@ ## 4.10.2-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 + * Add more helpers on `Expression`: * Add `Expression.matchCase` * Add `Expression.yielded` * Add `Expression.yieldStarred` -* Add support for control-flow expressions via `ControlExpression` + +* 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. ## 4.10.1 diff --git a/pkgs/code_builder/lib/code_builder.dart b/pkgs/code_builder/lib/code_builder.dart index 1f680379f..c0f3834be 100644 --- a/pkgs/code_builder/lib/code_builder.dart +++ b/pkgs/code_builder/lib/code_builder.dart @@ -10,6 +10,16 @@ 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 + ControlBlockEmitter, + ControlBlockVisitor, + ForInLoop, + ForInLoopBuilder, + ForLoop, + ForLoopBuilder, + WhileLoop, + WhileLoopBuilder; export 'src/specs/directive.dart' show Directive, DirectiveBuilder, DirectiveType; export 'src/specs/enum.dart' @@ -18,7 +28,6 @@ export 'src/specs/expression.dart' show BinaryExpression, CodeExpression, - ControlExpression, Expression, ExpressionEmitter, ExpressionVisitor, diff --git a/pkgs/code_builder/lib/src/emitter.dart b/pkgs/code_builder/lib/src/emitter.dart index 64e2aa46c..05c2683ea 100644 --- a/pkgs/code_builder/lib/src/emitter.dart +++ b/pkgs/code_builder/lib/src/emitter.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// 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. @@ -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'; @@ -51,7 +52,7 @@ StringSink visitAll( } 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/specs/control.dart b/pkgs/code_builder/lib/src/specs/control.dart new file mode 100644 index 000000000..22e5b40f6 --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/control.dart @@ -0,0 +1,213 @@ +// 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_value/built_value.dart'; +import 'package:meta/meta.dart'; + +import 'code.dart'; +import 'expression.dart'; + +part 'control.g.dart'; + +/// Root class for control-flow blocks. +/// +/// Most control-flow subclasses should mix in [ControlBlock] to avoid +/// duplication of boilerplate logic in [accept]. +/// +/// {@category controlFlow} +@internal +abstract mixin class ControlBlock implements Code { + /// The full control-flow expression that precedes this block. + @internal + 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); +} + +/// Control block that supports setting a label. +@internal +mixin Labeled 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); +} + +/// 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, Labeled + 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, Labeled + 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, Labeled + 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]. + @internal + 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); +} + +abstract class ControlBlockVisitor + implements ExpressionVisitor, CodeVisitor { + T visitControlBlock(ControlBlock block, [T? context]); + T visitLabeledBlock(Labeled block, [T? context]); + T visitWhileLoop(WhileLoop loop, [T? context]); +} + +abstract mixin class ControlBlockEmitter + implements ControlBlockVisitor { + @override + StringSink visitControlBlock(ControlBlock block, [StringSink? output]) { + output ??= StringBuffer(); + block.expression.accept(this, output); + output.write('{'); + block.body.accept(this, output); + output.write('}'); + return output; + } + + @override + StringSink visitLabeledBlock(Labeled block, [StringSink? output]) { + output ??= StringBuffer(); + if (block.label != null) { + output.write('${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); + 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 000000000..18665acdb --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/control.g.dart @@ -0,0 +1,423 @@ +// 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; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/pkgs/code_builder/lib/src/specs/expression.dart b/pkgs/code_builder/lib/src/specs/expression.dart index 4e806b58b..f9170904b 100644 --- a/pkgs/code_builder/lib/src/specs/expression.dart +++ b/pkgs/code_builder/lib/src/specs/expression.dart @@ -773,10 +773,6 @@ abstract mixin class ExpressionEmitter [StringSink? output]) { output ??= StringBuffer(); - if (expression.label case final String label) { - output.write('$label: '); - } - output.write(expression.control); if (expression.body == null || expression.body!.isEmpty) { diff --git a/pkgs/code_builder/lib/src/specs/expression/control.dart b/pkgs/code_builder/lib/src/specs/expression/control.dart index af5937c5e..41058fbe3 100644 --- a/pkgs/code_builder/lib/src/specs/expression/control.dart +++ b/pkgs/code_builder/lib/src/specs/expression/control.dart @@ -1,4 +1,4 @@ -// Copyright (c) 20125, the Dart project authors. Please see the AUTHORS file +// 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. @@ -13,6 +13,7 @@ part of '../expression.dart'; /// enclosed in parenthesis ([parenthesised]). /// /// {@category controlFlow} +@internal class ControlExpression extends Expression { /// The control statement (e.g. `if`, `for`). final String control; @@ -46,13 +47,8 @@ class ControlExpression extends Expression { /// If [body] is `null` or empty, [parenthesised] will have no effect. final bool parenthesised; - /// This expression's label. - /// - /// https://dart.dev/language/loops#labels - final String? label; - const ControlExpression._(this.control, - {this.body, this.separator, this.parenthesised = true, this.label}); + {this.body, this.separator, this.parenthesised = true}); @override R accept(covariant ExpressionVisitor visitor, [R? context]) => @@ -232,13 +228,4 @@ class ControlExpression extends Expression { /// static const finallyStatement = ControlExpression._('finally'); - - /// Returns `label: {this}` - /// - /// https://dart.dev/language/loops#labels - ControlExpression labeled(String label) => ControlExpression._(control, - body: body, - label: label, - parenthesised: parenthesised, - separator: separator); } 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 000000000..37df8b8b0 --- /dev/null +++ b/pkgs/code_builder/test/specs/code/control_test.dart @@ -0,0 +1,209 @@ +// 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. + +// Tests for ControlExpression + +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(); + + 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, equalsDart('else')); + }, + ); + + test( + 'should emit an else-if statement', + () { + expect(ControlExpression.elseIfStatement(literal(true)), + equalsDart('else if (true)')); + }, + ); + + 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 break statement without label', + () { + expect(ControlExpression.breakStatement(), equalsDart('break')); + }, + ); + + test( + 'should emit a break statement with label', + () { + expect( + ControlExpression.breakStatement('loop1'), equalsDart('break loop1')); + }, + ); + + test( + 'should emit a continue statement without label', + () { + expect(ControlExpression.continueStatement(), equalsDart('continue')); + }, + ); + + test( + 'should emit a continue statement with label', + () { + expect(ControlExpression.continueStatement('loop1'), + equalsDart('continue loop1')); + }, + ); + + test( + 'should emit a try statement', + () { + expect(ControlExpression.tryStatement, equalsDart('try')); + }, + ); + + test( + 'should emit a catch statement with only error', + () { + expect(ControlExpression.catchStatement(refer('e')), + equalsDart('catch (e)')); + }, + ); + + test( + 'should emit a catch statement with error and stacktrace', + () { + expect(ControlExpression.catchStatement(refer('e'), refer('s')), + equalsDart('catch (e, s)')); + }, + ); + + test( + 'should emit an on statement', + () { + expect(ControlExpression.onStatement(refer('FormatException')), + equalsDart('on FormatException')); + }, + ); + + test( + 'should emit a finally statement', + () { + expect(ControlExpression.finallyStatement, equalsDart('finally')); + }, + ); +} diff --git a/pkgs/code_builder/test/specs/code/expression_test.dart b/pkgs/code_builder/test/specs/code/expression_test.dart index 8ed8df3cb..911e3ee7b 100644 --- a/pkgs/code_builder/test/specs/code/expression_test.dart +++ b/pkgs/code_builder/test/specs/code/expression_test.dart @@ -971,214 +971,4 @@ void main() { test('should emit a yield starred expression', () { expect(refer('foo').yieldStarred, equalsDart('yield* foo')); }); - - group( - 'ControlExpression', - () { - 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, equalsDart('else')); - }, - ); - - test( - 'should emit an else-if statement', - () { - expect(ControlExpression.elseIfStatement(literal(true)), - equalsDart('else if (true)')); - }, - ); - - 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 a labeled for-in loop', - () { - expect( - ControlExpression.forInLoop(refer('x'), refer('list')) - .labeled('foo'), - equalsDart('foo: 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 break statement without label', - () { - expect(ControlExpression.breakStatement(), equalsDart('break')); - }, - ); - - test( - 'should emit a break statement with label', - () { - expect(ControlExpression.breakStatement('loop1'), - equalsDart('break loop1')); - }, - ); - - test( - 'should emit a continue statement without label', - () { - expect(ControlExpression.continueStatement(), equalsDart('continue')); - }, - ); - - test( - 'should emit a continue statement with label', - () { - expect(ControlExpression.continueStatement('loop1'), - equalsDart('continue loop1')); - }, - ); - - test( - 'should emit a try statement', - () { - expect(ControlExpression.tryStatement, equalsDart('try')); - }, - ); - - test( - 'should emit a catch statement with only error', - () { - expect(ControlExpression.catchStatement(refer('e')), - equalsDart('catch (e)')); - }, - ); - - test( - 'should emit a catch statement with error and stacktrace', - () { - expect(ControlExpression.catchStatement(refer('e'), refer('s')), - equalsDart('catch (e, s)')); - }, - ); - - test( - 'should emit an on statement', - () { - expect(ControlExpression.onStatement(refer('FormatException')), - equalsDart('on FormatException')); - }, - ); - - test( - 'should emit a finally statement', - () { - expect(ControlExpression.finallyStatement, equalsDart('finally')); - }, - ); - }, - ); } 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 000000000..8c54af728 --- /dev/null +++ b/pkgs/code_builder/test/specs/control_test.dart @@ -0,0 +1,131 @@ +// 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('emits 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('emits 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('emits a for loop with only condition', () { + final loop = ForLoop((b) { + b.condition = refer('keepGoing'); + }); + + expect(loop, equalsDart('for (; keepGoing;) {}')); + }); + + test('emits a for loop with only advance', () { + final loop = ForLoop((b) { + b.advance = refer('i').operatorUnaryPostfixIncrement(); + }); + + expect(loop, equalsDart('for (;; i++) {}')); + }); + + test('emits a for loop with label', () { + final loop = ForLoop((b) { + b.label = 'outer'; + }); + + expect(loop, equalsDart('outer: for (;;) {}')); + }); + }); + + group('for-in loop', () { + test('emits a basic for-in loop', () { + final loop = ForInLoop((b) { + b + ..variable = refer('item') + ..object = refer('items'); + }); + + expect(loop, equalsDart('for (item in items) {}')); + }); + + test('emits 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('emits 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('emits a basic while loop', () { + final loop = WhileLoop((b) { + b.condition = refer('running'); + }); + + expect(loop, equalsDart('while (running) {}')); + }); + + test('emits a labeled while loop', () { + final loop = WhileLoop((b) { + b + ..label = 'mainLoop' + ..condition = refer('true'); + }); + + expect(loop, equalsDart('mainLoop: while (true) {}')); + }); + + test('emits 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);'), + ); + }); + }); +} From ec35a892966c59c11ef31f8fd1c6212f68e4a743 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:57:47 -0500 Subject: [PATCH 04/17] [code_builder] chore: rebuild generated files with newer `built_value` version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rebuild `*.g.dart` files in `lib/src/specs` that were generated with an older version of `built_value` and follow older dart conventions. * bump package version to 4.11.0-wip to reflect changes * dev dependencies: require `source_gen: ^3.0.0` and `build: ^3.0.0` all tests still pass 👍 --- pkgs/code_builder/lib/src/specs/class.g.dart | 63 ++++------ pkgs/code_builder/lib/src/specs/code.g.dart | 18 ++- .../lib/src/specs/constructor.g.dart | 56 ++++----- .../lib/src/specs/directive.g.dart | 38 +++--- pkgs/code_builder/lib/src/specs/enum.g.dart | 85 +++++-------- .../lib/src/specs/extension.g.dart | 34 ++---- .../lib/src/specs/extension_type.g.dart | 108 ++++++----------- pkgs/code_builder/lib/src/specs/field.g.dart | 48 +++----- .../code_builder/lib/src/specs/library.g.dart | 38 +++--- pkgs/code_builder/lib/src/specs/method.g.dart | 112 +++++++----------- pkgs/code_builder/lib/src/specs/mixin.g.dart | 43 +++---- .../lib/src/specs/type_function.g.dart | 38 ++---- .../lib/src/specs/type_record.g.dart | 24 ++-- .../lib/src/specs/type_reference.g.dart | 29 ++--- .../code_builder/lib/src/specs/typedef.g.dart | 34 ++---- pkgs/code_builder/pubspec.yaml | 6 +- 16 files changed, 290 insertions(+), 484 deletions(-) diff --git a/pkgs/code_builder/lib/src/specs/class.g.dart b/pkgs/code_builder/lib/src/specs/class.g.dart index 423f576ce..11da402a1 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 7b5ba7825..47a301eb3 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 3f0693272..91951a936 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/directive.g.dart b/pkgs/code_builder/lib/src/specs/directive.g.dart index b28158e07..9f45b8544 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 651cc610a..eb7407222 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/extension.g.dart b/pkgs/code_builder/lib/src/specs/extension.g.dart index 83de17614..e66fb1b82 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 576984032..5c02e7ff5 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 d15f1c7dc..18e1cf85c 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 63cfc200b..f96557ad7 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 214f6b214..19799206e 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 28c7356c4..f0beabef7 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 d09f59b03..0125b806c 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 b1d47dfbd..253ddfce6 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 124e8b4f0..4e45c2485 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 8c2a16c2d..61e8e63ae 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 6bf096579..987438fe7 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,10 +15,10 @@ dependencies: meta: ^1.3.0 dev_dependencies: - build: ^2.0.0 + build: ^3.0.0 build_runner: ^2.0.3 built_value_generator: ^8.0.0 dart_flutter_team_lints: ^3.0.0 dart_style: ^3.0.1 - source_gen: ^2.0.0 + source_gen: ^3.0.0 test: ^1.16.0 From 6d152c43f6955e58c9ab9c516ba3469cf4c7e3c8 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Thu, 24 Jul 2025 17:02:27 -0500 Subject: [PATCH 05/17] [code_builder] style: follow commonmark * update markdown files to follow commonmark spec * bump version number in changelog * add `doc/api` (`dart doc` generated docs directory) to `.gitignore` * recommend dart, spell checker, and markdown linter extensions for vscode * configure workspace extension settings --- pkgs/code_builder/.gitignore | 1 + pkgs/code_builder/.vscode/extensions.json | 7 +++++++ pkgs/code_builder/.vscode/settings.json | 9 ++++++++- pkgs/code_builder/CHANGELOG.md | 4 +++- pkgs/code_builder/README.md | 5 ++++- 5 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 pkgs/code_builder/.vscode/extensions.json diff --git a/pkgs/code_builder/.gitignore b/pkgs/code_builder/.gitignore index feb089dd9..f26b5966a 100644 --- a/pkgs/code_builder/.gitignore +++ b/pkgs/code_builder/.gitignore @@ -3,3 +3,4 @@ .packages .pub pubspec.lock +doc/api \ No newline at end of file diff --git a/pkgs/code_builder/.vscode/extensions.json b/pkgs/code_builder/.vscode/extensions.json new file mode 100644 index 000000000..b2ed32402 --- /dev/null +++ b/pkgs/code_builder/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "streetsidesoftware.code-spell-checker", + "Dart-Code.dart-code", + "DavidAnson.vscode-markdownlint" + ] +} \ No newline at end of file diff --git a/pkgs/code_builder/.vscode/settings.json b/pkgs/code_builder/.vscode/settings.json index ba038420b..ac95c336e 100644 --- a/pkgs/code_builder/.vscode/settings.json +++ b/pkgs/code_builder/.vscode/settings.json @@ -4,6 +4,13 @@ "Gitter", "Nikolas", "Rimikis", + ], + "cSpell.ignoreWords": [ "Substract" - ] + ], + "markdownlint.config": { + "MD024": { + "siblings_only": true + } + } } \ No newline at end of file diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index 019b48128..70f894478 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -1,4 +1,6 @@ -## 4.10.2-wip +# Changelog + +## 4.11.0-wip * Upgrade `dart_style` and `source_gen` to remove `package:macros` dependency. diff --git a/pkgs/code_builder/README.md b/pkgs/code_builder/README.md index 86decb594..17dc0897e 100644 --- a/pkgs/code_builder/README.md +++ b/pkgs/code_builder/README.md @@ -1,3 +1,5 @@ +# code_builder + [![Build Status](https://github.com/dart-lang/tools/actions/workflows/code_builder.yaml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/code_builder.yaml) [![Pub package](https://img.shields.io/pub/v/code_builder.svg)](https://pub.dev/packages/code_builder) [![package publisher](https://img.shields.io/pub/publisher/code_builder.svg)](https://pub.dev/packages/code_builder/publisher) @@ -91,11 +93,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 From 993ca063c05fc0b3a31a4a6d3b48b3c8d1838fd7 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Fri, 25 Jul 2025 19:49:42 -0500 Subject: [PATCH 06/17] [code_builder] feat: support if/else trees * add `ControlTree` class for sharing visitor logic among different control-block tree types * add `Condition` (+ builder) for representing a single if/else condition * add `IfTree` (+ builder) for representing an if/else tree * add helper functions to `IfTree` for easier usage * move `ControlExpression` visitor/emitter logic to `ControlBlockVisitor` and `ControlBlockEmitter`; stop exporting those internal-only classes * add `ControlFlow` extension on `Expression`; move expression control-flow helpers to `ControlFlow` * add `Expression` helpers for creating loops and if trees to `ControlFlow`; support chaining to easily create if trees * add relevant tests --- pkgs/code_builder/CHANGELOG.md | 22 +- pkgs/code_builder/lib/code_builder.dart | 7 +- pkgs/code_builder/lib/src/specs/control.dart | 320 +++++++++++- .../code_builder/lib/src/specs/control.g.dart | 218 ++++++++ .../lib/src/specs/expression.dart | 74 +-- .../lib/src/specs/expression/control.dart | 234 ++++++--- .../test/specs/code/control_test.dart | 475 ++++++++++++------ .../test/specs/code/expression_test.dart | 4 - .../code_builder/test/specs/control_test.dart | 386 +++++++++++++- 9 files changed, 1391 insertions(+), 349 deletions(-) diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index 70f894478..98fc199ce 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -10,16 +10,30 @@ * Fixed bug: Fields declared with `static` and `external` now produce code with correct order -* Add more helpers on `Expression`: - * Add `Expression.matchCase` - * Add `Expression.yielded` - * Add `Expression.yieldStarred` +* 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.loopWhile` (via ext.) + * Add `Expression.loopDoWhile` (via ext.) + * Add `Expression.loopForIn` (via ext.) + * Add static helper functions to `ControlFlow`: + * Add `ControlFlow.breakVoid` + * Add `ControlFlow.breakLabel` + * Add `ControlFlow.continueVoid` + * Add `ControlFlow.continueLabel` + * Add `ControlFlow.returnVoid` + * Add `ControlFlow.ifCase` * 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 `Condition` and `ConditionBuilder` for single statements + * Add `IfTree` and `IfTreeBuilder` for conditional trees + ## 4.10.1 * Require Dart `^3.5.0` diff --git a/pkgs/code_builder/lib/code_builder.dart b/pkgs/code_builder/lib/code_builder.dart index c0f3834be..4de80c980 100644 --- a/pkgs/code_builder/lib/code_builder.dart +++ b/pkgs/code_builder/lib/code_builder.dart @@ -12,12 +12,14 @@ export 'src/specs/code.dart' export 'src/specs/constructor.dart' show Constructor, ConstructorBuilder; export 'src/specs/control.dart' show - ControlBlockEmitter, - ControlBlockVisitor, + Condition, + ConditionBuilder, ForInLoop, ForInLoopBuilder, ForLoop, ForLoopBuilder, + IfTree, + IfTreeBuilder, WhileLoop, WhileLoopBuilder; export 'src/specs/directive.dart' @@ -28,6 +30,7 @@ export 'src/specs/expression.dart' show BinaryExpression, CodeExpression, + ControlFlow, Expression, ExpressionEmitter, ExpressionVisitor, diff --git a/pkgs/code_builder/lib/src/specs/control.dart b/pkgs/code_builder/lib/src/specs/control.dart index 22e5b40f6..6b8268ffb 100644 --- a/pkgs/code_builder/lib/src/specs/control.dart +++ b/pkgs/code_builder/lib/src/specs/control.dart @@ -2,9 +2,11 @@ // 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:meta/meta.dart'; +import '../base.dart'; import 'code.dart'; import 'expression.dart'; @@ -12,15 +14,12 @@ part 'control.g.dart'; /// Root class for control-flow blocks. /// -/// Most control-flow subclasses should mix in [ControlBlock] to avoid -/// duplication of boilerplate logic in [accept]. -/// /// {@category controlFlow} @internal -abstract mixin class ControlBlock implements Code { +@immutable +abstract mixin class ControlBlock implements Code, Spec { /// The full control-flow expression that precedes this block. - @internal - ControlExpression get expression; + ControlExpression get _expression; /// The body of this block. /// @@ -32,9 +31,11 @@ abstract mixin class ControlBlock implements Code { visitor.visitControlBlock(this, context); } -/// Control block that supports setting a label. +/// Adds label support to a [ControlBlock]. +/// +/// {@category controlFlow} @internal -mixin Labeled on ControlBlock { +mixin LabeledControlBlock on ControlBlock { /// An (optional) label for this block. /// /// ```dart @@ -49,6 +50,8 @@ mixin Labeled on ControlBlock { visitor.visitLabeledBlock(this); } +// abstract mixin class ControlTree + /// Represents a traditional `for` loop. /// /// ```dart @@ -61,7 +64,7 @@ mixin Labeled on ControlBlock { /// /// {@category controlFlow} abstract class ForLoop - with ControlBlock, Labeled + with ControlBlock, LabeledControlBlock implements Built { ForLoop._(); @@ -81,7 +84,7 @@ abstract class ForLoop Expression? get advance; @override - ControlExpression get expression => + ControlExpression get _expression => ControlExpression.forLoop(initialize, condition, advance); factory ForLoop(void Function(ForLoopBuilder loop) builder) = _$ForLoop; @@ -106,7 +109,7 @@ abstract class ForLoop /// /// {@category controlFlow} abstract class ForInLoop - with ControlBlock, Labeled + with ControlBlock, LabeledControlBlock implements Built { ForInLoop._(); factory ForInLoop(void Function(ForInLoopBuilder loop) builder) = _$ForInLoop; @@ -121,7 +124,7 @@ abstract class ForInLoop Expression get object; @override - ControlExpression get expression => async == true + ControlExpression get _expression => async == true ? ControlExpression.awaitForLoop(variable, object) : ControlExpression.forInLoop(variable, object); } @@ -145,7 +148,7 @@ abstract class ForInLoop /// /// {@category controlFlow} abstract class WhileLoop - with ControlBlock, Labeled + with ControlBlock, LabeledControlBlock implements Built { WhileLoop._(); factory WhileLoop(void Function(WhileLoopBuilder loop) builder) = _$WhileLoop; @@ -158,39 +161,240 @@ abstract class WhileLoop /// Always returns the `while` statement, regardless /// of the value of [doWhile]. - @internal - ControlExpression get statement => ControlExpression.whileLoop(condition); + ControlExpression get _statement => ControlExpression.whileLoop(condition); @override - ControlExpression get expression => - doWhile == true ? ControlExpression.doStatement : statement; + ControlExpression get _expression => + doWhile == true ? ControlExpression.doStatement : _statement; @override R accept(covariant ControlBlockVisitor visitor, [R? context]) => visitor.visitWhileLoop(this, context); } +/// A tree of [ControlBlock]s +@internal +@immutable +abstract mixin class ControlTree implements Code, Spec { + /// The items in this tree. + BuiltList get blocks; + + @override + R accept(covariant ControlBlockVisitor visitor, [R? context]) => + visitor.visitControlTree(this, context); +} + +/// Represents a single `if` block. +/// +/// Use [IfTree] to create a tree of `if`, `else if`, +/// and `else` statements. +/// +/// {@category controlFlow} +abstract class Condition + with ControlBlock + implements Built { + Condition._(); + factory Condition(void Function(ConditionBuilder block) builder) = + _$Condition; + + /// The statement condition. + /// + /// Required if this is a standalone [Condition] or + /// the first in an [IfTree], otherwise optional. + Expression? get condition; + + ControlExpression? get _statement => + condition == null ? null : ControlExpression.ifStatement(condition!); + + @override + ControlExpression get _expression => + _statement ?? + (throw ArgumentError( + 'A condition must be provided with an `if` statement', 'condition')); + + /// This condition as an `else` block. + /// + /// Will be `else` if [condition] is `null`, + /// otherwise `else if`. + Condition get asElse => ElseCondition(this); + + /// Returns an [IfTree] with just this [Condition]. + IfTree get asTree => IfTree.of([this]); +} + +abstract class ConditionBuilder + implements Builder { + ConditionBuilder._(); + factory ConditionBuilder() = _$ConditionBuilder; + + BlockBuilder body = BlockBuilder(); + Expression? condition; + + /// Sets [condition] to an `if-case` expression. + /// + /// Uses [ControlFlow.ifCase] to create the expression. + /// + /// The expression will take the form: + /// ```dart + /// object case pattern + /// ``` + /// + /// Optionally set a guard (`when`) clause with [guard]: + /// ```dart + /// object case pattern when guard + /// ``` + /// + /// See https://dart.dev/language/branches#if-case + void ifCase({ + required Expression object, + required Expression pattern, + Expression? guard, + }) { + condition = + ControlFlow.ifCase(object: object, pattern: pattern, guard: guard); + } +} + +/// A [condition] preceded by `else` +@internal +@visibleForTesting +class ElseCondition extends _$Condition { + ElseCondition(Condition condition) + : super._(body: condition.body, condition: condition.condition); + + @override + ControlExpression get _expression => + ControlExpression.elseStatement(_statement); +} + +/// Represents an `if`/`else` tree. +/// +/// The first [Condition] in [blocks] will be treated as an `if` +/// block. All subsequent conditions will be treated as `else` blocks +/// using [Condition.asElse]. +/// +/// {@category controlFlow} +abstract class IfTree with ControlTree implements Built { + IfTree._(); + + /// Build an [IfTree] + factory IfTree(void Function(IfTreeBuilder tree) builder) = _$IfTree; + + /// Create an [IfTree] from a list of [conditions]. + factory IfTree.of(Iterable conditions) => IfTree( + (tree) { + tree.addAll(conditions); + }, + ); + + /// Called when an [IfTreeBuilder] is built. + /// + /// Replaces all but the first block with an [ElseCondition] + /// + @BuiltValueHook(finalizeBuilder: true) + static void _build(IfTreeBuilder builder) { + if (builder.blocks.isEmpty) return; + + final first = builder.blocks.first; + builder.blocks + ..skip(1) + ..map((b) => b.asElse) + ..insert(0, first); + } + + @override + BuiltList get blocks; + + /// Returns a new [IfTree] with [condition] added. + IfTree withCondition(Condition condition) => + (toBuilder()..add(condition)).build(); + + /// Builds a [Condition] with [builder] and returns + /// a new [IfTree] with it added. + IfTree elseIf(void Function(ConditionBuilder block) builder) => + withCondition((ConditionBuilder()..update(builder)).build()); + + /// Builds a block with [builder] and returns a new [IfTree] + /// with it added as an `else` [Condition]. + IfTree orElse(void Function(BlockBuilder body) builder) => elseIf( + (block) { + builder(block.body); + }, + ); +} + +/// Builds an [IfTree]. +abstract class IfTreeBuilder implements Builder { + IfTreeBuilder._(); + factory IfTreeBuilder() = _$IfTreeBuilder; + + /// The items in this tree. + ListBuilder blocks = ListBuilder(); + + /// Build a [Condition] with [builder] and add it to the tree. + /// + /// Shorthand for calling `add` and creating a condition + void ifThen(void Function(ConditionBuilder block) builder) => + add((ConditionBuilder()..update(builder)).build()); + + /// Add a [Condition] to the tree. + /// + /// Shorthand for `blocks.add` + void add(Condition condition) => blocks.add(condition); + + /// Add multiple [Condition]s to the tree. + /// + /// Shorthand for `blocks.addAll` + void addAll(Iterable conditions) => blocks.addAll(conditions); + + /// Builds a block using [builder] and adds it to the tree + /// as an `else` [Condition]. + /// + /// Shorthand for calling [add] and creating an `else` condition. + void orElse(void Function(BlockBuilder body) builder) => add(Condition( + (block) { + builder(block.body); + }, + )); + + /// Shorthand to add an `else` statement that throws [expression]. + void orElseThrow(Expression expression) => orElse( + (body) { + body.addExpression(expression.thrown); + }, + ); +} + +/// Knowledge of different types of control blocks. +/// +@internal abstract class ControlBlockVisitor implements ExpressionVisitor, CodeVisitor { T visitControlBlock(ControlBlock block, [T? context]); - T visitLabeledBlock(Labeled 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]); } +/// 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.write('{'); + block._expression.accept(this, output); + output.write(' { '); block.body.accept(this, output); - output.write('}'); + output.write(' }'); return output; } @override - StringSink visitLabeledBlock(Labeled block, [StringSink? output]) { + StringSink visitLabeledBlock(LabeledControlBlock block, + [StringSink? output]) { output ??= StringBuffer(); if (block.label != null) { output.write('${block.label!}: '); @@ -207,7 +411,77 @@ abstract mixin class ControlBlockEmitter if (loop.doWhile != true) return output; output.write(' '); - loop.statement.statement.accept(this, output); + loop._statement.statement.accept(this, output); + return output; + } + + @override + StringSink visitControlTree(ControlTree tree, [StringSink? output]) { + output ??= StringBuffer(); + + for (final item in tree.blocks) { + 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; } + } diff --git a/pkgs/code_builder/lib/src/specs/control.g.dart b/pkgs/code_builder/lib/src/specs/control.g.dart index 18665acdb..1fea71caf 100644 --- a/pkgs/code_builder/lib/src/specs/control.g.dart +++ b/pkgs/code_builder/lib/src/specs/control.g.dart @@ -420,4 +420,222 @@ class WhileLoopBuilder implements Builder { } } +class _$Condition extends Condition { + @override + final Expression? condition; + @override + final Block body; + + factory _$Condition([void Function(ConditionBuilder)? updates]) => + (ConditionBuilder()..update(updates)).build() as _$Condition; + + _$Condition._({this.condition, required this.body}) : super._(); + @override + Condition rebuild(void Function(ConditionBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + _$ConditionBuilder toBuilder() => _$ConditionBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Condition && + 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'Condition') + ..add('condition', condition) + ..add('body', body)) + .toString(); + } +} + +class _$ConditionBuilder extends ConditionBuilder { + _$Condition? _$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; + } + + _$ConditionBuilder() : super._(); + + ConditionBuilder get _$this { + final $v = _$v; + if ($v != null) { + super.condition = $v.condition; + super.body = $v.body.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(Condition other) { + _$v = other as _$Condition; + } + + @override + void update(void Function(ConditionBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Condition build() => _build(); + + _$Condition _build() { + _$Condition _$result; + try { + _$result = _$v ?? + _$Condition._( + condition: condition, + body: body.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'body'; + body.build(); + } catch (e) { + throw BuiltValueNestedFieldError( + r'Condition', _$failedField, e.toString()); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +class _$IfTree extends IfTree { + @override + final BuiltList blocks; + + factory _$IfTree([void Function(IfTreeBuilder)? updates]) => + (IfTreeBuilder()..update(updates)).build() as _$IfTree; + + _$IfTree._({required this.blocks}) : super._(); + @override + IfTree rebuild(void Function(IfTreeBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + _$IfTreeBuilder toBuilder() => _$IfTreeBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is IfTree && blocks == other.blocks; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, blocks.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'IfTree')..add('blocks', blocks)) + .toString(); + } +} + +class _$IfTreeBuilder extends IfTreeBuilder { + _$IfTree? _$v; + + @override + ListBuilder get blocks { + _$this; + return super.blocks; + } + + @override + set blocks(ListBuilder blocks) { + _$this; + super.blocks = blocks; + } + + _$IfTreeBuilder() : super._(); + + IfTreeBuilder get _$this { + final $v = _$v; + if ($v != null) { + super.blocks = $v.blocks.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(IfTree other) { + _$v = other as _$IfTree; + } + + @override + void update(void Function(IfTreeBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + IfTree build() => _build(); + + _$IfTree _build() { + IfTree._build(this); + _$IfTree _$result; + try { + _$result = _$v ?? + _$IfTree._( + blocks: blocks.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'blocks'; + blocks.build(); + } catch (e) { + throw BuiltValueNestedFieldError( + r'IfTree', _$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/expression.dart b/pkgs/code_builder/lib/src/specs/expression.dart index f9170904b..7428339d2 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'; @@ -196,12 +197,6 @@ abstract class Expression implements Spec { 'await', ); - /// Returns `yield {this}` - Expression get yielded => BinaryExpression._(_empty, this, 'yield'); - - /// Returns `yield* {this}` - Expression get yieldStarred => BinaryExpression._(_empty, this, 'yield*'); - /// Returns the result of `++this`. Expression operatorUnaryPrefixIncrement() => BinaryExpression._(_empty, expression, '++', addSpace: false); @@ -316,15 +311,6 @@ abstract class Expression implements Spec { '??=', ); - /// Returns `{this} case {pattern}`. - /// - /// For use in [if-case](https://dart.dev/language/branches#if-case) - /// statements. - /// - /// {@category controlFlow} - Expression matchCase(Expression pattern) => - BinaryExpression._(this, pattern, 'case'); - /// Return `var {name} = {this}`. @Deprecated('Use `declareVar(name).assign(expression)`') Expression assignVar(String name, [Reference? type]) => BinaryExpression._( @@ -537,7 +523,6 @@ abstract class ExpressionVisitor implements SpecVisitor { [T? context]); T visitParenthesizedExpression(ParenthesizedExpression expression, [T? context]); - T visitControlExpression(ControlExpression expression, [T? context]); } /// Knowledge of how to write valid Dart code from [ExpressionVisitor]. @@ -768,63 +753,6 @@ abstract mixin class ExpressionEmitter 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; - } - /// Executes [visit] within a context which may alter the output if [isConst] /// is `true`. /// diff --git a/pkgs/code_builder/lib/src/specs/expression/control.dart b/pkgs/code_builder/lib/src/specs/expression/control.dart index 41058fbe3..6111b370a 100644 --- a/pkgs/code_builder/lib/src/specs/expression/control.dart +++ b/pkgs/code_builder/lib/src/specs/expression/control.dart @@ -4,8 +4,7 @@ part of '../expression.dart'; -/// Represents a control expression, such as an `if` or `for` -/// statement. +/// Represents a control expression. /// /// The expression consists of the control statement ([control]), /// followed by the expression body (if provided), which consists of @@ -47,47 +46,20 @@ class ControlExpression extends Expression { /// If [body] is `null` or empty, [parenthesised] will have no effect. final bool parenthesised; - const ControlExpression._(this.control, + @visibleForTesting + const ControlExpression(this.control, {this.body, this.separator, this.parenthesised = true}); @override - R accept(covariant ExpressionVisitor visitor, [R? context]) => + R accept(covariant ControlBlockVisitor visitor, [R? context]) => visitor.visitControlExpression(this, context); - /// Returns an `if` statement. - /// - /// ```dart - /// if (condition) - /// ``` - /// - /// https://dart.dev/language/branches#if - /// - factory ControlExpression.ifStatement(Expression condition) => - ControlExpression._('if', body: [condition]); + ControlExpression('if', body: [condition]); - /// An `else` statement - /// - /// ```dart - /// else - /// ``` - /// - /// https://dart.dev/language/branches#if - /// - - static const elseStatement = ControlExpression._('else'); - - /// Returns an `else if` statement - /// - /// ```dart - /// else if (condition) - /// ``` - /// - /// https://dart.dev/language/branches#if - /// - - factory ControlExpression.elseIfStatement(Expression condition) => - ControlExpression._('else if', body: [condition]); + factory ControlExpression.elseStatement(Expression? condition) => + ControlExpression('else', + body: condition != null ? [condition] : null, parenthesised: false); /// Returns a traditional `for` loop. /// @@ -100,7 +72,7 @@ class ControlExpression extends Expression { factory ControlExpression.forLoop( Expression? initialize, Expression? condition, Expression? advance) => - ControlExpression._('for', + ControlExpression('for', body: [initialize, condition, advance], separator: ';'); /// Returns a `for-in` loop. @@ -114,7 +86,7 @@ class ControlExpression extends Expression { factory ControlExpression.forInLoop( Expression identifier, Expression expression) => - ControlExpression._('for', + ControlExpression('for', body: [identifier, expression], separator: ' in'); /// Returns an asynchronous `for` loop. @@ -128,7 +100,7 @@ class ControlExpression extends Expression { factory ControlExpression.awaitForLoop( Expression identifier, Expression expression) => - ControlExpression._('await for', + ControlExpression('await for', body: [identifier, expression], separator: ' in'); /// Returns a `while` loop. @@ -141,7 +113,7 @@ class ControlExpression extends Expression { /// factory ControlExpression.whileLoop(Expression condition) => - ControlExpression._('while', body: [condition]); + ControlExpression('while', body: [condition]); /// A `do` statement. /// @@ -152,33 +124,7 @@ class ControlExpression extends Expression { /// https://dart.dev/language/loops#while-and-do-while /// - static const doStatement = ControlExpression._('do'); - - /// Returns a `break` statement. - /// - /// ```dart - /// break label - /// ``` - /// - /// https://dart.dev/language/loops#break-and-continue - /// - - factory ControlExpression.breakStatement([String? label]) => - ControlExpression._('break', - body: [if (label != null) refer(label)], parenthesised: false); - - /// Returns a `continue` statement. - /// - /// ```dart - /// continue label - /// ``` - /// - /// https://dart.dev/language/loops#break-and-continue - /// - - factory ControlExpression.continueStatement([String? label]) => - ControlExpression._('continue', - body: [if (label != null) refer(label)], parenthesised: false); + static const doStatement = ControlExpression('do'); /// A `try` statement. /// @@ -189,7 +135,7 @@ class ControlExpression extends Expression { /// https://dart.dev/language/error-handling#catch /// - static const tryStatement = ControlExpression._('try'); + static const tryStatement = ControlExpression('try'); /// Returns a `catch` statement. /// @@ -203,7 +149,7 @@ class ControlExpression extends Expression { factory ControlExpression.catchStatement(Expression error, [Expression? stacktrace]) => - ControlExpression._('catch', + ControlExpression('catch', body: [error, if (stacktrace != null) stacktrace], separator: ','); /// Returns an `on` statement. @@ -216,7 +162,7 @@ class ControlExpression extends Expression { /// factory ControlExpression.onStatement(Expression error) => - ControlExpression._('on', body: [error], parenthesised: false); + ControlExpression('on', body: [error], parenthesised: false); /// A `finally` statement. /// @@ -227,5 +173,151 @@ class ControlExpression extends Expression { /// https://dart.dev/language/error-handling#finally /// - static const finallyStatement = ControlExpression._('finally'); + static const finallyStatement = ControlExpression('finally'); +} + +/// 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 an `if` [Condition] and add it + /// to a new [IfTree]. + /// + /// ```dart + /// if (this) { + /// body + /// } + /// ``` + /// + /// Chain [IfTree.elseIf] and [IfTree.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?'); + /// } + /// ``` + IfTree ifThen(void Function(BlockBuilder body) builder) => Condition( + (block) => block + ..condition = this + ..body.update(builder), + ).asTree; + + /// `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'); + + /// 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'); + } } diff --git a/pkgs/code_builder/test/specs/code/control_test.dart b/pkgs/code_builder/test/specs/code/control_test.dart index 37df8b8b0..af64eec20 100644 --- a/pkgs/code_builder/test/specs/code/control_test.dart +++ b/pkgs/code_builder/test/specs/code/control_test.dart @@ -2,8 +2,6 @@ // 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. -// Tests for ControlExpression - import 'package:code_builder/code_builder.dart'; import 'package:code_builder/src/specs/expression.dart'; import 'package:test/test.dart'; @@ -13,197 +11,352 @@ import '../../common.dart'; void main() { useDartfmt(); - test( - 'should emit an if statement', + group( + 'ControlExpression', () { - expect(ControlExpression.ifStatement(literal(1).equalTo(literal(2))), - equalsDart('if (1 == 2)')); - }, - ); + // general - test( - 'should emit an else statement', - () { - expect(ControlExpression.elseStatement, equalsDart('else')); - }, - ); + test('should insert a single body element', () { + final expr = ControlExpression('test', body: [literal(1)]); + expect(expr, equalsDart('test (1)')); + }); - test( - 'should emit an else-if statement', - () { - expect(ControlExpression.elseIfStatement(literal(true)), - equalsDart('else if (true)')); - }, - ); + 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 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 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 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 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 condition', - () { - expect( - ControlExpression.forLoop( - null, - refer('running'), - null, - ), - equalsDart('for (; running;)'), + 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 advance', - () { - expect( - ControlExpression.forLoop( - null, - null, - refer('i').operatorUnaryPostfixIncrement(), - ), - equalsDart('for (;; i++)'), + 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 all null body entries', - () { - expect( - ControlExpression.forLoop(null, null, null), - equalsDart('for (;;)'), + 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-in loop', - () { - expect( - ControlExpression.forInLoop(refer('x'), refer('list')), - equalsDart('for (x in list)'), + test( + 'should emit a for loop with all null body entries', + () { + expect( + ControlExpression.forLoop(null, null, null), + equalsDart('for (;;)'), + ); + }, ); - }, - ); - test( - 'should emit an await for loop', - () { - expect( - ControlExpression.awaitForLoop(refer('x'), refer('stream')), - equalsDart('await for (x in stream)'), + test( + 'should emit a for-in loop', + () { + expect( + ControlExpression.forInLoop(refer('x'), refer('list')), + equalsDart('for (x in list)'), + ); + }, ); - }, - ); - test( - 'should emit a while loop', - () { - expect( - ControlExpression.whileLoop(literal(true)), - equalsDart('while (true)'), + test( + 'should emit an await for loop', + () { + expect( + ControlExpression.awaitForLoop(refer('x'), refer('stream')), + equalsDart('await for (x in stream)'), + ); + }, ); - }, - ); - test( - 'should emit a do statement', - () { - expect(ControlExpression.doStatement, equalsDart('do')); - }, - ); + test( + 'should emit a while loop', + () { + expect( + ControlExpression.whileLoop(literal(true)), + equalsDart('while (true)'), + ); + }, + ); - test( - 'should emit a break statement without label', - () { - expect(ControlExpression.breakStatement(), equalsDart('break')); - }, - ); + test( + 'should emit a do statement', + () { + expect(ControlExpression.doStatement, equalsDart('do')); + }, + ); - test( - 'should emit a break statement with label', - () { - expect( - ControlExpression.breakStatement('loop1'), equalsDart('break loop1')); - }, - ); + test( + 'should emit a try statement', + () { + expect(ControlExpression.tryStatement, equalsDart('try')); + }, + ); - test( - 'should emit a continue statement without label', - () { - expect(ControlExpression.continueStatement(), equalsDart('continue')); - }, - ); + test( + 'should emit a catch statement with only error', + () { + expect(ControlExpression.catchStatement(refer('e')), + equalsDart('catch (e)')); + }, + ); - test( - 'should emit a continue statement with label', - () { - expect(ControlExpression.continueStatement('loop1'), - equalsDart('continue loop1')); - }, - ); + test( + 'should emit a catch statement with error and stacktrace', + () { + expect(ControlExpression.catchStatement(refer('e'), refer('s')), + equalsDart('catch (e, s)')); + }, + ); - test( - 'should emit a try statement', - () { - expect(ControlExpression.tryStatement, equalsDart('try')); - }, - ); + test( + 'should emit an on statement', + () { + expect(ControlExpression.onStatement(refer('FormatException')), + equalsDart('on FormatException')); + }, + ); - test( - 'should emit a catch statement with only error', - () { - expect(ControlExpression.catchStatement(refer('e')), - equalsDart('catch (e)')); + test( + 'should emit a finally statement', + () { + expect(ControlExpression.finallyStatement, equalsDart('finally')); + }, + ); }, ); - test( - 'should emit a catch statement with error and stacktrace', + group( + 'ControlFlow extension', () { - expect(ControlExpression.catchStatement(refer('e'), refer('s')), - equalsDart('catch (e, s)')); - }, - ); + test('should emit a yield expression', () { + final expr = refer('value').yielded; + expect(expr, equalsDart('yield value')); + }); - test( - 'should emit an on statement', - () { - expect(ControlExpression.onStatement(refer('FormatException')), - equalsDart('on FormatException')); - }, - ); + test('should emit a yield* expression', () { + final expr = refer('stream').yieldStarred; + expect(expr, equalsDart('yield* stream')); + }); - test( - 'should emit a finally statement', - () { - expect(ControlExpression.finallyStatement, equalsDart('finally')); + 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 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 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 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 911e3ee7b..1ea9a9e83 100644 --- a/pkgs/code_builder/test/specs/code/expression_test.dart +++ b/pkgs/code_builder/test/specs/code/expression_test.dart @@ -960,10 +960,6 @@ void main() { ); }); - test('should emit a case match expression', () { - expect(refer('foo').matchCase(refer('bar')), equalsDart('foo case bar')); - }); - test('should emit a yielded expression', () { expect(refer('foo').yielded, equalsDart('yield foo')); }); diff --git a/pkgs/code_builder/test/specs/control_test.dart b/pkgs/code_builder/test/specs/control_test.dart index 8c54af728..81f94e269 100644 --- a/pkgs/code_builder/test/specs/control_test.dart +++ b/pkgs/code_builder/test/specs/control_test.dart @@ -11,7 +11,7 @@ void main() { useDartfmt(); group('for loop', () { - test('emits a full for loop with body', () { + test('should emit a full for loop with body', () { final loop = ForLoop((b) { b ..initialize = declareVar('i', type: refer('int')).assign(literal(0)) @@ -26,7 +26,7 @@ void main() { ); }); - test('emits a for loop with only init', () { + test('should emit a for loop with only init', () { final loop = ForLoop((b) { b.initialize = declareVar('i').assign(literal(1)); }); @@ -34,7 +34,7 @@ void main() { expect(loop, equalsDart('for (var i = 1;;) {}')); }); - test('emits a for loop with only condition', () { + test('should emit a for loop with only condition', () { final loop = ForLoop((b) { b.condition = refer('keepGoing'); }); @@ -42,7 +42,7 @@ void main() { expect(loop, equalsDart('for (; keepGoing;) {}')); }); - test('emits a for loop with only advance', () { + test('should emit a for loop with only advance', () { final loop = ForLoop((b) { b.advance = refer('i').operatorUnaryPostfixIncrement(); }); @@ -50,7 +50,7 @@ void main() { expect(loop, equalsDart('for (;; i++) {}')); }); - test('emits a for loop with label', () { + test('should emit a for loop with label', () { final loop = ForLoop((b) { b.label = 'outer'; }); @@ -60,7 +60,7 @@ void main() { }); group('for-in loop', () { - test('emits a basic for-in loop', () { + test('should emit a basic for-in loop', () { final loop = ForInLoop((b) { b ..variable = refer('item') @@ -70,7 +70,7 @@ void main() { expect(loop, equalsDart('for (item in items) {}')); }); - test('emits a labeled for-in loop', () { + test('should emit a labeled for-in loop', () { final loop = ForInLoop((b) { b ..label = 'each' @@ -81,7 +81,7 @@ void main() { expect(loop, equalsDart('each: for (item in items) {}')); }); - test('emits an async for-in loop', () { + test('should emit an async for-in loop', () { final loop = ForInLoop((b) { b ..async = true @@ -94,7 +94,7 @@ void main() { }); group('while loop', () { - test('emits a basic while loop', () { + test('should emit a basic while loop', () { final loop = WhileLoop((b) { b.condition = refer('running'); }); @@ -102,7 +102,7 @@ void main() { expect(loop, equalsDart('while (running) {}')); }); - test('emits a labeled while loop', () { + test('should emit a labeled while loop', () { final loop = WhileLoop((b) { b ..label = 'mainLoop' @@ -112,7 +112,7 @@ void main() { expect(loop, equalsDart('mainLoop: while (true) {}')); }); - test('emits a do-while loop', () { + test('should emit a do-while loop', () { final loop = WhileLoop((b) { b ..doWhile = true @@ -127,5 +127,369 @@ void main() { 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('condition', () { + test('should emit a basic if condition', () { + final condition = Condition((b) { + b + ..condition = refer('x').equalTo(literal(0)) + ..body.addExpression(refer('print').call([literal('zero')])); + }); + + expect( + condition, + equalsDart('if (x == 0) {\n print(\'zero\');\n}'), + ); + + expect( + condition.asTree, + equalsDart('if (x == 0) {\n print(\'zero\');\n}'), + ); + }); + + test('should emit a condition with null body', () { + final condition = Condition((b) { + b.condition = literal(true); + }); + + expect( + condition, + equalsDart('if (true) {}'), + ); + }); + + test('should emit an else condition', () { + final original = Condition((b) { + b.body.addExpression(refer('print').call([literal('fallback')])); + }); + + final elseBlock = original.asElse; + + expect( + elseBlock, + equalsDart('else {\n print(\'fallback\');\n}'), + ); + }); + + test('should emit an else-if condition', () { + final original = Condition((b) { + b + ..condition = refer('value').greaterThan(literal(10)) + ..body.addExpression(refer('log').call([literal('big')])); + }); + + final elseIf = original.asElse; + + expect( + elseIf, + equalsDart('else if (value > 10) {\n log(\'big\');\n}'), + ); + }); + + test('should throw if condition is null', () { + expect( + () => Condition((b) {}).accept(DartEmitter()), + throwsArgumentError, + ); + }); + + test('should emit an if-case condition', () { + final condition = Condition((b) { + b.ifCase( + object: refer('value'), + pattern: refer('int'), + ); + b.body.addExpression(refer('print').call([literal('int')])); + }); + + expect( + condition, + equalsDart('if (value case int) {\n print(\'int\');\n}'), + ); + }); + + test('should emit an if-case with guard clause', () { + final condition = Condition((b) { + b.ifCase( + object: refer('value'), + pattern: refer('int'), + guard: refer('value').greaterThan(literal(0)), + ); + b.body.addExpression(refer('print').call([literal('positive')])); + }); + + expect( + condition, + equalsDart( + 'if (value case int when value > 0) {\n print(\'positive\');\n}'), + ); + }); + }); + + group('if tree', () { + test('should emit a single if block', () { + final tree = IfTree((b) { + b.add(Condition((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 if-else if-else chain', () { + final tree = IfTree((b) { + b + ..add(Condition((b) { + b + ..condition = refer('x').equalTo(literal(1)) + ..body.addExpression(refer('print').call([literal('one')])); + })) + ..add(Condition((b) { + b + ..condition = refer('x').equalTo(literal(2)) + ..body.addExpression(refer('print').call([literal('two')])); + })) + ..orElse((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 IfTree.of constructor', () { + final tree = IfTree.of([ + Condition((b) { + b + ..condition = refer('ready') + ..body.addExpression(refer('start').call([])); + }), + Condition((b) { + b.body.addExpression(refer('exit').call([])); + }), + ]); + + expect( + tree, + equalsDart(''' +if (ready) { + start(); +} else { + exit(); +}'''), + ); + }); + + test('should support withCondition', () { + final base = IfTree((b) { + b.add(Condition((b) { + b + ..condition = refer('a') + ..body.addExpression(refer('doA').call([])); + })); + }); + + final extended = base.withCondition(Condition((b) { + b + ..condition = refer('b') + ..body.addExpression(refer('doB').call([])); + })); + + expect( + extended, + equalsDart(''' +if (a) { + doA(); +} else if (b) { + doB(); +}'''), + ); + }); + + test('should support elseIf', () { + final tree = IfTree((b) { + b.add(Condition((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 = IfTree((b) { + b.add(Condition((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 support empty IfTree', () { + final tree = IfTree((b) {}); + expect(tree.blocks, isEmpty); + }); + }); + + group('if tree builder', () { + test('should support add', () { + final tree = IfTree((b) { + final condition = Condition((b) { + b + ..condition = refer('ok') + ..body.addExpression(refer('run').call([])); + }); + b.add(condition); + }); + + expect(tree, equalsDart('if (ok) {\n run();\n}')); + }); + + test('should support addAll', () { + final conditions = [ + Condition((b) { + b + ..condition = refer('x > 0') + ..body.addExpression(refer('handlePositive').call([])); + }), + Condition((b) { + b.body.addExpression(refer('handleZeroOrNegative').call([])); + }), + ]; + + final tree = IfTree((b) { + b.addAll(conditions); + }); + + expect( + tree, + equalsDart(''' +if (x > 0) { + handlePositive(); +} else { + handleZeroOrNegative(); +}'''), + ); + }); + + test('should support orElse', () { + final tree = IfTree((b) { + b + ..add(Condition((b) { + b + ..condition = refer('a') + ..body.addExpression(refer('doA').call([])); + })) + ..orElse((body) { + body.addExpression(refer('fallback').call([])); + }); + }); + + expect( + tree, + equalsDart(''' +if (a) { + doA(); +} else { + fallback(); +}'''), + ); + }); + + test('should support orElseThrow', () { + final tree = IfTree((b) { + b + ..add(Condition((b) { + b + ..condition = refer('valid') + ..body.addExpression(refer('process').call([])); + })) + ..orElseThrow(refer('UnsupportedError') + .newInstance([literal('Invalid input')])); + }); + + expect( + tree, + equalsDart(''' +if (valid) { + process(); +} else { + throw UnsupportedError('Invalid input'); +}'''), + ); + }); + + test('should support ifThen', () { + final tree = IfTree((b) { + b.ifThen((cond) { + cond + ..condition = refer('ready') + ..body.addExpression(refer('init').call([])); + }); + }); + + expect(tree, equalsDart('if (ready) {\n init();\n}')); + }); }); } From 415bc29d4b07d30c9990dc7010d36c8abadcabe3 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Sat, 26 Jul 2025 16:03:58 -0500 Subject: [PATCH 07/17] [code_builder] feat: support try/catch blocks * add `CatchBlock` and `TryCatch` + builders for generating catch clauses and try/catch blocks * add `ControlFlow.rethrow` to support `rethrow` keyword * recommend build_value helper extension for convenience * add relevant tests --- pkgs/code_builder/.vscode/extensions.json | 3 +- pkgs/code_builder/CHANGELOG.md | 7 +- pkgs/code_builder/lib/code_builder.dart | 6 +- pkgs/code_builder/lib/src/emitter.dart | 2 +- pkgs/code_builder/lib/src/specs/control.dart | 166 ++++++++++- .../code_builder/lib/src/specs/control.g.dart | 274 ++++++++++++++++++ .../lib/src/specs/expression.dart | 2 +- .../lib/src/specs/expression/control.dart | 44 +-- .../test/specs/code/control_test.dart | 19 +- .../code_builder/test/specs/control_test.dart | 153 ++++++++++ 10 files changed, 628 insertions(+), 48 deletions(-) diff --git a/pkgs/code_builder/.vscode/extensions.json b/pkgs/code_builder/.vscode/extensions.json index b2ed32402..99e6778c5 100644 --- a/pkgs/code_builder/.vscode/extensions.json +++ b/pkgs/code_builder/.vscode/extensions.json @@ -2,6 +2,7 @@ "recommendations": [ "streetsidesoftware.code-spell-checker", "Dart-Code.dart-code", - "DavidAnson.vscode-markdownlint" + "DavidAnson.vscode-markdownlint", + "GiancarloCode.built-value-snippets" ] } \ No newline at end of file diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index 98fc199ce..c63b73328 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -24,16 +24,21 @@ * Add `ControlFlow.continueLabel` * Add `ControlFlow.returnVoid` * Add `ControlFlow.ifCase` + * Add `ControlFlow.rethrowVoid` * 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 +* Support emitting `if` statements and `if`/`else if`/`else` trees. * Add `Condition` and `ConditionBuilder` for single statements * Add `IfTree` and `IfTreeBuilder` for conditional trees +* Support emitting `try`/`catch`/`finally` blocks. + * Add `CatchBlock` and `CatchBlockBuilder` for catch clauses. + * Add `TryCatch` and `TryCatchBuilder` for try/catch blocks. + ## 4.10.1 * Require Dart `^3.5.0` diff --git a/pkgs/code_builder/lib/code_builder.dart b/pkgs/code_builder/lib/code_builder.dart index 4de80c980..0dff681f8 100644 --- a/pkgs/code_builder/lib/code_builder.dart +++ b/pkgs/code_builder/lib/code_builder.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2017, 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. @@ -12,6 +12,8 @@ export 'src/specs/code.dart' export 'src/specs/constructor.dart' show Constructor, ConstructorBuilder; export 'src/specs/control.dart' show + CatchBlock, + CatchBlockBuilder, Condition, ConditionBuilder, ForInLoop, @@ -20,6 +22,8 @@ export 'src/specs/control.dart' ForLoopBuilder, IfTree, IfTreeBuilder, + TryCatch, + TryCatchBuilder, WhileLoop, WhileLoopBuilder; export 'src/specs/directive.dart' diff --git a/pkgs/code_builder/lib/src/emitter.dart b/pkgs/code_builder/lib/src/emitter.dart index 05c2683ea..04a0cec81 100644 --- a/pkgs/code_builder/lib/src/emitter.dart +++ b/pkgs/code_builder/lib/src/emitter.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2017, 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. diff --git a/pkgs/code_builder/lib/src/specs/control.dart b/pkgs/code_builder/lib/src/specs/control.dart index 6b8268ffb..16708d593 100644 --- a/pkgs/code_builder/lib/src/specs/control.dart +++ b/pkgs/code_builder/lib/src/specs/control.dart @@ -9,6 +9,7 @@ import 'package:meta/meta.dart'; import '../base.dart'; import 'code.dart'; import 'expression.dart'; +import 'reference.dart'; part 'control.g.dart'; @@ -177,7 +178,7 @@ abstract class WhileLoop @immutable abstract mixin class ControlTree implements Code, Spec { /// The items in this tree. - BuiltList get blocks; + List get _blocks; @override R accept(covariant ControlBlockVisitor visitor, [R? context]) => @@ -222,6 +223,9 @@ abstract class Condition IfTree get asTree => IfTree.of([this]); } +/// Builds a [Condition]. +/// +/// {@category controlFlow} abstract class ConditionBuilder implements Builder { ConditionBuilder._(); @@ -302,9 +306,11 @@ abstract class IfTree with ControlTree implements Built { ..insert(0, first); } - @override BuiltList get blocks; + @override + List get _blocks => blocks.toList(); + /// Returns a new [IfTree] with [condition] added. IfTree withCondition(Condition condition) => (toBuilder()..add(condition)).build(); @@ -324,6 +330,8 @@ abstract class IfTree with ControlTree implements Built { } /// Builds an [IfTree]. +/// +/// {@category controlFlow} abstract class IfTreeBuilder implements Builder { IfTreeBuilder._(); factory IfTreeBuilder() = _$IfTreeBuilder; @@ -365,6 +373,155 @@ abstract class IfTreeBuilder implements Builder { ); } +/// 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). + /// + /// ``` dart + /// on type catch (exception) + /// on type catch (exception, stacktrace) + /// ``` + Reference? get type; + + /// The name of the exception parameter (default: `e`). + /// + /// ```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 => + type == null ? _catch : ControlExpression.onStatement(type!, _catch); + + /// Set the default value of [exception] + @BuiltValueHook(initializeBuilder: true) + static void _initialize(CatchBlockBuilder builder) => builder.exception = 'e'; +} + +/// 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); +} + /// Knowledge of different types of control blocks. /// @internal @@ -419,7 +576,7 @@ abstract mixin class ControlBlockEmitter StringSink visitControlTree(ControlTree tree, [StringSink? output]) { output ??= StringBuffer(); - for (final item in tree.blocks) { + for (final item in tree._blocks.nonNulls) { item.accept(this, output); output.write(' '); } @@ -427,7 +584,7 @@ abstract mixin class ControlBlockEmitter return output; } -@override + @override StringSink visitControlExpression(ControlExpression expression, [StringSink? output]) { output ??= StringBuffer(); @@ -483,5 +640,4 @@ abstract mixin class ControlBlockEmitter return output; } - } diff --git a/pkgs/code_builder/lib/src/specs/control.g.dart b/pkgs/code_builder/lib/src/specs/control.g.dart index 1fea71caf..64619550d 100644 --- a/pkgs/code_builder/lib/src/specs/control.g.dart +++ b/pkgs/code_builder/lib/src/specs/control.g.dart @@ -638,4 +638,278 @@ class _$IfTreeBuilder extends IfTreeBuilder { } } +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, required 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() { + CatchBlock._initialize(this); + } + + 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: BuiltValueNullFieldError.checkNotNull( + exception, r'CatchBlock', '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; + } +} + // ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/pkgs/code_builder/lib/src/specs/expression.dart b/pkgs/code_builder/lib/src/specs/expression.dart index 7428339d2..b4cfb920a 100644 --- a/pkgs/code_builder/lib/src/specs/expression.dart +++ b/pkgs/code_builder/lib/src/specs/expression.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2017, 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. diff --git a/pkgs/code_builder/lib/src/specs/expression/control.dart b/pkgs/code_builder/lib/src/specs/expression/control.dart index 6111b370a..f941301db 100644 --- a/pkgs/code_builder/lib/src/specs/expression/control.dart +++ b/pkgs/code_builder/lib/src/specs/expression/control.dart @@ -126,43 +126,18 @@ class ControlExpression extends Expression { static const doStatement = ControlExpression('do'); - /// A `try` statement. - /// - /// ```dart - /// try - /// ``` - /// - /// https://dart.dev/language/error-handling#catch - /// - static const tryStatement = ControlExpression('try'); - /// Returns a `catch` statement. - /// - /// ```dart - /// catch (error) - /// catch (error, stacktrace) - /// ``` - /// - /// https://dart.dev/language/error-handling#catch - /// - - factory ControlExpression.catchStatement(Expression error, - [Expression? stacktrace]) => + factory ControlExpression.catchStatement(String error, + [String? stacktrace]) => ControlExpression('catch', - body: [error, if (stacktrace != null) stacktrace], separator: ','); + body: [refer(error), if (stacktrace != null) refer(stacktrace)], + separator: ','); - /// Returns an `on` statement. - /// - /// ```dart - /// on error - /// ``` - /// - /// https://dart.dev/language/error-handling#catch - /// - - factory ControlExpression.onStatement(Expression error) => - ControlExpression('on', body: [error], parenthesised: false); + factory ControlExpression.onStatement( + Reference type, ControlExpression statement) => + ControlExpression('on', + body: [type, statement], parenthesised: false, separator: ''); /// A `finally` statement. /// @@ -283,6 +258,9 @@ extension ControlFlow on Expression { /// For usage with a label, use [continueLabel]. static const continueVoid = LiteralExpression._('continue'); + /// `rethrow` + static const rethrowVoid = LiteralExpression._('rethrow'); + /// Returns a labeled `break` statement. /// /// ```dart diff --git a/pkgs/code_builder/test/specs/code/control_test.dart b/pkgs/code_builder/test/specs/code/control_test.dart index af64eec20..ced5d45a6 100644 --- a/pkgs/code_builder/test/specs/code/control_test.dart +++ b/pkgs/code_builder/test/specs/code/control_test.dart @@ -196,15 +196,15 @@ void main() { test( 'should emit a catch statement with only error', () { - expect(ControlExpression.catchStatement(refer('e')), - equalsDart('catch (e)')); + expect( + ControlExpression.catchStatement('e'), equalsDart('catch (e)')); }, ); test( 'should emit a catch statement with error and stacktrace', () { - expect(ControlExpression.catchStatement(refer('e'), refer('s')), + expect(ControlExpression.catchStatement('e', 's'), equalsDart('catch (e, s)')); }, ); @@ -212,8 +212,10 @@ void main() { test( 'should emit an on statement', () { - expect(ControlExpression.onStatement(refer('FormatException')), - equalsDart('on FormatException')); + expect( + ControlExpression.onStatement(refer('FormatException'), + ControlExpression.catchStatement('e')), + equalsDart('on FormatException catch (e)')); }, ); @@ -261,6 +263,13 @@ void main() { 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'), diff --git a/pkgs/code_builder/test/specs/control_test.dart b/pkgs/code_builder/test/specs/control_test.dart index 81f94e269..70fafc12b 100644 --- a/pkgs/code_builder/test/specs/control_test.dart +++ b/pkgs/code_builder/test/specs/control_test.dart @@ -492,4 +492,157 @@ if (valid) { expect(tree, equalsDart('if (ready) {\n init();\n}')); }); }); + + group('catch block', () { + test('should emit catch with default exception name', () { + final catchBlock = CatchBlock((b) => b..body.addExpression(literal(1))); + expect(catchBlock, equalsDart('catch (e) {\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 on-type catch block', () { + final catchBlock = CatchBlock((b) => b + ..type = refer('FormatException') + ..exception = 'e' + ..stacktrace = 's' + ..body.addExpression(refer('print').call([refer('e')]))); + expect( + catchBlock, + equalsDart('on FormatException catch (e, 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 (e) { + handleError(); +}'''), + ); + }); + + 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 (e) { + 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 (e) { + 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 (e) { + 1; +} finally { + done; +} +''')); + }); + }); } From 32797a0da0ecf63e61fce34763b699ca9eed0753 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Sun, 27 Jul 2025 12:05:13 -0500 Subject: [PATCH 08/17] [code_builder] refactor: split up control.dart --- pkgs/code_builder/lib/src/mixins/control.dart | 58 ++ pkgs/code_builder/lib/src/specs/control.dart | 512 +----------------- .../lib/src/specs/control/branches.dart | 193 +++++++ .../lib/src/specs/control/handling.dart | 154 ++++++ .../lib/src/specs/control/loops.dart | 125 +++++ 5 files changed, 534 insertions(+), 508 deletions(-) create mode 100644 pkgs/code_builder/lib/src/mixins/control.dart create mode 100644 pkgs/code_builder/lib/src/specs/control/branches.dart create mode 100644 pkgs/code_builder/lib/src/specs/control/handling.dart create mode 100644 pkgs/code_builder/lib/src/specs/control/loops.dart 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 000000000..2aa27edb6 --- /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/control.dart b/pkgs/code_builder/lib/src/specs/control.dart index 16708d593..689b2be3a 100644 --- a/pkgs/code_builder/lib/src/specs/control.dart +++ b/pkgs/code_builder/lib/src/specs/control.dart @@ -12,515 +12,11 @@ import 'expression.dart'; import 'reference.dart'; part 'control.g.dart'; +part '../mixins/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 -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); -} - -// abstract mixin class ControlTree - -/// 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); -} - -/// A tree of [ControlBlock]s -@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); -} - -/// Represents a single `if` block. -/// -/// Use [IfTree] to create a tree of `if`, `else if`, -/// and `else` statements. -/// -/// {@category controlFlow} -abstract class Condition - with ControlBlock - implements Built { - Condition._(); - factory Condition(void Function(ConditionBuilder block) builder) = - _$Condition; - - /// The statement condition. - /// - /// Required if this is a standalone [Condition] or - /// the first in an [IfTree], otherwise optional. - Expression? get condition; - - ControlExpression? get _statement => - condition == null ? null : ControlExpression.ifStatement(condition!); - - @override - ControlExpression get _expression => - _statement ?? - (throw ArgumentError( - 'A condition must be provided with an `if` statement', 'condition')); - - /// This condition as an `else` block. - /// - /// Will be `else` if [condition] is `null`, - /// otherwise `else if`. - Condition get asElse => ElseCondition(this); - - /// Returns an [IfTree] with just this [Condition]. - IfTree get asTree => IfTree.of([this]); -} - -/// Builds a [Condition]. -/// -/// {@category controlFlow} -abstract class ConditionBuilder - implements Builder { - ConditionBuilder._(); - factory ConditionBuilder() = _$ConditionBuilder; - - BlockBuilder body = BlockBuilder(); - Expression? condition; - - /// Sets [condition] to an `if-case` expression. - /// - /// Uses [ControlFlow.ifCase] to create the expression. - /// - /// The expression will take the form: - /// ```dart - /// object case pattern - /// ``` - /// - /// Optionally set a guard (`when`) clause with [guard]: - /// ```dart - /// object case pattern when guard - /// ``` - /// - /// See https://dart.dev/language/branches#if-case - void ifCase({ - required Expression object, - required Expression pattern, - Expression? guard, - }) { - condition = - ControlFlow.ifCase(object: object, pattern: pattern, guard: guard); - } -} - -/// A [condition] preceded by `else` -@internal -@visibleForTesting -class ElseCondition extends _$Condition { - ElseCondition(Condition condition) - : super._(body: condition.body, condition: condition.condition); - - @override - ControlExpression get _expression => - ControlExpression.elseStatement(_statement); -} - -/// Represents an `if`/`else` tree. -/// -/// The first [Condition] in [blocks] will be treated as an `if` -/// block. All subsequent conditions will be treated as `else` blocks -/// using [Condition.asElse]. -/// -/// {@category controlFlow} -abstract class IfTree with ControlTree implements Built { - IfTree._(); - - /// Build an [IfTree] - factory IfTree(void Function(IfTreeBuilder tree) builder) = _$IfTree; - - /// Create an [IfTree] from a list of [conditions]. - factory IfTree.of(Iterable conditions) => IfTree( - (tree) { - tree.addAll(conditions); - }, - ); - - /// Called when an [IfTreeBuilder] is built. - /// - /// Replaces all but the first block with an [ElseCondition] - /// - @BuiltValueHook(finalizeBuilder: true) - static void _build(IfTreeBuilder builder) { - if (builder.blocks.isEmpty) return; - - final first = builder.blocks.first; - builder.blocks - ..skip(1) - ..map((b) => b.asElse) - ..insert(0, first); - } - - BuiltList get blocks; - - @override - List get _blocks => blocks.toList(); - - /// Returns a new [IfTree] with [condition] added. - IfTree withCondition(Condition condition) => - (toBuilder()..add(condition)).build(); - - /// Builds a [Condition] with [builder] and returns - /// a new [IfTree] with it added. - IfTree elseIf(void Function(ConditionBuilder block) builder) => - withCondition((ConditionBuilder()..update(builder)).build()); - - /// Builds a block with [builder] and returns a new [IfTree] - /// with it added as an `else` [Condition]. - IfTree orElse(void Function(BlockBuilder body) builder) => elseIf( - (block) { - builder(block.body); - }, - ); -} - -/// Builds an [IfTree]. -/// -/// {@category controlFlow} -abstract class IfTreeBuilder implements Builder { - IfTreeBuilder._(); - factory IfTreeBuilder() = _$IfTreeBuilder; - - /// The items in this tree. - ListBuilder blocks = ListBuilder(); - - /// Build a [Condition] with [builder] and add it to the tree. - /// - /// Shorthand for calling `add` and creating a condition - void ifThen(void Function(ConditionBuilder block) builder) => - add((ConditionBuilder()..update(builder)).build()); - - /// Add a [Condition] to the tree. - /// - /// Shorthand for `blocks.add` - void add(Condition condition) => blocks.add(condition); - - /// Add multiple [Condition]s to the tree. - /// - /// Shorthand for `blocks.addAll` - void addAll(Iterable conditions) => blocks.addAll(conditions); - - /// Builds a block using [builder] and adds it to the tree - /// as an `else` [Condition]. - /// - /// Shorthand for calling [add] and creating an `else` condition. - void orElse(void Function(BlockBuilder body) builder) => add(Condition( - (block) { - builder(block.body); - }, - )); - - /// Shorthand to add an `else` statement that throws [expression]. - void orElseThrow(Expression expression) => orElse( - (body) { - body.addExpression(expression.thrown); - }, - ); -} - -/// 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). - /// - /// ``` dart - /// on type catch (exception) - /// on type catch (exception, stacktrace) - /// ``` - Reference? get type; - - /// The name of the exception parameter (default: `e`). - /// - /// ```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 => - type == null ? _catch : ControlExpression.onStatement(type!, _catch); - - /// Set the default value of [exception] - @BuiltValueHook(initializeBuilder: true) - static void _initialize(CatchBlockBuilder builder) => builder.exception = 'e'; -} - -/// 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); -} +part './control/loops.dart'; +part './control/branches.dart'; +part './control/handling.dart'; /// Knowledge of different types of control blocks. /// 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 000000000..2575e2cd4 --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/control/branches.dart @@ -0,0 +1,193 @@ +// 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 single `if` block. +/// +/// Use [IfTree] to create a tree of `if`, `else if`, +/// and `else` statements. +/// +/// {@category controlFlow} +abstract class Condition + with ControlBlock + implements Built { + Condition._(); + factory Condition(void Function(ConditionBuilder block) builder) = + _$Condition; + + /// The statement condition. + /// + /// Required if this is a standalone [Condition] or + /// the first in an [IfTree], otherwise optional. + Expression? get condition; + + ControlExpression? get _statement => + condition == null ? null : ControlExpression.ifStatement(condition!); + + @override + ControlExpression get _expression => + _statement ?? + (throw ArgumentError( + 'A condition must be provided with an `if` statement', 'condition')); + + /// This condition as an `else` block. + /// + /// Will be `else` if [condition] is `null`, + /// otherwise `else if`. + Condition get asElse => ElseCondition(this); + + /// Returns an [IfTree] with just this [Condition]. + IfTree get asTree => IfTree.of([this]); +} + +/// Builds a [Condition]. +/// +/// {@category controlFlow} +abstract class ConditionBuilder + implements Builder { + ConditionBuilder._(); + factory ConditionBuilder() = _$ConditionBuilder; + + BlockBuilder body = BlockBuilder(); + Expression? condition; + + /// Sets [condition] to an `if-case` expression. + /// + /// Uses [ControlFlow.ifCase] to create the expression. + /// + /// The expression will take the form: + /// ```dart + /// object case pattern + /// ``` + /// + /// Optionally set a guard (`when`) clause with [guard]: + /// ```dart + /// object case pattern when guard + /// ``` + /// + /// See https://dart.dev/language/branches#if-case + void ifCase({ + required Expression object, + required Expression pattern, + Expression? guard, + }) { + condition = + ControlFlow.ifCase(object: object, pattern: pattern, guard: guard); + } +} + +/// A [condition] preceded by `else` +@internal +@visibleForTesting +class ElseCondition extends _$Condition { + ElseCondition(Condition condition) + : super._(body: condition.body, condition: condition.condition); + + @override + ControlExpression get _expression => + ControlExpression.elseStatement(_statement); +} + +/// Represents an `if`/`else` tree. +/// +/// The first [Condition] in [blocks] will be treated as an `if` +/// block. All subsequent conditions will be treated as `else` blocks +/// using [Condition.asElse]. +/// +/// {@category controlFlow} +abstract class IfTree with ControlTree implements Built { + IfTree._(); + + /// Build an [IfTree] + factory IfTree(void Function(IfTreeBuilder tree) builder) = _$IfTree; + + /// Create an [IfTree] from a list of [conditions]. + factory IfTree.of(Iterable conditions) => IfTree( + (tree) { + tree.addAll(conditions); + }, + ); + + /// Called when an [IfTreeBuilder] is built. + /// + /// Replaces all but the first block with an [ElseCondition] + /// + @BuiltValueHook(finalizeBuilder: true) + static void _build(IfTreeBuilder builder) { + if (builder.blocks.isEmpty) return; + + final first = builder.blocks.first; + builder.blocks + ..skip(1) + ..map((b) => b.asElse) + ..insert(0, first); + } + + BuiltList get blocks; + + @override + List get _blocks => blocks.toList(); + + /// Returns a new [IfTree] with [condition] added. + IfTree withCondition(Condition condition) => + (toBuilder()..add(condition)).build(); + + /// Builds a [Condition] with [builder] and returns + /// a new [IfTree] with it added. + IfTree elseIf(void Function(ConditionBuilder block) builder) => + withCondition((ConditionBuilder()..update(builder)).build()); + + /// Builds a block with [builder] and returns a new [IfTree] + /// with it added as an `else` [Condition]. + IfTree orElse(void Function(BlockBuilder body) builder) => elseIf( + (block) { + builder(block.body); + }, + ); +} + +/// Builds an [IfTree]. +/// +/// {@category controlFlow} +abstract class IfTreeBuilder implements Builder { + IfTreeBuilder._(); + factory IfTreeBuilder() = _$IfTreeBuilder; + + /// The items in this tree. + ListBuilder blocks = ListBuilder(); + + /// Build a [Condition] with [builder] and add it to the tree. + /// + /// Shorthand for calling `add` and creating a condition + void ifThen(void Function(ConditionBuilder block) builder) => + add((ConditionBuilder()..update(builder)).build()); + + /// Add a [Condition] to the tree. + /// + /// Shorthand for `blocks.add` + void add(Condition condition) => blocks.add(condition); + + /// Add multiple [Condition]s to the tree. + /// + /// Shorthand for `blocks.addAll` + void addAll(Iterable conditions) => blocks.addAll(conditions); + + /// Builds a block using [builder] and adds it to the tree + /// as an `else` [Condition]. + /// + /// Shorthand for calling [add] and creating an `else` condition. + void orElse(void Function(BlockBuilder body) builder) => add(Condition( + (block) { + builder(block.body); + }, + )); + + /// Shorthand to add an `else` statement that throws [expression]. + void orElseThrow(Expression expression) => orElse( + (body) { + body.addExpression(expression.thrown); + }, + ); +} 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 000000000..f9dd60b30 --- /dev/null +++ b/pkgs/code_builder/lib/src/specs/control/handling.dart @@ -0,0 +1,154 @@ +// 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). + /// + /// ``` dart + /// on type catch (exception) + /// on type catch (exception, stacktrace) + /// ``` + Reference? get type; + + /// The name of the exception parameter (default: `e`). + /// + /// ```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 => + type == null ? _catch : ControlExpression.onStatement(type!, _catch); + + /// Set the default value of [exception] + @BuiltValueHook(initializeBuilder: true) + static void _initialize(CatchBlockBuilder builder) => builder.exception = 'e'; +} + +/// 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 000000000..3e0a085e0 --- /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); +} From b39e41576ba5a98afa2c05ea2b7c34a884ba1273 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Sun, 27 Jul 2025 16:30:18 -0500 Subject: [PATCH 09/17] [code_builder] feat: support switch statements * add `Case` (+ builder) for creating cases in switch statements * add `SwitchStatement` (+ builder) for emitting switch statements * add relevant tests still todo: add switch *expressions* --- pkgs/code_builder/lib/code_builder.dart | 4 + pkgs/code_builder/lib/src/specs/control.dart | 41 +++ .../code_builder/lib/src/specs/control.g.dart | 220 +++++++++++++++ .../lib/src/specs/control/branches.dart | 198 ++++++++++++++ .../lib/src/specs/expression/control.dart | 6 + .../test/specs/code/control_test.dart | 15 + .../code_builder/test/specs/control_test.dart | 258 ++++++++++++++++++ 7 files changed, 742 insertions(+) diff --git a/pkgs/code_builder/lib/code_builder.dart b/pkgs/code_builder/lib/code_builder.dart index 0dff681f8..acc99ad54 100644 --- a/pkgs/code_builder/lib/code_builder.dart +++ b/pkgs/code_builder/lib/code_builder.dart @@ -12,6 +12,8 @@ export 'src/specs/code.dart' export 'src/specs/constructor.dart' show Constructor, ConstructorBuilder; export 'src/specs/control.dart' show + Case, + CaseBuilder, CatchBlock, CatchBlockBuilder, Condition, @@ -22,6 +24,8 @@ export 'src/specs/control.dart' ForLoopBuilder, IfTree, IfTreeBuilder, + SwitchStatement, + SwitchStatementBuilder, TryCatch, TryCatchBuilder, WhileLoop, diff --git a/pkgs/code_builder/lib/src/specs/control.dart b/pkgs/code_builder/lib/src/specs/control.dart index 689b2be3a..8ebe16fa4 100644 --- a/pkgs/code_builder/lib/src/specs/control.dart +++ b/pkgs/code_builder/lib/src/specs/control.dart @@ -28,6 +28,8 @@ abstract class ControlBlockVisitor T visitWhileLoop(WhileLoop loop, [T? context]); T visitControlTree(ControlTree tree, [T? context]); T visitControlExpression(ControlExpression expression, [T? context]); + T visitSwitch(Switch statement, [T? context]); + T visitCaseStatement(CaseStatement statement, [T? context]); } /// Knowledge of how to write valid Dart code from [ControlBlockVisitor]. @@ -136,4 +138,43 @@ abstract mixin class ControlBlockEmitter 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.write('$label:\n'); + } + + if (statement._default) { + output.write('default:\n'); + } else { + output.write('case '); + statement.pattern.accept(this, output); + + if (statement.guard case final Expression guard) { + output.write(' when '); + guard.accept(this, output); + } + + output.write(':\n'); + } + + if (statement.body case final Code body) { + body.accept(this, output); + } + + return output; + } } diff --git a/pkgs/code_builder/lib/src/specs/control.g.dart b/pkgs/code_builder/lib/src/specs/control.g.dart index 64619550d..3c1773554 100644 --- a/pkgs/code_builder/lib/src/specs/control.g.dart +++ b/pkgs/code_builder/lib/src/specs/control.g.dart @@ -638,6 +638,226 @@ class _$IfTreeBuilder extends IfTreeBuilder { } } +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 { + _$SwitchStatement? _$v; + + Expression? _value; + Expression? get value => _$this._value; + set value(Expression? value) => _$this._value = value; + + ListBuilder>? _cases; + ListBuilder> get cases => + _$this._cases ??= ListBuilder>(); + set cases(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(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 _$CatchBlock extends CatchBlock { @override final Reference? type; diff --git a/pkgs/code_builder/lib/src/specs/control/branches.dart b/pkgs/code_builder/lib/src/specs/control/branches.dart index 2575e2cd4..ebadc79b9 100644 --- a/pkgs/code_builder/lib/src/specs/control/branches.dart +++ b/pkgs/code_builder/lib/src/specs/control/branches.dart @@ -191,3 +191,201 @@ abstract class IfTreeBuilder implements Builder { }, ); } + +/// 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 [ControlFlow.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: ControlFlow.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** +/// +/// Base class for `switch` types. +/// +@internal +@optionalTypeArgs +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; + + // non-abstract so that built_value ignores it + /// Convert generic [Case] into an implementation-specific buildable + /// subtype. + Iterable get _cases => + throw UnsupportedError('Must be implemented by subclasses'); +} + +/// **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); +} + +/// Common [accept] implementation for [Switch] subtypes +mixin _SwitchImpl { + R accept(covariant ControlBlockVisitor visitor, [R? context]) => + // can't be `on Switch` because built_value classes can't extend + // anything but `Object`, and even though switch subtypes implement + // `Switch`, they must extend `Switch` instead in order to be allowed + // to use a mixin `on Switch`. as a workaround, we are casting this + // as a switch, which will always work because this mixin will only + // ever be used on switch subtypes (hence why it's private). + visitor.visitSwitch(this as Switch, context); +} + +abstract class SwitchStatement + with _SwitchImpl + implements Switch, Built { + SwitchStatement._(); + factory SwitchStatement([void Function(SwitchStatementBuilder) updates]) = + _$SwitchStatement; + + @override + Iterable get _cases => cases.map(CaseStatement._); +} diff --git a/pkgs/code_builder/lib/src/specs/expression/control.dart b/pkgs/code_builder/lib/src/specs/expression/control.dart index f941301db..8e358d832 100644 --- a/pkgs/code_builder/lib/src/specs/expression/control.dart +++ b/pkgs/code_builder/lib/src/specs/expression/control.dart @@ -149,6 +149,9 @@ class ControlExpression extends Expression { /// static const finallyStatement = ControlExpression('finally'); + + factory ControlExpression.switchStatement(Expression value) => + ControlExpression('switch', body: [value]); } /// Provides control-flow utilities for [Expression]. @@ -261,6 +264,9 @@ extension ControlFlow on Expression { /// `rethrow` static const rethrowVoid = LiteralExpression._('rethrow'); + /// Wildcard expression (`_`). + static const wildcard = LiteralExpression._('_'); + /// Returns a labeled `break` statement. /// /// ```dart diff --git a/pkgs/code_builder/test/specs/code/control_test.dart b/pkgs/code_builder/test/specs/code/control_test.dart index ced5d45a6..5f747f666 100644 --- a/pkgs/code_builder/test/specs/code/control_test.dart +++ b/pkgs/code_builder/test/specs/code/control_test.dart @@ -225,6 +225,14 @@ void main() { expect(ControlExpression.finallyStatement, equalsDart('finally')); }, ); + + test( + 'should emit a switch expression', + () { + final expression = ControlExpression.switchStatement(refer('object')); + expect(expression, equalsDart('switch (object)')); + }, + ); }, ); @@ -287,6 +295,13 @@ void main() { expect(expr, equalsDart('value case int when value > 0')); }); + test( + 'should emit a wildcard expression', + () { + expect(ControlFlow.wildcard, equalsDart('_')); + }, + ); + test('should build a while loop with loopWhile', () { final expr = refer('isRunning').loopWhile((b) { b.addExpression(refer('tick').call([])); diff --git a/pkgs/code_builder/test/specs/control_test.dart b/pkgs/code_builder/test/specs/control_test.dart index 70fafc12b..a99e9f69c 100644 --- a/pkgs/code_builder/test/specs/control_test.dart +++ b/pkgs/code_builder/test/specs/control_test.dart @@ -645,4 +645,262 @@ try { ''')); }); }); + + 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 = ControlFlow.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(); +}'''), + ); + }); + }); } From 8b9dbfc0c57e27e9fc2f9b0b219b98f7e8139357 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Sun, 27 Jul 2025 19:07:11 -0500 Subject: [PATCH 10/17] [code_builder] feat: support `switch` expressions * add `SwitchExpression` (+ builder) for emitting switch expressions * move `ControlFlow.wildcard` to `Expression.wildcard` * add/update relevant tests --- pkgs/code_builder/CHANGELOG.md | 7 +- pkgs/code_builder/lib/code_builder.dart | 2 + pkgs/code_builder/lib/src/specs/control.dart | 43 ++++- .../code_builder/lib/src/specs/control.g.dart | 121 +++++++++++++- .../lib/src/specs/control/branches.dart | 118 +++++++++++--- .../lib/src/specs/expression.dart | 3 + .../lib/src/specs/expression/control.dart | 3 - .../test/specs/code/control_test.dart | 7 - .../test/specs/code/expression_test.dart | 7 + .../code_builder/test/specs/control_test.dart | 154 +++++++++++++++++- 10 files changed, 421 insertions(+), 44 deletions(-) diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index c63b73328..3a2c47473 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -1,4 +1,4 @@ -# Changelog + ## 4.11.0-wip @@ -39,6 +39,11 @@ * 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. + ## 4.10.1 * Require Dart `^3.5.0` diff --git a/pkgs/code_builder/lib/code_builder.dart b/pkgs/code_builder/lib/code_builder.dart index acc99ad54..568cf04cf 100644 --- a/pkgs/code_builder/lib/code_builder.dart +++ b/pkgs/code_builder/lib/code_builder.dart @@ -24,6 +24,8 @@ export 'src/specs/control.dart' ForLoopBuilder, IfTree, IfTreeBuilder, + SwitchExpression, + SwitchExpressionBuilder, SwitchStatement, SwitchStatementBuilder, TryCatch, diff --git a/pkgs/code_builder/lib/src/specs/control.dart b/pkgs/code_builder/lib/src/specs/control.dart index 8ebe16fa4..2b52aa8ba 100644 --- a/pkgs/code_builder/lib/src/specs/control.dart +++ b/pkgs/code_builder/lib/src/specs/control.dart @@ -29,7 +29,12 @@ abstract class ControlBlockVisitor T visitControlTree(ControlTree tree, [T? context]); T visitControlExpression(ControlExpression expression, [T? context]); T visitSwitch(Switch statement, [T? context]); - T visitCaseStatement(CaseStatement 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]. @@ -41,9 +46,11 @@ abstract mixin class ControlBlockEmitter StringSink visitControlBlock(ControlBlock block, [StringSink? output]) { output ??= StringBuffer(); block._expression.accept(this, output); - output.write(' { '); + output.writeln(' {'); block.body.accept(this, output); + output.write(' }'); + return output; } @@ -52,7 +59,7 @@ abstract mixin class ControlBlockEmitter [StringSink? output]) { output ??= StringBuffer(); if (block.label != null) { - output.write('${block.label!}: '); + output.writeln('${block.label!}:'); } return visitControlBlock(block, output); @@ -67,6 +74,7 @@ abstract mixin class ControlBlockEmitter output.write(' '); loop._statement.statement.accept(this, output); + output.writeln(); return output; } @@ -150,15 +158,16 @@ abstract mixin class ControlBlockEmitter } @override - StringSink visitCaseStatement(CaseStatement statement, [StringSink? output]) { + StringSink _visitCaseStatement(CaseStatement statement, + [StringSink? output]) { output ??= StringBuffer(); if (statement.label case final String label) { - output.write('$label:\n'); + output.writeln('$label:'); } if (statement._default) { - output.write('default:\n'); + output.writeln('default:'); } else { output.write('case '); statement.pattern.accept(this, output); @@ -168,7 +177,7 @@ abstract mixin class ControlBlockEmitter guard.accept(this, output); } - output.write(':\n'); + output.writeln(':'); } if (statement.body case final Code body) { @@ -177,4 +186,24 @@ abstract mixin class ControlBlockEmitter 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 index 3c1773554..45e235c94 100644 --- a/pkgs/code_builder/lib/src/specs/control.g.dart +++ b/pkgs/code_builder/lib/src/specs/control.g.dart @@ -796,17 +796,19 @@ class _$SwitchStatement extends SwitchStatement { } class SwitchStatementBuilder - implements Builder { + implements + Builder, + SwitchBuilder { _$SwitchStatement? _$v; Expression? _value; Expression? get value => _$this._value; - set value(Expression? value) => _$this._value = value; + set value(covariant Expression? value) => _$this._value = value; ListBuilder>? _cases; ListBuilder> get cases => _$this._cases ??= ListBuilder>(); - set cases(ListBuilder>? cases) => _$this._cases = cases; + set cases(covariant ListBuilder>? cases) => _$this._cases = cases; SwitchStatementBuilder(); @@ -821,7 +823,7 @@ class SwitchStatementBuilder } @override - void replace(SwitchStatement other) { + void replace(covariant SwitchStatement other) { _$v = other as _$SwitchStatement; } @@ -858,6 +860,117 @@ class SwitchStatementBuilder } } +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; + } +} + class _$CatchBlock extends CatchBlock { @override final Reference? type; diff --git a/pkgs/code_builder/lib/src/specs/control/branches.dart b/pkgs/code_builder/lib/src/specs/control/branches.dart index ebadc79b9..0d8fe3de6 100644 --- a/pkgs/code_builder/lib/src/specs/control/branches.dart +++ b/pkgs/code_builder/lib/src/specs/control/branches.dart @@ -214,7 +214,7 @@ abstract class IfTreeBuilder implements Builder { /// /// 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 +/// Attempting to use fall-through by leaving [body] `null` will throw an /// [ArgumentError], and setting [label] will have no effect. /// /// ```dart @@ -233,7 +233,7 @@ abstract class Case implements Built, CaseBuilder> { /// /// 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 [ControlFlow.wildcard] as the pattern. + /// 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. @@ -274,7 +274,7 @@ abstract class Case implements Built, CaseBuilder> { @internal class DefaultCase extends _$Case { DefaultCase._(T body, {super.label}) - : super._(body: body, pattern: ControlFlow.wildcard); + : super._(body: body, pattern: Expression.wildcard); @override bool get _default => true; @@ -298,15 +298,36 @@ class CaseStatement extends _$Case implements Code { @override R accept(covariant ControlBlockVisitor visitor, [R? context]) => - visitor.visitCaseStatement(this, 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. /// @@ -328,9 +349,7 @@ abstract class Switch implements Code, Spec { } /// **INTERNAL** -/// /// Base class for `switch` builders -/// @internal @optionalTypeArgs abstract class SwitchBuilder { @@ -367,25 +386,82 @@ class BuildableSwitch with ControlBlock { Block get body => Block.of(cases); } -/// Common [accept] implementation for [Switch] subtypes -mixin _SwitchImpl { - R accept(covariant ControlBlockVisitor visitor, [R? context]) => - // can't be `on Switch` because built_value classes can't extend - // anything but `Object`, and even though switch subtypes implement - // `Switch`, they must extend `Switch` instead in order to be allowed - // to use a mixin `on Switch`. as a workaround, we are casting this - // as a switch, which will always work because this mixin will only - // ever be used on switch subtypes (hence why it's private). - visitor.visitSwitch(this as Switch, context); -} - +/// 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 - with _SwitchImpl 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/expression.dart b/pkgs/code_builder/lib/src/specs/expression.dart index b4cfb920a..3ac3d2407 100644 --- a/pkgs/code_builder/lib/src/specs/expression.dart +++ b/pkgs/code_builder/lib/src/specs/expression.dart @@ -438,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]. diff --git a/pkgs/code_builder/lib/src/specs/expression/control.dart b/pkgs/code_builder/lib/src/specs/expression/control.dart index 8e358d832..21b6d51af 100644 --- a/pkgs/code_builder/lib/src/specs/expression/control.dart +++ b/pkgs/code_builder/lib/src/specs/expression/control.dart @@ -264,9 +264,6 @@ extension ControlFlow on Expression { /// `rethrow` static const rethrowVoid = LiteralExpression._('rethrow'); - /// Wildcard expression (`_`). - static const wildcard = LiteralExpression._('_'); - /// Returns a labeled `break` statement. /// /// ```dart diff --git a/pkgs/code_builder/test/specs/code/control_test.dart b/pkgs/code_builder/test/specs/code/control_test.dart index 5f747f666..7bb1be98b 100644 --- a/pkgs/code_builder/test/specs/code/control_test.dart +++ b/pkgs/code_builder/test/specs/code/control_test.dart @@ -295,13 +295,6 @@ void main() { expect(expr, equalsDart('value case int when value > 0')); }); - test( - 'should emit a wildcard expression', - () { - expect(ControlFlow.wildcard, equalsDart('_')); - }, - ); - test('should build a while loop with loopWhile', () { final expr = refer('isRunning').loopWhile((b) { b.addExpression(refer('tick').call([])); diff --git a/pkgs/code_builder/test/specs/code/expression_test.dart b/pkgs/code_builder/test/specs/code/expression_test.dart index 1ea9a9e83..2bfd2b241 100644 --- a/pkgs/code_builder/test/specs/code/expression_test.dart +++ b/pkgs/code_builder/test/specs/code/expression_test.dart @@ -967,4 +967,11 @@ void main() { 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 index a99e9f69c..d2ff4f97c 100644 --- a/pkgs/code_builder/test/specs/control_test.dart +++ b/pkgs/code_builder/test/specs/control_test.dart @@ -846,7 +846,7 @@ switch (cmd) { final stmt = SwitchStatement((b) { b.value = refer('cmd'); b.cases.add(Case((cb) => cb - ..pattern = ControlFlow.wildcard + ..pattern = Expression.wildcard ..body = refer('log').call([literal('wildcard')]).statement)); }); @@ -903,4 +903,156 @@ switch (x) { ); }); }); + + 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; +''')); + }, + ); + }); } From 4ef3b88fdfe2239713b11a510a1427100b0305ff Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Mon, 28 Jul 2025 16:03:28 -0500 Subject: [PATCH 11/17] [code_builder] feat: support collection control-flow expressions * update literal collection emitters to support chaining * add `collectionIf`, `collectionElse`, `collectionFor`, and `collectionForIn` static methods to `ControlFlow` for emitting collection control-flow expressions * remove unnecessary helper function * add relevant tests --- pkgs/code_builder/.vscode/settings.json | 4 +- pkgs/code_builder/CHANGELOG.md | 39 ++- pkgs/code_builder/lib/src/emitter.dart | 19 +- .../lib/src/specs/control/branches.dart | 7 - .../lib/src/specs/expression.dart | 27 +- .../lib/src/specs/expression/control.dart | 213 +++++++++---- .../test/specs/code/control_test.dart | 280 ++++++++++++++---- .../code_builder/test/specs/control_test.dart | 23 -- 8 files changed, 449 insertions(+), 163 deletions(-) diff --git a/pkgs/code_builder/.vscode/settings.json b/pkgs/code_builder/.vscode/settings.json index ac95c336e..7de15b732 100644 --- a/pkgs/code_builder/.vscode/settings.json +++ b/pkgs/code_builder/.vscode/settings.json @@ -1,9 +1,11 @@ { "cSpell.words": [ + "controlflow", "dartfmt", + "endtemplate", "Gitter", "Nikolas", - "Rimikis", + "Rimikis" ], "cSpell.ignoreWords": [ "Substract" diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index 3a2c47473..4135cec43 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -6,34 +6,42 @@ * Require Dart `^3.6.0` due to the upgrades. -* Support `Expression.newInstanceNamed` with empty name +* Support `Expression.newInstanceNamed` with empty name. -* Fixed bug: Fields declared with `static` and `external` now produce code with correct order +* 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 `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.loopWhile` (via ext.) * Add `Expression.loopDoWhile` (via ext.) * Add `Expression.loopForIn` (via ext.) - * Add static helper functions to `ControlFlow`: - * Add `ControlFlow.breakVoid` - * Add `ControlFlow.breakLabel` - * Add `ControlFlow.continueVoid` - * Add `ControlFlow.continueLabel` - * Add `ControlFlow.returnVoid` - * Add `ControlFlow.ifCase` - * Add `ControlFlow.rethrowVoid` - -* Support emitting control-flow loops + +* 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 `Condition` and `ConditionBuilder` for single statements - * Add `IfTree` and `IfTreeBuilder` for conditional trees + * Add `Condition` and `ConditionBuilder` for single statements. + * Add `IfTree` and `IfTreeBuilder` for conditional trees. * Support emitting `try`/`catch`/`finally` blocks. * Add `CatchBlock` and `CatchBlockBuilder` for catch clauses. @@ -43,6 +51,7 @@ * 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. ## 4.10.1 diff --git a/pkgs/code_builder/lib/src/emitter.dart b/pkgs/code_builder/lib/src/emitter.dart index 04a0cec81..21b3a320e 100644 --- a/pkgs/code_builder/lib/src/emitter.dart +++ b/pkgs/code_builder/lib/src/emitter.dart @@ -42,12 +42,25 @@ 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; } diff --git a/pkgs/code_builder/lib/src/specs/control/branches.dart b/pkgs/code_builder/lib/src/specs/control/branches.dart index 0d8fe3de6..72faf815d 100644 --- a/pkgs/code_builder/lib/src/specs/control/branches.dart +++ b/pkgs/code_builder/lib/src/specs/control/branches.dart @@ -183,13 +183,6 @@ abstract class IfTreeBuilder implements Builder { builder(block.body); }, )); - - /// Shorthand to add an `else` statement that throws [expression]. - void orElseThrow(Expression expression) => orElse( - (body) { - body.addExpression(expression.thrown); - }, - ); } /// A `switch` case used in either a [SwitchStatement] or a [SwitchExpression]. diff --git a/pkgs/code_builder/lib/src/specs/expression.dart b/pkgs/code_builder/lib/src/specs/expression.dart index 3ac3d2407..9afcf7c66 100644 --- a/pkgs/code_builder/lib/src/specs/expression.dart +++ b/pkgs/code_builder/lib/src/specs/expression.dart @@ -509,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**. @@ -652,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(']'); @@ -676,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('}'); @@ -710,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 index 21b6d51af..d3d8a9b0f 100644 --- a/pkgs/code_builder/lib/src/specs/expression/control.dart +++ b/pkgs/code_builder/lib/src/specs/expression/control.dart @@ -4,12 +4,9 @@ part of '../expression.dart'; -/// Represents a control expression. +/// **INTERNAL** /// -/// The expression consists of the control statement ([control]), -/// followed by the expression body (if provided), which consists of -/// all expressions in [body] joined by [separator], optionally -/// enclosed in parenthesis ([parenthesised]). +/// Represents a control expression. /// /// {@category controlFlow} @internal @@ -42,8 +39,6 @@ class ControlExpression extends Expression { final String? separator; /// Whether or not the body should be wrapped in parenthesis (default: `true`) - /// - /// If [body] is `null` or empty, [parenthesised] will have no effect. final bool parenthesised; @visibleForTesting @@ -61,69 +56,24 @@ class ControlExpression extends Expression { ControlExpression('else', body: condition != null ? [condition] : null, parenthesised: false); - /// Returns a traditional `for` loop. - /// - /// ```dart - /// for (initialize; condition; advance) - /// ``` - /// - /// https://dart.dev/language/loops#for-loops - /// - factory ControlExpression.forLoop( Expression? initialize, Expression? condition, Expression? advance) => ControlExpression('for', body: [initialize, condition, advance], separator: ';'); - /// Returns a `for-in` loop. - /// - /// ```dart - /// for (identifier in expression) - /// ``` - /// - /// https://dart.dev/language/loops#for-loops - /// - factory ControlExpression.forInLoop( Expression identifier, Expression expression) => ControlExpression('for', body: [identifier, expression], separator: ' in'); - /// Returns an asynchronous `for` loop. - /// - /// ```dart - /// await for (identifier in expression) - /// ``` - /// - /// https://dart.dev/language/async#handling-streams - /// - factory ControlExpression.awaitForLoop( Expression identifier, Expression expression) => ControlExpression('await for', body: [identifier, expression], separator: ' in'); - /// Returns a `while` loop. - /// - /// ```dart - /// while (condition) - /// ``` - /// - /// https://dart.dev/language/loops#while-and-do-while - /// - factory ControlExpression.whileLoop(Expression condition) => ControlExpression('while', body: [condition]); - /// A `do` statement. - /// - /// ```dart - /// do - /// ``` - /// - /// https://dart.dev/language/loops#while-and-do-while - /// - static const doStatement = ControlExpression('do'); static const tryStatement = ControlExpression('try'); @@ -139,21 +89,38 @@ class ControlExpression extends Expression { ControlExpression('on', body: [type, statement], parenthesised: false, separator: ''); - /// A `finally` statement. - /// - /// ```dart - /// finally - /// ``` - /// - /// https://dart.dev/language/error-handling#finally - /// - 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} @@ -301,4 +268,128 @@ extension ControlFlow on Expression { 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/test/specs/code/control_test.dart b/pkgs/code_builder/test/specs/code/control_test.dart index 7bb1be98b..e9a36cf02 100644 --- a/pkgs/code_builder/test/specs/code/control_test.dart +++ b/pkgs/code_builder/test/specs/code/control_test.dart @@ -12,7 +12,7 @@ void main() { useDartfmt(); group( - 'ControlExpression', + 'control expression', () { // general @@ -237,7 +237,7 @@ void main() { ); group( - 'ControlFlow extension', + 'expression control-flow', () { test('should emit a yield expression', () { final expr = refer('value').yielded; @@ -295,76 +295,257 @@ void main() { expect(expr, equalsDart('value case int when value > 0')); }); - test('should build a while loop with loopWhile', () { - final expr = refer('isRunning').loopWhile((b) { - b.addExpression(refer('tick').call([])); + 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(''' -while (isRunning) { - tick(); -}'''), + equalsDart('{for (final x in items) if (x.valid) x else fallback}'), ); }); - test('should build a do-while loop with loopDoWhile', () { - final expr = refer('conditionMet').loopDoWhile((b) { - b.addExpression(refer('step').call([])); - }); + 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(''' -do { - step(); -} while (conditionMet);'''), + equalsDart('[static, else shouldNotChain, ]'), ); }); - test('should build a for-in loop with loopForIn', () { - final expr = refer('item').loopForIn(refer('items'), (b) { - b.addExpression(refer('print').call([refer('item')])); - }); + 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(''' + equalsDart('[for (final x in items) x, else shouldNotChain, ]'), + ); + }); + }, + ); + + group('expression loop 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([])); - }); + test('should build if statement with ifThen', () { + final tree = refer('isTrue').ifThen((b) { + b.addExpression(refer('execute').call([])); + }); - expect( - tree, - equalsDart(''' + expect( + tree, + equalsDart(''' if (isTrue) { execute(); }'''), - ); + ); + }); + + 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?')])); }); - 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(''' + expect( + tree, + equalsDart(''' if (1 == 2) { print('Bad'); } else if (2 == 2) { @@ -372,8 +553,7 @@ if (1 == 2) { } else { print('What?'); }'''), - ); - }); - }, - ); + ); + }); + }); } diff --git a/pkgs/code_builder/test/specs/control_test.dart b/pkgs/code_builder/test/specs/control_test.dart index d2ff4f97c..26d5831b9 100644 --- a/pkgs/code_builder/test/specs/control_test.dart +++ b/pkgs/code_builder/test/specs/control_test.dart @@ -457,29 +457,6 @@ if (a) { ); }); - test('should support orElseThrow', () { - final tree = IfTree((b) { - b - ..add(Condition((b) { - b - ..condition = refer('valid') - ..body.addExpression(refer('process').call([])); - })) - ..orElseThrow(refer('UnsupportedError') - .newInstance([literal('Invalid input')])); - }); - - expect( - tree, - equalsDart(''' -if (valid) { - process(); -} else { - throw UnsupportedError('Invalid input'); -}'''), - ); - }); - test('should support ifThen', () { final tree = IfTree((b) { b.ifThen((cond) { From da9b2540c230dacc1a29f4525293d3664cfb10d7 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Mon, 28 Jul 2025 19:48:00 -0500 Subject: [PATCH 12/17] update readme and delete temp files --- pkgs/code_builder/.gitignore | 3 +- pkgs/code_builder/.vscode/extensions.json | 8 -- pkgs/code_builder/.vscode/settings.json | 18 ----- pkgs/code_builder/CHANGELOG.md | 4 +- pkgs/code_builder/README.md | 92 +++++++++++++++++------ 5 files changed, 72 insertions(+), 53 deletions(-) delete mode 100644 pkgs/code_builder/.vscode/extensions.json delete mode 100644 pkgs/code_builder/.vscode/settings.json diff --git a/pkgs/code_builder/.gitignore b/pkgs/code_builder/.gitignore index f26b5966a..eeaaeced0 100644 --- a/pkgs/code_builder/.gitignore +++ b/pkgs/code_builder/.gitignore @@ -3,4 +3,5 @@ .packages .pub pubspec.lock -doc/api \ No newline at end of file +doc/api +coverage \ No newline at end of file diff --git a/pkgs/code_builder/.vscode/extensions.json b/pkgs/code_builder/.vscode/extensions.json deleted file mode 100644 index 99e6778c5..000000000 --- a/pkgs/code_builder/.vscode/extensions.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "recommendations": [ - "streetsidesoftware.code-spell-checker", - "Dart-Code.dart-code", - "DavidAnson.vscode-markdownlint", - "GiancarloCode.built-value-snippets" - ] -} \ No newline at end of file diff --git a/pkgs/code_builder/.vscode/settings.json b/pkgs/code_builder/.vscode/settings.json deleted file mode 100644 index 7de15b732..000000000 --- a/pkgs/code_builder/.vscode/settings.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "cSpell.words": [ - "controlflow", - "dartfmt", - "endtemplate", - "Gitter", - "Nikolas", - "Rimikis" - ], - "cSpell.ignoreWords": [ - "Substract" - ], - "markdownlint.config": { - "MD024": { - "siblings_only": true - } - } -} \ No newline at end of file diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index 4135cec43..9ff26bf51 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -1,5 +1,3 @@ - - ## 4.11.0-wip * Upgrade `dart_style` and `source_gen` to remove `package:macros` dependency. @@ -53,6 +51,8 @@ * Add `SwitchStatement` and `SwitchStatementBuilder` for `switch` statements. * Add `Expression.wildcard` static constant for wildcard (`_`) expressions. +* Simplify usage examples on the README. + ## 4.10.1 * Require Dart `^3.5.0` diff --git a/pkgs/code_builder/README.md b/pkgs/code_builder/README.md index 17dc0897e..3ff069eb8 100644 --- a/pkgs/code_builder/README.md +++ b/pkgs/code_builder/README.md @@ -1,5 +1,3 @@ -# code_builder - [![Build Status](https://github.com/dart-lang/tools/actions/workflows/code_builder.yaml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/code_builder.yaml) [![Pub package](https://img.shields.io/pub/v/code_builder.svg)](https://pub.dev/packages/code_builder) [![package publisher](https://img.shields.io/pub/publisher/code_builder.svg)](https://pub.dev/packages/code_builder/publisher) @@ -7,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: + +```dart +final animal = Class((builder) => builder + ..name = 'Animal' + ..extend = refer('Organism') +); +``` -For example creating a class with a method: +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 { @@ -42,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: @@ -61,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; From 76e45608de4e8fa9c9c42e063543d118ff97849f Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Mon, 28 Jul 2025 19:48:00 -0500 Subject: [PATCH 13/17] update readme and delete temp files --- pkgs/code_builder/test/specs/code/expression_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/code_builder/test/specs/code/expression_test.dart b/pkgs/code_builder/test/specs/code/expression_test.dart index 2bfd2b241..a36e27a0c 100644 --- a/pkgs/code_builder/test/specs/code/expression_test.dart +++ b/pkgs/code_builder/test/specs/code/expression_test.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2017, 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. From 25f0368f8b162ecd3008963ec4adb6f8f3c863eb Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Tue, 29 Jul 2025 13:12:18 -0500 Subject: [PATCH 14/17] allow `catch` omission and update defaults --- .../code_builder/lib/src/specs/control.g.dart | 11 ++--- .../lib/src/specs/control/handling.dart | 30 ++++++++++---- .../lib/src/specs/expression/control.dart | 8 ++-- .../test/specs/code/control_test.dart | 8 ++++ .../code_builder/test/specs/control_test.dart | 41 +++++++++++++++---- 5 files changed, 73 insertions(+), 25 deletions(-) diff --git a/pkgs/code_builder/lib/src/specs/control.g.dart b/pkgs/code_builder/lib/src/specs/control.g.dart index 45e235c94..96bb91e1d 100644 --- a/pkgs/code_builder/lib/src/specs/control.g.dart +++ b/pkgs/code_builder/lib/src/specs/control.g.dart @@ -975,7 +975,7 @@ class _$CatchBlock extends CatchBlock { @override final Reference? type; @override - final String exception; + final String? exception; @override final String? stacktrace; @override @@ -985,7 +985,7 @@ class _$CatchBlock extends CatchBlock { (CatchBlockBuilder()..update(updates))._build(); _$CatchBlock._( - {this.type, required this.exception, this.stacktrace, required this.body}) + {this.type, this.exception, this.stacktrace, required this.body}) : super._(); @override CatchBlock rebuild(void Function(CatchBlockBuilder) updates) => @@ -1045,9 +1045,7 @@ class CatchBlockBuilder implements Builder { BlockBuilder get body => _$this._body ??= BlockBuilder(); set body(BlockBuilder? body) => _$this._body = body; - CatchBlockBuilder() { - CatchBlock._initialize(this); - } + CatchBlockBuilder(); CatchBlockBuilder get _$this { final $v = _$v; @@ -1080,8 +1078,7 @@ class CatchBlockBuilder implements Builder { _$result = _$v ?? _$CatchBlock._( type: type, - exception: BuiltValueNullFieldError.checkNotNull( - exception, r'CatchBlock', 'exception'), + exception: exception, stacktrace: stacktrace, body: body.build(), ); diff --git a/pkgs/code_builder/lib/src/specs/control/handling.dart b/pkgs/code_builder/lib/src/specs/control/handling.dart index f9dd60b30..9bb46f35a 100644 --- a/pkgs/code_builder/lib/src/specs/control/handling.dart +++ b/pkgs/code_builder/lib/src/specs/control/handling.dart @@ -15,19 +15,29 @@ abstract class 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 name of the exception parameter (default: `e`). + /// 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; + String? get exception; /// The optional name of the stacktrace parameter. /// @@ -40,15 +50,19 @@ abstract class CatchBlock String? get stacktrace; ControlExpression get _catch => - ControlExpression.catchStatement(exception, stacktrace); + ControlExpression.catchStatement(exception ?? '_', stacktrace); @override - ControlExpression get _expression => - type == null ? _catch : ControlExpression.onStatement(type!, _catch); + 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!); + } - /// Set the default value of [exception] - @BuiltValueHook(initializeBuilder: true) - static void _initialize(CatchBlockBuilder builder) => builder.exception = 'e'; + return ControlExpression.onStatement(type!, _catch); + } } /// Represents a `try` or `finally` block. diff --git a/pkgs/code_builder/lib/src/specs/expression/control.dart b/pkgs/code_builder/lib/src/specs/expression/control.dart index d3d8a9b0f..363bc02f4 100644 --- a/pkgs/code_builder/lib/src/specs/expression/control.dart +++ b/pkgs/code_builder/lib/src/specs/expression/control.dart @@ -84,10 +84,12 @@ class ControlExpression extends Expression { body: [refer(error), if (stacktrace != null) refer(stacktrace)], separator: ','); - factory ControlExpression.onStatement( - Reference type, ControlExpression statement) => + factory ControlExpression.onStatement(Reference type, + [ControlExpression? statement]) => ControlExpression('on', - body: [type, statement], parenthesised: false, separator: ''); + body: [type, if (statement != null) statement], + parenthesised: false, + separator: ''); static const finallyStatement = ControlExpression('finally'); diff --git a/pkgs/code_builder/test/specs/code/control_test.dart b/pkgs/code_builder/test/specs/code/control_test.dart index e9a36cf02..db8522bcb 100644 --- a/pkgs/code_builder/test/specs/code/control_test.dart +++ b/pkgs/code_builder/test/specs/code/control_test.dart @@ -211,6 +211,14 @@ void main() { 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'), diff --git a/pkgs/code_builder/test/specs/control_test.dart b/pkgs/code_builder/test/specs/control_test.dart index 26d5831b9..7cf601aef 100644 --- a/pkgs/code_builder/test/specs/control_test.dart +++ b/pkgs/code_builder/test/specs/control_test.dart @@ -473,7 +473,7 @@ if (a) { group('catch block', () { test('should emit catch with default exception name', () { final catchBlock = CatchBlock((b) => b..body.addExpression(literal(1))); - expect(catchBlock, equalsDart('catch (e) {\n 1;\n}')); + expect(catchBlock, equalsDart('catch (_) {\n 1;\n}')); }); test('should emit catch with custom exception name', () { @@ -491,15 +491,24 @@ if (a) { 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') - ..exception = 'e' ..stacktrace = 's' ..body.addExpression(refer('print').call([refer('e')]))); expect( catchBlock, - equalsDart('on FormatException catch (e, s) {\n print(e);\n}'), + equalsDart('on FormatException catch (_, s) {\n print(e);\n}'), ); }); }); @@ -522,12 +531,30 @@ if (a) { equalsDart(''' try { mightFail(); -} catch (e) { +} 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([])); @@ -579,7 +606,7 @@ try { handleFormat(); } on SocketException catch (e2) { handleSocket(); -} catch (e) { +} catch (_) { rethrow; }'''), ); @@ -597,7 +624,7 @@ try { expect(result, equalsDart(''' try { 0; -} catch (e) { +} catch (_) { 1; } ''')); @@ -614,7 +641,7 @@ try { expect(result, equalsDart(''' try { 0; -} catch (e) { +} catch (_) { 1; } finally { done; From 4c026062a74dacbdfb1b66ada5c4af9bbffb1094 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Tue, 29 Jul 2025 21:36:48 -0500 Subject: [PATCH 15/17] refactor branches * replace IfTree/Condition with Conditional and BranchBuilder * move `switch` logic to a dedicated file * add ifThenReturn to Expression --- pkgs/code_builder/CHANGELOG.md | 5 +- pkgs/code_builder/lib/code_builder.dart | 7 +- pkgs/code_builder/lib/src/specs/control.dart | 2 + .../code_builder/lib/src/specs/control.g.dart | 710 +++++++++--------- .../lib/src/specs/control/branches.dart | 503 +++---------- .../lib/src/specs/control/switch.dart | 283 +++++++ .../lib/src/specs/expression/control.dart | 27 +- .../test/specs/code/control_test.dart | 22 +- .../code_builder/test/specs/control_test.dart | 283 ++----- 9 files changed, 819 insertions(+), 1023 deletions(-) create mode 100644 pkgs/code_builder/lib/src/specs/control/switch.dart diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index 9ff26bf51..c4e1a7c34 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -12,6 +12,7 @@ * 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.) @@ -38,8 +39,8 @@ * Add `WhileLoop` and `WhileLoopBuilder` for `while` and `do-while` loops. * Support emitting `if` statements and `if`/`else if`/`else` trees. - * Add `Condition` and `ConditionBuilder` for single statements. - * Add `IfTree` and `IfTreeBuilder` for conditional 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. diff --git a/pkgs/code_builder/lib/code_builder.dart b/pkgs/code_builder/lib/code_builder.dart index 568cf04cf..63e39e09f 100644 --- a/pkgs/code_builder/lib/code_builder.dart +++ b/pkgs/code_builder/lib/code_builder.dart @@ -12,18 +12,17 @@ export 'src/specs/code.dart' export 'src/specs/constructor.dart' show Constructor, ConstructorBuilder; export 'src/specs/control.dart' show + BranchBuilder, Case, CaseBuilder, CatchBlock, CatchBlockBuilder, - Condition, - ConditionBuilder, + Conditional, + ConditionalBuilder, ForInLoop, ForInLoopBuilder, ForLoop, ForLoopBuilder, - IfTree, - IfTreeBuilder, SwitchExpression, SwitchExpressionBuilder, SwitchStatement, diff --git a/pkgs/code_builder/lib/src/specs/control.dart b/pkgs/code_builder/lib/src/specs/control.dart index 2b52aa8ba..bbfae9e07 100644 --- a/pkgs/code_builder/lib/src/specs/control.dart +++ b/pkgs/code_builder/lib/src/specs/control.dart @@ -4,6 +4,7 @@ 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'; @@ -17,6 +18,7 @@ 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. /// diff --git a/pkgs/code_builder/lib/src/specs/control.g.dart b/pkgs/code_builder/lib/src/specs/control.g.dart index 96bb91e1d..d98dbdc70 100644 --- a/pkgs/code_builder/lib/src/specs/control.g.dart +++ b/pkgs/code_builder/lib/src/specs/control.g.dart @@ -420,27 +420,27 @@ class WhileLoopBuilder implements Builder { } } -class _$Condition extends Condition { +class _$Branch extends Branch { @override final Expression? condition; @override final Block body; - factory _$Condition([void Function(ConditionBuilder)? updates]) => - (ConditionBuilder()..update(updates)).build() as _$Condition; + factory _$Branch([void Function(BranchBuilder)? updates]) => + (BranchBuilder()..update(updates)).build() as _$Branch; - _$Condition._({this.condition, required this.body}) : super._(); + _$Branch._({this.condition, required this.body}) : super._(); @override - Condition rebuild(void Function(ConditionBuilder) updates) => + Branch rebuild(void Function(BranchBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$ConditionBuilder toBuilder() => _$ConditionBuilder()..replace(this); + _$BranchBuilder toBuilder() => _$BranchBuilder()..replace(this); @override bool operator ==(Object other) { if (identical(other, this)) return true; - return other is Condition && + return other is Branch && condition == other.condition && body == other.body; } @@ -456,15 +456,15 @@ class _$Condition extends Condition { @override String toString() { - return (newBuiltValueToStringHelper(r'Condition') + return (newBuiltValueToStringHelper(r'Branch') ..add('condition', condition) ..add('body', body)) .toString(); } } -class _$ConditionBuilder extends ConditionBuilder { - _$Condition? _$v; +class _$BranchBuilder extends BranchBuilder { + _$Branch? _$v; @override Expression? get condition { @@ -490,9 +490,9 @@ class _$ConditionBuilder extends ConditionBuilder { super.body = body; } - _$ConditionBuilder() : super._(); + _$BranchBuilder() : super._(); - ConditionBuilder get _$this { + BranchBuilder get _$this { final $v = _$v; if ($v != null) { super.condition = $v.condition; @@ -503,23 +503,23 @@ class _$ConditionBuilder extends ConditionBuilder { } @override - void replace(Condition other) { - _$v = other as _$Condition; + void replace(Branch other) { + _$v = other as _$Branch; } @override - void update(void Function(ConditionBuilder)? updates) { + void update(void Function(BranchBuilder)? updates) { if (updates != null) updates(this); } @override - Condition build() => _build(); + Branch build() => _build(); - _$Condition _build() { - _$Condition _$result; + _$Branch _build() { + _$Branch _$result; try { _$result = _$v ?? - _$Condition._( + _$Branch._( condition: condition, body: body.build(), ); @@ -530,7 +530,7 @@ class _$ConditionBuilder extends ConditionBuilder { body.build(); } catch (e) { throw BuiltValueNestedFieldError( - r'Condition', _$failedField, e.toString()); + r'Branch', _$failedField, e.toString()); } rethrow; } @@ -539,97 +539,97 @@ class _$ConditionBuilder extends ConditionBuilder { } } -class _$IfTree extends IfTree { +class _$Conditional extends Conditional { @override - final BuiltList blocks; + final BuiltList branches; - factory _$IfTree([void Function(IfTreeBuilder)? updates]) => - (IfTreeBuilder()..update(updates)).build() as _$IfTree; + factory _$Conditional([void Function(ConditionalBuilder)? updates]) => + (ConditionalBuilder()..update(updates)).build() as _$Conditional; - _$IfTree._({required this.blocks}) : super._(); + _$Conditional._({required this.branches}) : super._(); @override - IfTree rebuild(void Function(IfTreeBuilder) updates) => + Conditional rebuild(void Function(ConditionalBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$IfTreeBuilder toBuilder() => _$IfTreeBuilder()..replace(this); + _$ConditionalBuilder toBuilder() => _$ConditionalBuilder()..replace(this); @override bool operator ==(Object other) { if (identical(other, this)) return true; - return other is IfTree && blocks == other.blocks; + return other is Conditional && branches == other.branches; } @override int get hashCode { var _$hash = 0; - _$hash = $jc(_$hash, blocks.hashCode); + _$hash = $jc(_$hash, branches.hashCode); _$hash = $jf(_$hash); return _$hash; } @override String toString() { - return (newBuiltValueToStringHelper(r'IfTree')..add('blocks', blocks)) + return (newBuiltValueToStringHelper(r'Conditional') + ..add('branches', branches)) .toString(); } } -class _$IfTreeBuilder extends IfTreeBuilder { - _$IfTree? _$v; +class _$ConditionalBuilder extends ConditionalBuilder { + _$Conditional? _$v; @override - ListBuilder get blocks { + ListBuilder get branches { _$this; - return super.blocks; + return super.branches; } @override - set blocks(ListBuilder blocks) { + set branches(ListBuilder branches) { _$this; - super.blocks = blocks; + super.branches = branches; } - _$IfTreeBuilder() : super._(); + _$ConditionalBuilder() : super._(); - IfTreeBuilder get _$this { + ConditionalBuilder get _$this { final $v = _$v; if ($v != null) { - super.blocks = $v.blocks.toBuilder(); + super.branches = $v.branches.toBuilder(); _$v = null; } return this; } @override - void replace(IfTree other) { - _$v = other as _$IfTree; + void replace(Conditional other) { + _$v = other as _$Conditional; } @override - void update(void Function(IfTreeBuilder)? updates) { + void update(void Function(ConditionalBuilder)? updates) { if (updates != null) updates(this); } @override - IfTree build() => _build(); + Conditional build() => _build(); - _$IfTree _build() { - IfTree._build(this); - _$IfTree _$result; + _$Conditional _build() { + _$Conditional _$result; try { _$result = _$v ?? - _$IfTree._( - blocks: blocks.build(), + _$Conditional._( + branches: branches.build(), ); } catch (_) { late String _$failedField; try { - _$failedField = 'blocks'; - blocks.build(); + _$failedField = 'branches'; + branches.build(); } catch (e) { throw BuiltValueNestedFieldError( - r'IfTree', _$failedField, e.toString()); + r'Conditional', _$failedField, e.toString()); } rethrow; } @@ -638,44 +638,45 @@ class _$IfTreeBuilder extends IfTreeBuilder { } } -class _$Case extends Case { +class _$CatchBlock extends CatchBlock { @override - final Expression pattern; + final Reference? type; @override - final Expression? guard; + final String? exception; @override - final String? label; + final String? stacktrace; @override - final T? body; + final Block body; - factory _$Case([void Function(CaseBuilder)? updates]) => - (CaseBuilder()..update(updates))._build(); + factory _$CatchBlock([void Function(CatchBlockBuilder)? updates]) => + (CatchBlockBuilder()..update(updates))._build(); - _$Case._({required this.pattern, this.guard, this.label, this.body}) + _$CatchBlock._( + {this.type, this.exception, this.stacktrace, required this.body}) : super._(); @override - Case rebuild(void Function(CaseBuilder) updates) => + CatchBlock rebuild(void Function(CatchBlockBuilder) updates) => (toBuilder()..update(updates)).build(); @override - CaseBuilder toBuilder() => CaseBuilder()..replace(this); + CatchBlockBuilder toBuilder() => CatchBlockBuilder()..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 && + 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, pattern.hashCode); - _$hash = $jc(_$hash, guard.hashCode); - _$hash = $jc(_$hash, label.hashCode); + _$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; @@ -683,175 +684,223 @@ class _$Case extends Case { @override String toString() { - return (newBuiltValueToStringHelper(r'Case') - ..add('pattern', pattern) - ..add('guard', guard) - ..add('label', label) + return (newBuiltValueToStringHelper(r'CatchBlock') + ..add('type', type) + ..add('exception', exception) + ..add('stacktrace', stacktrace) ..add('body', body)) .toString(); } } -class CaseBuilder implements Builder, CaseBuilder> { - _$Case? _$v; +class CatchBlockBuilder implements Builder { + _$CatchBlock? _$v; - Expression? _pattern; - Expression? get pattern => _$this._pattern; - set pattern(Expression? pattern) => _$this._pattern = pattern; + Reference? _type; + Reference? get type => _$this._type; + set type(Reference? type) => _$this._type = type; - Expression? _guard; - Expression? get guard => _$this._guard; - set guard(Expression? guard) => _$this._guard = guard; + String? _exception; + String? get exception => _$this._exception; + set exception(String? exception) => _$this._exception = exception; - String? _label; - String? get label => _$this._label; - set label(String? label) => _$this._label = label; + String? _stacktrace; + String? get stacktrace => _$this._stacktrace; + set stacktrace(String? stacktrace) => _$this._stacktrace = stacktrace; - T? _body; - T? get body => _$this._body; - set body(T? body) => _$this._body = body; + BlockBuilder? _body; + BlockBuilder get body => _$this._body ??= BlockBuilder(); + set body(BlockBuilder? body) => _$this._body = body; - CaseBuilder(); + CatchBlockBuilder(); - CaseBuilder get _$this { + CatchBlockBuilder get _$this { final $v = _$v; if ($v != null) { - _pattern = $v.pattern; - _guard = $v.guard; - _label = $v.label; - _body = $v.body; + _type = $v.type; + _exception = $v.exception; + _stacktrace = $v.stacktrace; + _body = $v.body.toBuilder(); _$v = null; } return this; } @override - void replace(Case other) { - _$v = other as _$Case; + void replace(CatchBlock other) { + _$v = other as _$CatchBlock; } @override - void update(void Function(CaseBuilder)? updates) { + void update(void Function(CatchBlockBuilder)? updates) { if (updates != null) updates(this); } @override - Case build() => _build(); + CatchBlock build() => _build(); - _$Case _build() { - final _$result = _$v ?? - _$Case._( - pattern: BuiltValueNullFieldError.checkNotNull( - pattern, r'Case', 'pattern'), - guard: guard, - label: label, - body: body, - ); + _$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 _$SwitchStatement extends SwitchStatement { +class _$TryCatch extends TryCatch { @override - final Expression value; + final Block body; @override - final BuiltList> cases; + final BuiltList handlers; + @override + final Block? handleAll; - factory _$SwitchStatement([void Function(SwitchStatementBuilder)? updates]) => - (SwitchStatementBuilder()..update(updates))._build(); + factory _$TryCatch([void Function(TryCatchBuilder)? updates]) => + (TryCatchBuilder()..update(updates)).build() as _$TryCatch; - _$SwitchStatement._({required this.value, required this.cases}) : super._(); + _$TryCatch._({required this.body, required this.handlers, this.handleAll}) + : super._(); @override - SwitchStatement rebuild(void Function(SwitchStatementBuilder) updates) => + TryCatch rebuild(void Function(TryCatchBuilder) updates) => (toBuilder()..update(updates)).build(); @override - SwitchStatementBuilder toBuilder() => SwitchStatementBuilder()..replace(this); + _$TryCatchBuilder toBuilder() => _$TryCatchBuilder()..replace(this); @override bool operator ==(Object other) { if (identical(other, this)) return true; - return other is SwitchStatement && - value == other.value && - cases == other.cases; + return other is TryCatch && + body == other.body && + handlers == other.handlers && + handleAll == other.handleAll; } @override int get hashCode { var _$hash = 0; - _$hash = $jc(_$hash, value.hashCode); - _$hash = $jc(_$hash, cases.hashCode); + _$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'SwitchStatement') - ..add('value', value) - ..add('cases', cases)) + return (newBuiltValueToStringHelper(r'TryCatch') + ..add('body', body) + ..add('handlers', handlers) + ..add('handleAll', handleAll)) .toString(); } } -class SwitchStatementBuilder - implements - Builder, - SwitchBuilder { - _$SwitchStatement? _$v; +class _$TryCatchBuilder extends TryCatchBuilder { + _$TryCatch? _$v; - Expression? _value; - Expression? get value => _$this._value; - set value(covariant Expression? value) => _$this._value = value; + @override + BlockBuilder get body { + _$this; + return super.body; + } - ListBuilder>? _cases; - ListBuilder> get cases => - _$this._cases ??= ListBuilder>(); - set cases(covariant ListBuilder>? cases) => _$this._cases = cases; + @override + set body(BlockBuilder body) { + _$this; + super.body = body; + } - SwitchStatementBuilder(); + @override + ListBuilder get handlers { + _$this; + return super.handlers; + } - SwitchStatementBuilder get _$this { + @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) { - _value = $v.value; - _cases = $v.cases.toBuilder(); + super.body = $v.body.toBuilder(); + super.handlers = $v.handlers.toBuilder(); + super.handleAll = $v.handleAll?.toBuilder(); _$v = null; } return this; } @override - void replace(covariant SwitchStatement other) { - _$v = other as _$SwitchStatement; + void replace(TryCatch other) { + _$v = other as _$TryCatch; } @override - void update(void Function(SwitchStatementBuilder)? updates) { + void update(void Function(TryCatchBuilder)? updates) { if (updates != null) updates(this); } @override - SwitchStatement build() => _build(); + TryCatch build() => _build(); - _$SwitchStatement _build() { - _$SwitchStatement _$result; + _$TryCatch _build() { + TryCatch._build(this); + _$TryCatch _$result; try { _$result = _$v ?? - _$SwitchStatement._( - value: BuiltValueNullFieldError.checkNotNull( - value, r'SwitchStatement', 'value'), - cases: cases.build(), + _$TryCatch._( + body: body.build(), + handlers: handlers.build(), + handleAll: super.handleAll?.build(), ); } catch (_) { late String _$failedField; try { - _$failedField = 'cases'; - cases.build(); + _$failedField = 'body'; + body.build(); + _$failedField = 'handlers'; + handlers.build(); + _$failedField = 'handleAll'; + super.handleAll?.build(); } catch (e) { throw BuiltValueNestedFieldError( - r'SwitchStatement', _$failedField, e.toString()); + r'TryCatch', _$failedField, e.toString()); } rethrow; } @@ -860,236 +909,220 @@ class SwitchStatementBuilder } } -class _$SwitchExpression extends SwitchExpression { +class _$Case extends Case { @override - final Expression value; + final Expression pattern; @override - final BuiltList> cases; - - factory _$SwitchExpression( - [void Function(SwitchExpressionBuilder)? updates]) => - (SwitchExpressionBuilder()..update(updates))._build(); + final Expression? guard; + @override + final String? label; + @override + final T? body; - _$SwitchExpression._({required this.value, required this.cases}) : super._(); + factory _$Case([void Function(CaseBuilder)? updates]) => + (CaseBuilder()..update(updates))._build(); + + _$Case._({required this.pattern, this.guard, this.label, this.body}) + : super._(); @override - SwitchExpression rebuild(void Function(SwitchExpressionBuilder) updates) => + Case rebuild(void Function(CaseBuilder) updates) => (toBuilder()..update(updates)).build(); @override - SwitchExpressionBuilder toBuilder() => - SwitchExpressionBuilder()..replace(this); + CaseBuilder toBuilder() => CaseBuilder()..replace(this); @override bool operator ==(Object other) { if (identical(other, this)) return true; - return other is SwitchExpression && - value == other.value && - cases == other.cases; + 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, value.hashCode); - _$hash = $jc(_$hash, cases.hashCode); + _$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'SwitchExpression') - ..add('value', value) - ..add('cases', cases)) + return (newBuiltValueToStringHelper(r'Case') + ..add('pattern', pattern) + ..add('guard', guard) + ..add('label', label) + ..add('body', body)) .toString(); } } -class SwitchExpressionBuilder - implements - Builder, - SwitchBuilder { - _$SwitchExpression? _$v; +class CaseBuilder implements Builder, CaseBuilder> { + _$Case? _$v; - Expression? _value; - Expression? get value => _$this._value; - set value(covariant Expression? value) => _$this._value = value; + Expression? _pattern; + Expression? get pattern => _$this._pattern; + set pattern(Expression? pattern) => _$this._pattern = pattern; - ListBuilder>? _cases; - ListBuilder> get cases => - _$this._cases ??= ListBuilder>(); - set cases(covariant ListBuilder>? cases) => - _$this._cases = cases; + Expression? _guard; + Expression? get guard => _$this._guard; + set guard(Expression? guard) => _$this._guard = guard; - SwitchExpressionBuilder(); + String? _label; + String? get label => _$this._label; + set label(String? label) => _$this._label = label; - SwitchExpressionBuilder get _$this { + T? _body; + T? get body => _$this._body; + set body(T? body) => _$this._body = body; + + CaseBuilder(); + + CaseBuilder get _$this { final $v = _$v; if ($v != null) { - _value = $v.value; - _cases = $v.cases.toBuilder(); + _pattern = $v.pattern; + _guard = $v.guard; + _label = $v.label; + _body = $v.body; _$v = null; } return this; } @override - void replace(covariant SwitchExpression other) { - _$v = other as _$SwitchExpression; + void replace(Case other) { + _$v = other as _$Case; } @override - void update(void Function(SwitchExpressionBuilder)? updates) { + void update(void Function(CaseBuilder)? updates) { if (updates != null) updates(this); } @override - SwitchExpression build() => _build(); + Case 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; - } + _$Case _build() { + final _$result = _$v ?? + _$Case._( + pattern: BuiltValueNullFieldError.checkNotNull( + pattern, r'Case', 'pattern'), + guard: guard, + label: label, + body: body, + ); replace(_$result); return _$result; } } -class _$CatchBlock extends CatchBlock { - @override - final Reference? type; - @override - final String? exception; +class _$SwitchStatement extends SwitchStatement { @override - final String? stacktrace; + final Expression value; @override - final Block body; + final BuiltList> cases; - factory _$CatchBlock([void Function(CatchBlockBuilder)? updates]) => - (CatchBlockBuilder()..update(updates))._build(); + factory _$SwitchStatement([void Function(SwitchStatementBuilder)? updates]) => + (SwitchStatementBuilder()..update(updates))._build(); - _$CatchBlock._( - {this.type, this.exception, this.stacktrace, required this.body}) - : super._(); + _$SwitchStatement._({required this.value, required this.cases}) : super._(); @override - CatchBlock rebuild(void Function(CatchBlockBuilder) updates) => + SwitchStatement rebuild(void Function(SwitchStatementBuilder) updates) => (toBuilder()..update(updates)).build(); @override - CatchBlockBuilder toBuilder() => CatchBlockBuilder()..replace(this); + SwitchStatementBuilder toBuilder() => SwitchStatementBuilder()..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; + return other is SwitchStatement && + value == other.value && + cases == other.cases; } @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 = $jc(_$hash, value.hashCode); + _$hash = $jc(_$hash, cases.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)) + return (newBuiltValueToStringHelper(r'SwitchStatement') + ..add('value', value) + ..add('cases', cases)) .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; +class SwitchStatementBuilder + implements + Builder, + SwitchBuilder { + _$SwitchStatement? _$v; - String? _stacktrace; - String? get stacktrace => _$this._stacktrace; - set stacktrace(String? stacktrace) => _$this._stacktrace = stacktrace; + Expression? _value; + Expression? get value => _$this._value; + set value(covariant Expression? value) => _$this._value = value; - BlockBuilder? _body; - BlockBuilder get body => _$this._body ??= BlockBuilder(); - set body(BlockBuilder? body) => _$this._body = body; + ListBuilder>? _cases; + ListBuilder> get cases => + _$this._cases ??= ListBuilder>(); + set cases(covariant ListBuilder>? cases) => _$this._cases = cases; - CatchBlockBuilder(); + SwitchStatementBuilder(); - CatchBlockBuilder get _$this { + SwitchStatementBuilder get _$this { final $v = _$v; if ($v != null) { - _type = $v.type; - _exception = $v.exception; - _stacktrace = $v.stacktrace; - _body = $v.body.toBuilder(); + _value = $v.value; + _cases = $v.cases.toBuilder(); _$v = null; } return this; } @override - void replace(CatchBlock other) { - _$v = other as _$CatchBlock; + void replace(covariant SwitchStatement other) { + _$v = other as _$SwitchStatement; } @override - void update(void Function(CatchBlockBuilder)? updates) { + void update(void Function(SwitchStatementBuilder)? updates) { if (updates != null) updates(this); } @override - CatchBlock build() => _build(); + SwitchStatement build() => _build(); - _$CatchBlock _build() { - _$CatchBlock _$result; + _$SwitchStatement _build() { + _$SwitchStatement _$result; try { _$result = _$v ?? - _$CatchBlock._( - type: type, - exception: exception, - stacktrace: stacktrace, - body: body.build(), + _$SwitchStatement._( + value: BuiltValueNullFieldError.checkNotNull( + value, r'SwitchStatement', 'value'), + cases: cases.build(), ); } catch (_) { late String _$failedField; try { - _$failedField = 'body'; - body.build(); + _$failedField = 'cases'; + cases.build(); } catch (e) { throw BuiltValueNestedFieldError( - r'CatchBlock', _$failedField, e.toString()); + r'SwitchStatement', _$failedField, e.toString()); } rethrow; } @@ -1098,142 +1131,109 @@ class CatchBlockBuilder implements Builder { } } -class _$TryCatch extends TryCatch { - @override - final Block body; +class _$SwitchExpression extends SwitchExpression { @override - final BuiltList handlers; + final Expression value; @override - final Block? handleAll; + final BuiltList> cases; - factory _$TryCatch([void Function(TryCatchBuilder)? updates]) => - (TryCatchBuilder()..update(updates)).build() as _$TryCatch; + factory _$SwitchExpression( + [void Function(SwitchExpressionBuilder)? updates]) => + (SwitchExpressionBuilder()..update(updates))._build(); - _$TryCatch._({required this.body, required this.handlers, this.handleAll}) - : super._(); + _$SwitchExpression._({required this.value, required this.cases}) : super._(); @override - TryCatch rebuild(void Function(TryCatchBuilder) updates) => + SwitchExpression rebuild(void Function(SwitchExpressionBuilder) updates) => (toBuilder()..update(updates)).build(); @override - _$TryCatchBuilder toBuilder() => _$TryCatchBuilder()..replace(this); + SwitchExpressionBuilder toBuilder() => + SwitchExpressionBuilder()..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; + return other is SwitchExpression && + value == other.value && + cases == other.cases; } @override int get hashCode { var _$hash = 0; - _$hash = $jc(_$hash, body.hashCode); - _$hash = $jc(_$hash, handlers.hashCode); - _$hash = $jc(_$hash, handleAll.hashCode); + _$hash = $jc(_$hash, value.hashCode); + _$hash = $jc(_$hash, cases.hashCode); _$hash = $jf(_$hash); return _$hash; } @override String toString() { - return (newBuiltValueToStringHelper(r'TryCatch') - ..add('body', body) - ..add('handlers', handlers) - ..add('handleAll', handleAll)) + return (newBuiltValueToStringHelper(r'SwitchExpression') + ..add('value', value) + ..add('cases', cases)) .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; - } +class SwitchExpressionBuilder + implements + Builder, + SwitchBuilder { + _$SwitchExpression? _$v; - @override - BlockBuilder get handleAll { - _$this; - return super.handleAll ??= BlockBuilder(); - } + Expression? _value; + Expression? get value => _$this._value; + set value(covariant Expression? value) => _$this._value = value; - @override - set handleAll(BlockBuilder? handleAll) { - _$this; - super.handleAll = handleAll; - } + ListBuilder>? _cases; + ListBuilder> get cases => + _$this._cases ??= ListBuilder>(); + set cases(covariant ListBuilder>? cases) => + _$this._cases = cases; - _$TryCatchBuilder() : super._(); + SwitchExpressionBuilder(); - TryCatchBuilder get _$this { + SwitchExpressionBuilder get _$this { final $v = _$v; if ($v != null) { - super.body = $v.body.toBuilder(); - super.handlers = $v.handlers.toBuilder(); - super.handleAll = $v.handleAll?.toBuilder(); + _value = $v.value; + _cases = $v.cases.toBuilder(); _$v = null; } return this; } @override - void replace(TryCatch other) { - _$v = other as _$TryCatch; + void replace(covariant SwitchExpression other) { + _$v = other as _$SwitchExpression; } @override - void update(void Function(TryCatchBuilder)? updates) { + void update(void Function(SwitchExpressionBuilder)? updates) { if (updates != null) updates(this); } @override - TryCatch build() => _build(); + SwitchExpression build() => _build(); - _$TryCatch _build() { - TryCatch._build(this); - _$TryCatch _$result; + _$SwitchExpression _build() { + _$SwitchExpression _$result; try { _$result = _$v ?? - _$TryCatch._( - body: body.build(), - handlers: handlers.build(), - handleAll: super.handleAll?.build(), + _$SwitchExpression._( + value: BuiltValueNullFieldError.checkNotNull( + value, r'SwitchExpression', 'value'), + cases: cases.build(), ); } catch (_) { late String _$failedField; try { - _$failedField = 'body'; - body.build(); - _$failedField = 'handlers'; - handlers.build(); - _$failedField = 'handleAll'; - super.handleAll?.build(); + _$failedField = 'cases'; + cases.build(); } catch (e) { throw BuiltValueNestedFieldError( - r'TryCatch', _$failedField, e.toString()); + r'SwitchExpression', _$failedField, e.toString()); } rethrow; } diff --git a/pkgs/code_builder/lib/src/specs/control/branches.dart b/pkgs/code_builder/lib/src/specs/control/branches.dart index 72faf815d..c15cac046 100644 --- a/pkgs/code_builder/lib/src/specs/control/branches.dart +++ b/pkgs/code_builder/lib/src/specs/control/branches.dart @@ -4,457 +4,138 @@ part of '../control.dart'; -/// Represents a single `if` block. +/// A [Conditional] branch. /// -/// Use [IfTree] to create a tree of `if`, `else if`, -/// and `else` statements. +/// Intentionally not exported to avoid confusion +/// with [Conditional]. /// -/// {@category controlFlow} -abstract class Condition - with ControlBlock - implements Built { - Condition._(); - factory Condition(void Function(ConditionBuilder block) builder) = - _$Condition; +@internal +abstract class Branch implements Built { + Branch._(); + factory Branch([void Function(BranchBuilder) updates]) = _$Branch; - /// The statement condition. - /// - /// Required if this is a standalone [Condition] or - /// the first in an [IfTree], otherwise optional. Expression? get condition; + Block get body; - ControlExpression? get _statement => - condition == null ? null : ControlExpression.ifStatement(condition!); + _Branch _buildable(bool isElse) => + _Branch(condition: condition, body: body, isElse: isElse); +} - @override - ControlExpression get _expression => - _statement ?? - (throw ArgumentError( - 'A condition must be provided with an `if` statement', 'condition')); +/// Builds a [Conditional] branch. +abstract class BranchBuilder implements Builder { + BranchBuilder._(); + factory BranchBuilder() = _$BranchBuilder; - /// This condition as an `else` block. + /// The `if` statement condition. /// - /// Will be `else` if [condition] is `null`, - /// otherwise `else if`. - Condition get asElse => ElseCondition(this); - - /// Returns an [IfTree] with just this [Condition]. - IfTree get asTree => IfTree.of([this]); -} - -/// Builds a [Condition]. -/// -/// {@category controlFlow} -abstract class ConditionBuilder - implements Builder { - ConditionBuilder._(); - factory ConditionBuilder() = _$ConditionBuilder; + /// 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(); - Expression? condition; - /// Sets [condition] to an `if-case` expression. - /// - /// Uses [ControlFlow.ifCase] to create the expression. + /// Set [condition] to an `if-case` expression, matching [object] against + /// [pattern]. /// - /// The expression will take the form: - /// ```dart - /// object case pattern - /// ``` + /// Guard clause [guard] can also be specified. /// - /// Optionally set a guard (`when`) clause with [guard]: /// ```dart - /// object case pattern when guard + /// if (object case pattern) + /// if (object case pattern when guard) /// ``` /// - /// See https://dart.dev/language/branches#if-case + /// Equivalent to using [ControlFlow.ifCase]. void ifCase({ required Expression object, required Expression pattern, Expression? guard, - }) { - condition = - ControlFlow.ifCase(object: object, pattern: pattern, guard: guard); - } -} - -/// A [condition] preceded by `else` -@internal -@visibleForTesting -class ElseCondition extends _$Condition { - ElseCondition(Condition condition) - : super._(body: condition.body, condition: condition.condition); - - @override - ControlExpression get _expression => - ControlExpression.elseStatement(_statement); + }) => + condition = + ControlFlow.ifCase(object: object, pattern: pattern, guard: guard); } -/// Represents an `if`/`else` tree. -/// -/// The first [Condition] in [blocks] will be treated as an `if` -/// block. All subsequent conditions will be treated as `else` blocks -/// using [Condition.asElse]. -/// -/// {@category controlFlow} -abstract class IfTree with ControlTree implements Built { - IfTree._(); - - /// Build an [IfTree] - factory IfTree(void Function(IfTreeBuilder tree) builder) = _$IfTree; - - /// Create an [IfTree] from a list of [conditions]. - factory IfTree.of(Iterable conditions) => IfTree( - (tree) { - tree.addAll(conditions); - }, - ); - - /// Called when an [IfTreeBuilder] is built. - /// - /// Replaces all but the first block with an [ElseCondition] - /// - @BuiltValueHook(finalizeBuilder: true) - static void _build(IfTreeBuilder builder) { - if (builder.blocks.isEmpty) return; +/// Buildable version of [Branch] +class _Branch extends _$Branch with ControlBlock { + final bool isElse; - final first = builder.blocks.first; - builder.blocks - ..skip(1) - ..map((b) => b.asElse) - ..insert(0, first); - } + _Branch({required super.condition, required super.body, required this.isElse}) + : super._(); - BuiltList get blocks; + ControlExpression? get _condition => + condition == null ? null : ControlExpression.ifStatement(condition!); @override - List get _blocks => blocks.toList(); - - /// Returns a new [IfTree] with [condition] added. - IfTree withCondition(Condition condition) => - (toBuilder()..add(condition)).build(); - - /// Builds a [Condition] with [builder] and returns - /// a new [IfTree] with it added. - IfTree elseIf(void Function(ConditionBuilder block) builder) => - withCondition((ConditionBuilder()..update(builder)).build()); - - /// Builds a block with [builder] and returns a new [IfTree] - /// with it added as an `else` [Condition]. - IfTree orElse(void Function(BlockBuilder body) builder) => elseIf( - (block) { - builder(block.body); - }, - ); -} - -/// Builds an [IfTree]. -/// -/// {@category controlFlow} -abstract class IfTreeBuilder implements Builder { - IfTreeBuilder._(); - factory IfTreeBuilder() = _$IfTreeBuilder; - - /// The items in this tree. - ListBuilder blocks = ListBuilder(); - - /// Build a [Condition] with [builder] and add it to the tree. - /// - /// Shorthand for calling `add` and creating a condition - void ifThen(void Function(ConditionBuilder block) builder) => - add((ConditionBuilder()..update(builder)).build()); - - /// Add a [Condition] to the tree. - /// - /// Shorthand for `blocks.add` - void add(Condition condition) => blocks.add(condition); - - /// Add multiple [Condition]s to the tree. - /// - /// Shorthand for `blocks.addAll` - void addAll(Iterable conditions) => blocks.addAll(conditions); - - /// Builds a block using [builder] and adds it to the tree - /// as an `else` [Condition]. - /// - /// Shorthand for calling [add] and creating an `else` condition. - void orElse(void Function(BlockBuilder body) builder) => add(Condition( - (block) { - builder(block.body); - }, - )); + 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'); + } } -/// 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]. +/// Represents a conditional (`if`/`else`) tree. /// -/// ```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, -/// ``` +/// The first added branch will be treated as an `if` block, with +/// all subsequent conditions being treated as `else`. /// /// {@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; +abstract class Conditional + with ControlTree + implements Built { + Conditional._(); - /// Whether or not to use the `default` keyword - bool get _default => false; + /// Build an [Conditional] + factory Conditional(void Function(ConditionalBuilder tree) builder) = + _$Conditional; - /// 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); + @protected + @visibleForTesting + BuiltList get branches; @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; - - // non-abstract so that built_value ignores it - /// Convert generic [Case] into an implementation-specific buildable - /// subtype. - Iterable get _cases => - throw UnsupportedError('Must be implemented by subclasses'); + 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)); } -/// **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. +/// Builds a [Conditional]. /// -/// `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. +/// The first added branch will be treated as an `if` block, with +/// all subsequent conditions being treated as `else`. /// -/// ```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._(); +/// {@category controlFlow} +abstract class ConditionalBuilder + implements Builder { + ConditionalBuilder._(); + factory ConditionalBuilder() = _$ConditionalBuilder; - /// Build a [SwitchExpression]. - factory SwitchExpression([void Function(SwitchExpressionBuilder) updates]) = - _$SwitchExpression; + /// The items in this tree. + ListBuilder branches = ListBuilder(); - @override - T accept(covariant ControlBlockVisitor visitor, [T? context]) => - visitor.visitSwitch(this, context); + /// 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)); - @override - Iterable get _cases => cases.map(CaseExpression._); + /// 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/switch.dart b/pkgs/code_builder/lib/src/specs/control/switch.dart new file mode 100644 index 000000000..f59519c73 --- /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/expression/control.dart b/pkgs/code_builder/lib/src/specs/expression/control.dart index 363bc02f4..99147e9bc 100644 --- a/pkgs/code_builder/lib/src/specs/expression/control.dart +++ b/pkgs/code_builder/lib/src/specs/expression/control.dart @@ -174,8 +174,7 @@ extension ControlFlow on Expression { ..variable = this ..body.update(builder)); - /// Build this expression into an `if` [Condition] and add it - /// to a new [IfTree]. + /// Build this expression into a [Conditional] /// /// ```dart /// if (this) { @@ -183,8 +182,9 @@ extension ControlFlow on Expression { /// } /// ``` /// - /// Chain [IfTree.elseIf] and [IfTree.orElse] to easily create a full - /// `if-elseif-else` tree: + /// Chain [Conditional.elseIf] and [Conditional.orElse] to + /// easily create a full `if-elseif-else` tree: + /// /// ```dart /// literal(1) /// .equalTo(literal(2)) @@ -211,11 +211,20 @@ extension ControlFlow on Expression { /// print('What?'); /// } /// ``` - IfTree ifThen(void Function(BlockBuilder body) builder) => Condition( - (block) => block - ..condition = this - ..body.update(builder), - ).asTree; + 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'); diff --git a/pkgs/code_builder/test/specs/code/control_test.dart b/pkgs/code_builder/test/specs/code/control_test.dart index db8522bcb..f904f19c3 100644 --- a/pkgs/code_builder/test/specs/code/control_test.dart +++ b/pkgs/code_builder/test/specs/code/control_test.dart @@ -483,7 +483,7 @@ void main() { }, ); - group('expression loop helpers', () { + group('expression helpers', () { test('should build a while loop with loopWhile', () { final expr = refer('isRunning').loopWhile((b) { b.addExpression(refer('tick').call([])); @@ -540,6 +540,26 @@ if (isTrue) { ); }); + 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')])); diff --git a/pkgs/code_builder/test/specs/control_test.dart b/pkgs/code_builder/test/specs/control_test.dart index 7cf601aef..94ff5c2b5 100644 --- a/pkgs/code_builder/test/specs/control_test.dart +++ b/pkgs/code_builder/test/specs/control_test.dart @@ -146,134 +146,54 @@ void main() { }); }); - group('condition', () { - test('should emit a basic if condition', () { - final condition = Condition((b) { - b - ..condition = refer('x').equalTo(literal(0)) - ..body.addExpression(refer('print').call([literal('zero')])); - }); - - expect( - condition, - equalsDart('if (x == 0) {\n print(\'zero\');\n}'), - ); - - expect( - condition.asTree, - equalsDart('if (x == 0) {\n print(\'zero\');\n}'), - ); - }); - - test('should emit a condition with null body', () { - final condition = Condition((b) { - b.condition = literal(true); - }); - - expect( - condition, - equalsDart('if (true) {}'), - ); - }); - - test('should emit an else condition', () { - final original = Condition((b) { - b.body.addExpression(refer('print').call([literal('fallback')])); - }); - - final elseBlock = original.asElse; - - expect( - elseBlock, - equalsDart('else {\n print(\'fallback\');\n}'), - ); - }); - - test('should emit an else-if condition', () { - final original = Condition((b) { - b - ..condition = refer('value').greaterThan(literal(10)) - ..body.addExpression(refer('log').call([literal('big')])); - }); - - final elseIf = original.asElse; - - expect( - elseIf, - equalsDart('else if (value > 10) {\n log(\'big\');\n}'), - ); - }); - - test('should throw if condition is null', () { - expect( - () => Condition((b) {}).accept(DartEmitter()), - throwsArgumentError, - ); - }); - - test('should emit an if-case condition', () { - final condition = Condition((b) { - b.ifCase( - object: refer('value'), - pattern: refer('int'), - ); - b.body.addExpression(refer('print').call([literal('int')])); - }); + 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( - condition, - equalsDart('if (value case int) {\n print(\'int\');\n}'), + tree, + equalsDart('if (x == 1) {\n print(\'one\');\n}'), ); }); - test('should emit an if-case with guard clause', () { - final condition = Condition((b) { - b.ifCase( - object: refer('value'), - pattern: refer('int'), - guard: refer('value').greaterThan(literal(0)), - ); - b.body.addExpression(refer('print').call([literal('positive')])); - }); + 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( - condition, - equalsDart( - 'if (value case int when value > 0) {\n print(\'positive\');\n}'), + tree, + equalsDart('if (x case y) {\n return;\n}'), ); }); - }); - group('if tree', () { - test('should emit a single if block', () { - final tree = IfTree((b) { - b.add(Condition((b) { - b - ..condition = refer('x').equalTo(literal(1)) - ..body.addExpression(refer('print').call([literal('one')])); - })); - }); + 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 == 1) {\n print(\'one\');\n}'), + equalsDart('if (x case y when z) {\n return;\n}'), ); }); test('should emit if-else if-else chain', () { - final tree = IfTree((b) { - b - ..add(Condition((b) { + final tree = Conditional((tree) { + tree + ..add((b) { b ..condition = refer('x').equalTo(literal(1)) ..body.addExpression(refer('print').call([literal('one')])); - })) - ..add(Condition((b) { + }) + ..add((b) { b ..condition = refer('x').equalTo(literal(2)) ..body.addExpression(refer('print').call([literal('two')])); - })) - ..orElse((body) { + }) + ..addElse((body) { body.addExpression(refer('print').call([literal('other')])); }); }); @@ -291,62 +211,13 @@ if (x == 1) { ); }); - test('should support IfTree.of constructor', () { - final tree = IfTree.of([ - Condition((b) { - b - ..condition = refer('ready') - ..body.addExpression(refer('start').call([])); - }), - Condition((b) { - b.body.addExpression(refer('exit').call([])); - }), - ]); - - expect( - tree, - equalsDart(''' -if (ready) { - start(); -} else { - exit(); -}'''), - ); - }); - - test('should support withCondition', () { - final base = IfTree((b) { - b.add(Condition((b) { - b - ..condition = refer('a') - ..body.addExpression(refer('doA').call([])); - })); - }); - - final extended = base.withCondition(Condition((b) { - b - ..condition = refer('b') - ..body.addExpression(refer('doB').call([])); - })); - - expect( - extended, - equalsDart(''' -if (a) { - doA(); -} else if (b) { - doB(); -}'''), - ); - }); - test('should support elseIf', () { - final tree = IfTree((b) { - b.add(Condition((b) { + final tree = Conditional((tree) { + tree.add((b) { b ..condition = refer('loggedIn') ..body.addExpression(refer('showDashboard').call([])); - })); + }); }).elseIf((b) { b ..condition = refer('isGuest') @@ -365,12 +236,12 @@ if (loggedIn) { }); test('should support orElse', () { - final tree = IfTree((b) { - b.add(Condition((b) { + 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')])); }); @@ -386,88 +257,18 @@ if (ready) { ); }); - test('should support empty IfTree', () { - final tree = IfTree((b) {}); - expect(tree.blocks, isEmpty); - }); - }); - - group('if tree builder', () { - test('should support add', () { - final tree = IfTree((b) { - final condition = Condition((b) { - b - ..condition = refer('ok') - ..body.addExpression(refer('run').call([])); - }); - b.add(condition); - }); - - expect(tree, equalsDart('if (ok) {\n run();\n}')); - }); - - test('should support addAll', () { - final conditions = [ - Condition((b) { - b - ..condition = refer('x > 0') - ..body.addExpression(refer('handlePositive').call([])); - }), - Condition((b) { - b.body.addExpression(refer('handleZeroOrNegative').call([])); - }), - ]; - - final tree = IfTree((b) { - b.addAll(conditions); - }); - - expect( - tree, - equalsDart(''' -if (x > 0) { - handlePositive(); -} else { - handleZeroOrNegative(); -}'''), - ); - }); - - test('should support orElse', () { - final tree = IfTree((b) { - b - ..add(Condition((b) { - b - ..condition = refer('a') - ..body.addExpression(refer('doA').call([])); - })) - ..orElse((body) { - body.addExpression(refer('fallback').call([])); - }); - }); - - expect( - tree, - equalsDart(''' -if (a) { - doA(); -} else { - fallback(); -}'''), - ); - }); - - test('should support ifThen', () { - final tree = IfTree((b) { - b.ifThen((cond) { - cond - ..condition = refer('ready') - ..body.addExpression(refer('init').call([])); - }); - }); + test( + 'should throw an argument error', + () { + final tree = Conditional( + (tree) => tree.add( + (branch) {}, + ), + ); - expect(tree, equalsDart('if (ready) {\n init();\n}')); - }); + expect(() => tree.accept(DartEmitter()), throwsArgumentError); + }, + ); }); group('catch block', () { From 3435a07bf8da0d637b3213aee90fbd4bd44184d4 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Sat, 9 Aug 2025 12:21:14 -0500 Subject: [PATCH 16/17] - drop ignores - downgrade dev deps to work with sdk constraint --- pkgs/code_builder/.gitignore | 4 +--- pkgs/code_builder/pubspec.yaml | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkgs/code_builder/.gitignore b/pkgs/code_builder/.gitignore index eeaaeced0..ae7af3fc0 100644 --- a/pkgs/code_builder/.gitignore +++ b/pkgs/code_builder/.gitignore @@ -2,6 +2,4 @@ .dart_tool .packages .pub -pubspec.lock -doc/api -coverage \ No newline at end of file +pubspec.lock \ No newline at end of file diff --git a/pkgs/code_builder/pubspec.yaml b/pkgs/code_builder/pubspec.yaml index 987438fe7..302b03f41 100644 --- a/pkgs/code_builder/pubspec.yaml +++ b/pkgs/code_builder/pubspec.yaml @@ -15,10 +15,10 @@ dependencies: meta: ^1.3.0 dev_dependencies: - build: ^3.0.0 + build: ^2.5.4 build_runner: ^2.0.3 built_value_generator: ^8.0.0 dart_flutter_team_lints: ^3.0.0 dart_style: ^3.0.1 - source_gen: ^3.0.0 + source_gen: ^2.0.0 test: ^1.16.0 From ab72c8ba6085327852e2031e1cf0ebfdc8559375 Mon Sep 17 00:00:00 2001 From: Jason Mayer <72141247+one23four56@users.noreply.github.com> Date: Sun, 17 Aug 2025 20:51:02 -0500 Subject: [PATCH 17/17] downgrade `build` to 2.4.2 --- pkgs/code_builder/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/code_builder/pubspec.yaml b/pkgs/code_builder/pubspec.yaml index 302b03f41..d67a4de04 100644 --- a/pkgs/code_builder/pubspec.yaml +++ b/pkgs/code_builder/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: meta: ^1.3.0 dev_dependencies: - build: ^2.5.4 + build: ^2.4.2 build_runner: ^2.0.3 built_value_generator: ^8.0.0 dart_flutter_team_lints: ^3.0.0