├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── example_request.md │ └── feature_request.md └── workflows │ └── build.yml ├── .gitignore ├── README.md ├── packages ├── code_gen_tester │ ├── lib │ │ ├── code_gen_tester.dart │ │ └── src │ │ │ ├── analysis_utils.dart │ │ │ ├── annotation.dart │ │ │ └── tester.dart │ └── pubspec.yaml ├── functional_widget │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── build.yaml │ ├── example │ │ ├── analysis_options.yaml │ │ ├── build.yaml │ │ ├── lib │ │ │ ├── main.dart │ │ │ └── main.g.dart │ │ └── pubspec.yaml │ ├── lib │ │ ├── builder.dart │ │ ├── function_to_widget_class.dart │ │ └── src │ │ │ ├── parameters.dart │ │ │ └── utils.dart │ ├── pubspec.yaml │ └── test │ │ ├── decorator_test.dart │ │ ├── diagnostics_test.dart │ │ ├── fail_test.dart │ │ ├── parse_options_test.dart │ │ ├── src │ │ ├── diagnostics.dart │ │ ├── fail.dart │ │ ├── fake_flutter.dart │ │ └── success.dart │ │ └── success_test.dart └── functional_widget_annotation │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ ├── .gitignore │ ├── analysis_options.yaml │ ├── lib │ │ ├── main.dart │ │ └── main.g.dart │ └── pubspec.yaml │ ├── lib │ └── functional_widget_annotation.dart │ └── pubspec.yaml └── pubspec.yaml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: There is a problem in how provider behaves 4 | title: "" 5 | labels: bug, needs triage 6 | --- 7 | 8 | **Describe the bug** 9 | A clear and concise description of what the bug is. 10 | 11 | **To Reproduce** 12 | 13 | 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: I have a problem and I need help 4 | url: https://stackoverflow.com/questions/tagged/flutter -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/example_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation improvement request 3 | about: >- 4 | Suggest a new example/documentation or ask for clarification about an 5 | existing one. 6 | title: "" 7 | labels: documentation, needs triage 8 | --- 9 | 10 | **Describe what scenario you think is uncovered by the existing examples/articles** 11 | A clear and concise description of the problem that you want explained. 12 | 13 | **Describe why existing examples/articles do not cover this case** 14 | Explain which examples/articles you have seen before making this request, and 15 | why they did not help you with your problem. 16 | 17 | **Additional context** 18 | Add any other context or screenshots about the documentation request here. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: enhancement, needs triage 6 | --- 7 | 8 | **Is your feature request related to a problem? Please describe.** 9 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 10 | 11 | **Describe the solution you'd like** 12 | A clear and concise description of what you want to happen. 13 | 14 | **Describe alternatives you've considered** 15 | A clear and concise description of any alternative solutions or features you've considered. 16 | 17 | **Additional context** 18 | Add any other context or screenshots about the feature request here. 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | # runs the CI everyday at 10AM 8 | - cron: "0 10 * * *" 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | channel: 17 | - stable 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - uses: subosito/flutter-action@v2 23 | with: 24 | channel: ${{ matrix.channel }} 25 | 26 | - name: Add pub cache bin to PATH 27 | run: echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH 28 | 29 | - name: Add pub cache to PATH 30 | run: echo "PUB_CACHE="$HOME/.pub-cache"" >> $GITHUB_ENV 31 | 32 | - name: Install dependencies 33 | run: flutter pub get 34 | 35 | - name: Check format 36 | run: dart format --set-exit-if-changed . 37 | 38 | - name: Analyze 39 | run: flutter analyze 40 | 41 | - name: Run tests 42 | run: dart test -r github 43 | working-directory: packages/functional_widget 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter/Dart/Pub related 2 | **/doc/api/ 3 | .dart_tool/ 4 | .flutter-plugins 5 | .packages 6 | .pub-cache/ 7 | .pub/ 8 | build/ 9 | android/ 10 | ios/ 11 | 12 | # test_coverage generated main file 13 | .test_coverage.dart 14 | coverage/ 15 | 16 | # Misc 17 | *.iml 18 | .idea 19 | pubspec.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ./packages/functional_widget/README.md -------------------------------------------------------------------------------- /packages/code_gen_tester/lib/code_gen_tester.dart: -------------------------------------------------------------------------------- 1 | export 'package:code_gen_tester/src/tester.dart'; 2 | -------------------------------------------------------------------------------- /packages/code_gen_tester/lib/src/analysis_utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:build/build.dart'; 9 | import 'package:build_test/build_test.dart'; 10 | import 'package:path/path.dart' as p; 11 | import 'package:source_gen/source_gen.dart'; 12 | 13 | Future resolveCompilationUnit(String filePath) async { 14 | final assetId = AssetId.parse('a|lib/${p.basename(filePath)}'); 15 | final files = 16 | Directory(p.dirname(filePath)).listSync().whereType().toList(); 17 | 18 | final fileMap = Map.fromEntries(files.map( 19 | (f) => MapEntry('a|lib/${p.basename(f.path)}', f.readAsStringSync()))); 20 | 21 | final library = await resolveSources(fileMap, (item) async { 22 | return await item.libraryFor(assetId); 23 | }, resolverFor: 'a|lib/${p.basename(filePath)}'); 24 | 25 | return LibraryReader(library); 26 | } 27 | -------------------------------------------------------------------------------- /packages/code_gen_tester/lib/src/annotation.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | class ShouldThrow { 6 | final String errorMessage; 7 | final String? todo; 8 | const ShouldThrow(this.errorMessage, [this.todo]); 9 | } 10 | 11 | class ShouldGenerate { 12 | final String expectedOutput; 13 | final String? expectedWrappedOutput; 14 | final bool contains; 15 | final List expectedLogItems; 16 | final bool checked; 17 | const ShouldGenerate(this.expectedOutput, 18 | {this.expectedWrappedOutput, 19 | this.contains = false, 20 | this.expectedLogItems = const [], 21 | this.checked = false}); 22 | } 23 | 24 | // fake flutter 25 | class Widget {} 26 | 27 | class Container extends Widget {} 28 | -------------------------------------------------------------------------------- /packages/code_gen_tester/lib/src/tester.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:analyzer/dart/ast/ast.dart'; 5 | import 'package:analyzer/dart/element/element.dart'; 6 | import 'package:build/build.dart'; 7 | import 'package:code_gen_tester/src/analysis_utils.dart'; 8 | import 'package:crypto/crypto.dart'; 9 | import 'package:dart_style/dart_style.dart'; 10 | import 'package:glob/glob.dart'; 11 | import 'package:package_config/src/package_config.dart'; 12 | import 'package:pub_semver/pub_semver.dart'; 13 | import 'package:source_gen/source_gen.dart'; 14 | import 'package:test/test.dart'; 15 | 16 | Future expectGenerate( 17 | SourceGenTester tester, 18 | Generator generator, 19 | Matcher matcher, { 20 | BuildStep? buildStep, 21 | String? reason, 22 | dynamic skip, 23 | }) async { 24 | await expectLater( 25 | Future.microtask(() => tester.generateFor(generator, buildStep)), 26 | matcher, 27 | reason: reason, 28 | skip: skip, 29 | ); 30 | } 31 | 32 | Future expectGenerateNamed( 33 | SourceGenTester tester, 34 | String name, 35 | GeneratorForAnnotation generator, 36 | Matcher matcher, { 37 | BuildStep? buildStep, 38 | String? reason, 39 | dynamic skip, 40 | }) async { 41 | await expectLater( 42 | Future.microtask(() => tester.generateForName(generator, name, buildStep)), 43 | matcher, 44 | reason: reason, 45 | skip: skip, 46 | ); 47 | } 48 | 49 | final Map _cacheLibrary = {}; 50 | 51 | abstract class SourceGenTester { 52 | static Future fromPath(String path) async { 53 | var libraryReader = _cacheLibrary[path]; 54 | if (libraryReader == null) { 55 | libraryReader = await resolveCompilationUnit(path); 56 | _cacheLibrary[path] = libraryReader; 57 | } 58 | return _SourceGenTesterImpl(libraryReader); 59 | } 60 | 61 | Future generateFor(Generator generator, [BuildStep? buildStep]); 62 | Future generateForName(GeneratorForAnnotation generator, String name, 63 | [BuildStep? buildStep]); 64 | } 65 | 66 | class _SourceGenTesterImpl implements SourceGenTester { 67 | final LibraryReader library; 68 | final formatter = DartFormatter(languageVersion: Version(2, 12, 0)); 69 | 70 | _SourceGenTesterImpl(this.library); 71 | 72 | @override 73 | Future generateFor( 74 | Generator generator, [ 75 | BuildStep? buildStep, 76 | ]) async { 77 | final generated = 78 | await generator.generate(library, buildStep ?? _BuildStepImpl()); 79 | final output = formatter.format(generated!); 80 | printOnFailure(''' 81 | Generator ${generator.runtimeType} generated: 82 | ``` 83 | $output 84 | ``` 85 | '''); 86 | return output; 87 | } 88 | 89 | @override 90 | Future generateForName(GeneratorForAnnotation generator, String name, 91 | [BuildStep? buildStep]) async { 92 | final e = library 93 | .annotatedWith(generator.typeChecker) 94 | .firstWhere((e) => e.element.name == name); 95 | final dynamic generated = await generator.generateForAnnotatedElement( 96 | e.element, 97 | e.annotation, 98 | buildStep ?? _BuildStepImpl(), 99 | ); 100 | 101 | final output = formatter.format(generated.toString()); 102 | printOnFailure(''' 103 | Generator ${generator.runtimeType} generated: 104 | ``` 105 | $output 106 | ``` 107 | '''); 108 | return output; 109 | } 110 | } 111 | 112 | Matcher throwsInvalidGenerationSourceError([dynamic messageMatcher]) { 113 | var c = const TypeMatcher() 114 | .having((e) => e.element, 'element', isNotNull); 115 | 116 | if (messageMatcher != null) { 117 | c = c.having((e) => e.message, 'message', messageMatcher); 118 | } 119 | return throwsA(c); 120 | } 121 | 122 | // ignore: subtype_of_sealed_class 123 | class _BuildStepImpl implements BuildStep { 124 | @override 125 | AssetId get inputId => throw UnimplementedError(); 126 | 127 | @override 128 | Future get inputLibrary => throw UnimplementedError(); 129 | 130 | @override 131 | Resolver get resolver => _ResolverImpl(); 132 | 133 | @override 134 | Future canRead(AssetId id) { 135 | throw UnimplementedError(); 136 | } 137 | 138 | @override 139 | Future digest(AssetId id) { 140 | throw UnimplementedError(); 141 | } 142 | 143 | @override 144 | Future fetchResource(Resource resource) { 145 | throw UnimplementedError(); 146 | } 147 | 148 | @override 149 | Stream findAssets(Glob glob) { 150 | throw UnimplementedError(); 151 | } 152 | 153 | @override 154 | Future> readAsBytes(AssetId id) { 155 | throw UnimplementedError(); 156 | } 157 | 158 | @override 159 | Future readAsString(AssetId id, {Encoding encoding = utf8}) { 160 | throw UnimplementedError(); 161 | } 162 | 163 | @override 164 | void reportUnusedAssets(Iterable ids) {} 165 | 166 | @override 167 | T trackStage(String label, T Function() action, 168 | {bool isExternal = false}) { 169 | throw UnimplementedError(); 170 | } 171 | 172 | @override 173 | Future writeAsBytes(AssetId id, FutureOr> bytes) { 174 | throw UnimplementedError(); 175 | } 176 | 177 | @override 178 | Future writeAsString( 179 | AssetId id, 180 | FutureOr contents, { 181 | Encoding encoding = utf8, 182 | }) { 183 | throw UnimplementedError(); 184 | } 185 | 186 | @override 187 | Iterable get allowedOutputs => throw UnimplementedError(); 188 | 189 | @override 190 | Future get packageConfig => throw UnimplementedError(); 191 | } 192 | 193 | class _ResolverImpl implements Resolver { 194 | @override 195 | Future assetIdForElement(Element element) { 196 | throw UnimplementedError(); 197 | } 198 | 199 | @override 200 | Future astNodeFor(Element element, {bool resolve = false}) { 201 | return Future.error('UnimplementedError'); 202 | } 203 | 204 | @override 205 | Future compilationUnitFor(AssetId assetId, 206 | {bool allowSyntaxErrors = false}) { 207 | throw UnimplementedError(); 208 | } 209 | 210 | @override 211 | Future findLibraryByName(String libraryName) { 212 | throw UnimplementedError(); 213 | } 214 | 215 | @override 216 | Future isLibrary(AssetId assetId) { 217 | throw UnimplementedError(); 218 | } 219 | 220 | @override 221 | Stream get libraries => throw UnimplementedError(); 222 | 223 | @override 224 | Future libraryFor(AssetId assetId, 225 | {bool allowSyntaxErrors = false}) { 226 | throw UnimplementedError(); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /packages/code_gen_tester/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: code_gen_tester 2 | version: 0.0.1 3 | publish_to: none 4 | 5 | environment: 6 | sdk: ^3.6.0 7 | 8 | resolution: workspace 9 | 10 | dependencies: 11 | test: ^1.15.4 12 | dart_style: ^3.0.0 13 | pedantic: ^1.9.0 14 | build_runner: ^2.0.0 15 | build_test: ^2.0.0 16 | analyzer: ">=6.9.0 <8.0.0" 17 | build: ^2.0.0 18 | build_config: ^1.0.0 19 | meta: ^1.2.0 20 | functional_widget_annotation: 21 | source_gen: ^2.0.0 22 | code_builder: ^4.0.0 23 | -------------------------------------------------------------------------------- /packages/functional_widget/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.10.3 - 2025-03-24 2 | 3 | Upgrade dependencies (thanks to @qwezey) 4 | 5 | ## 0.10.2 - 2024-01-09 6 | 7 | Support analyzer 6.0.0 8 | 9 | ## 0.10.1 10 | 11 | Upgrade analyzer to ^5.2.0 12 | 13 | ## 0.10.0 14 | 15 | - Changed the logic for how the generated widget name is determined. (thanks to @doejon) 16 | Now, defining: 17 | 18 | ```dart 19 | @swidget 20 | Widget first() {} 21 | 22 | @swidget 23 | Widget _second() {} 24 | 25 | @swidget 26 | Widget __third() {} 27 | ``` 28 | 29 | generates: 30 | 31 | ```dart 32 | class First {...} 33 | class Second {...} 34 | class _Third {...} 35 | ``` 36 | 37 | That gives more control over whether the generated class is public or private. 38 | 39 | - Added support for manually overriding the generated widget name (thanks to @doejon) 40 | 41 | ``` 42 | @FunctionalWidget(name: 'MyClass') 43 | Widget _myWidget() {} 44 | ``` 45 | 46 | - Correctly support advanced annotations on widget parameters (thanks to @Almighty-Alpaca) 47 | 48 | - Upgraded dependencies to support analyzer 3.0.0 and 4.0.0 (thanks to @realshovanshah) 49 | 50 | ## 0.9.2 51 | 52 | Added support for `HookConsumerWidget` and `ConsumerWidget` from [Riverpod](https://pub.dev/packages/riverpod) (thanks to @tim-smart) 53 | 54 | ## 0.9.1 55 | 56 | - Allows nullable widget `Key` 57 | 58 | ## 0.9.0+2 59 | 60 | Fixed an issue where the generator potentially throws an `InconsistentAnalysisException` 61 | 62 | ## 0.9.0+1 63 | 64 | Ugraded dependencies to latest 65 | 66 | ## 0.9.0 67 | 68 | Migrated to null-safety (thanks to @tim-smart) 69 | 70 | ## 0.8.1 71 | 72 | - Updated `analyzer` and `build_runner` versions 73 | 74 | ## 0.8.0 75 | 76 | - removed `@swidget` 77 | - removed `FunctionalWidgetEquality.equal` 78 | - removed `FunctionalWidgetEquality.identical` 79 | 80 | ## 0.7.3 81 | 82 | - Upgraded all the dependencies to latest (thanks to @truongsinh) 83 | 84 | ## 0.7.1 85 | 86 | - support for default values of optional parameters 87 | 88 | ## 0.7.0 89 | 90 | - support `@required` for `Color` and other `dart:ui` types 91 | 92 | ## 0.6.1 93 | 94 | - fixes invalid generation with generic functions 95 | 96 | ## 0.6.0 97 | 98 | - Updated analyzer version to work with `flutter generate` & co 99 | 100 | ## 0.5.0 101 | 102 | - Allows enabling/disable features though both `build.yaml` and a new decorator: `FunctionalWidget` 103 | - `operator==` and `debugFillProperties` overrides are now turned off by default. 104 | 105 | ## 0.4.0 106 | 107 | - Overrides `debugFillProperties` for an integration with the widget inspector. 108 | This requires adding a new import in your dart files: 109 | `import 'package:flutter/foundation.dart'`; 110 | - Now overrides `operator==` and `hashCode` on the generated class. 111 | 112 | The behavior is that the following function: 113 | 114 | ```dart 115 | @swidget 116 | Widget foo(int foo, int bar) { 117 | return Container(); 118 | } 119 | ``` 120 | 121 | now generates the following overides: 122 | 123 | ```dart 124 | @override 125 | int get hashCode => hashValues(foo, bar); 126 | 127 | @override 128 | bool operator ==(Object o) => 129 | identical(o, this) || (o is Foo && foo == o.foo && bar == o.bar); 130 | ``` 131 | 132 | This is useful because overriding `operator==` prevents pointless rebuild when no parameter change. 133 | 134 | ## 0.3.0 135 | 136 | - Support function callbacks and generic functions: 137 | 138 | ```dart 139 | @swidget 140 | Widget foo(void onTap(T value)) { 141 | // do something 142 | } 143 | ``` 144 | 145 | - Updated support for `HookWidget` using new annotation `@hwidget`: 146 | 147 | ```dart 148 | @hwidget 149 | Widget foo() { 150 | final counter = useState(0); 151 | // do something 152 | } 153 | ``` 154 | 155 | ## 0.2.2 156 | 157 | - Readme update 158 | 159 | ## 0.2.1 160 | 161 | - Fix bug where types from `dart:ui` where generated as `dynamic` 162 | 163 | ## 0.2.0 164 | 165 | - Rename generator 166 | - Add documentation 167 | 168 | ## 0.1.0 169 | 170 | - Generate class documentation from the function documentation. 171 | - Pass down decorators from function parameter to class constructor 172 | 173 | ## 0.0.1 174 | 175 | Initial release 176 | -------------------------------------------------------------------------------- /packages/functional_widget/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Remi Rousselet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/functional_widget/README.md: -------------------------------------------------------------------------------- 1 | [![Build](https://github.com/rrousselGit/functional_widget/actions/workflows/build.yml/badge.svg)](https://github.com/rrousselGit/functional_widget/actions/workflows/build.yml) 2 | [![pub package](https://img.shields.io/pub/v/functional_widget.svg)](https://pub.dartlang.org/packages/functional_widget) [![pub package](https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square)](https://github.com/Solido/awesome-flutter) 3 | 4 | Widgets are cool. But classes are quite verbose: 5 | 6 | ```dart 7 | class Foo extends StatelessWidget { 8 | final int value; 9 | final int value2; 10 | 11 | const Foo({Key key, this.value, this.value2}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Text('$value $value2'); 16 | } 17 | } 18 | ``` 19 | 20 | So much code for something that could be done much better using a plain function: 21 | 22 | ```dart 23 | Widget foo(BuildContext context, { int value, int value2 }) { 24 | return Text('$value $value2'); 25 | } 26 | ``` 27 | 28 | The problem is, using functions instead of classes is not recommended: 29 | 30 | - https://stackoverflow.com/questions/53234825/what-is-the-difference-between-functions-and-classes-to-create-widgets/53234826#53234826 31 | - https://github.com/flutter/flutter/issues/19269 32 | 33 | ... Or is it? 34 | 35 | --- 36 | 37 | _functional_widgets_, is an attempt to solve this issue, using a code generator. 38 | 39 | Simply write your widget as a function, decorate it with a `@swidget`, and then 40 | this library will generate a class for you to use. 41 | 42 | As the added benefit, you also get for free the ability to inspect the parameters 43 | passed to your widgets in the devtool 44 | 45 | ## Example 46 | 47 | You write: 48 | 49 | ```dart 50 | @swidget 51 | Widget foo(BuildContext context, int value) { 52 | return Text('$value'); 53 | } 54 | ``` 55 | 56 | It generates: 57 | 58 | ```dart 59 | class Foo extends StatelessWidget { 60 | const Foo(this.value, {Key key}) : super(key: key); 61 | 62 | final int value; 63 | 64 | @override 65 | Widget build(BuildContext context) { 66 | return foo(context, value); 67 | } 68 | 69 | @override 70 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 71 | super.debugFillProperties(properties); 72 | properties.add(IntProperty('value', value)); 73 | } 74 | } 75 | ``` 76 | 77 | And then you use it: 78 | 79 | ```dart 80 | runApp( 81 | Foo(42) 82 | ); 83 | ``` 84 | 85 | ## How to use 86 | 87 | ### Install (builder) 88 | 89 | There are a few separate packages you need to install: 90 | 91 | - `functional_widget_annotation`, a package containing decorators. You must 92 | install it as `dependencies`. 93 | - `functional_widget`, a code-generator that uses the decorators from the previous 94 | packages to generate your widget. 95 | - `build_runner`, a dependency that all applications using code-generation should have 96 | 97 | Your `pubspec.yaml` should look like: 98 | 99 | ```yaml 100 | dependencies: 101 | functional_widget_annotation: ^0.8.0 102 | 103 | dev_dependencies: 104 | functional_widget: ^0.8.0 105 | build_runner: ^1.9.0 106 | ``` 107 | 108 | That's it! 109 | 110 | You can then start the code-generator with: 111 | 112 | ```sh 113 | flutter pub run build_runner watch 114 | ``` 115 | 116 | ## Customize the output 117 | 118 | It is possible to customize the output of the generator by using different decorators or configuring default values in `build.yaml` file. 119 | 120 | `build.yaml` change the default behavior of a configuration. 121 | 122 | ```yaml 123 | # build.yaml 124 | targets: 125 | $default: 126 | builders: 127 | functional_widget: 128 | options: 129 | # Default values: 130 | debugFillProperties: false 131 | widgetType: stateless # or 'hook' 132 | ``` 133 | 134 | `FunctionalWidget` decorator will override the default behavior for one specific widget. 135 | 136 | ```dart 137 | @FunctionalWidget( 138 | debugFillProperties: true, 139 | widgetType: FunctionalWidgetType.hook, 140 | ) 141 | Widget foo() => Container(); 142 | ``` 143 | 144 | ### debugFillProperties override 145 | 146 | Widgets can be override `debugFillProperties` to display custom fields on the widget inspector. `functional_widget` offer to generate these bits for your, by enabling `debugFillProperties` option. 147 | 148 | For this to work, it is required to add the following import: 149 | 150 | ```dart 151 | import 'package:flutter/foundation.dart'; 152 | ``` 153 | 154 | Example: 155 | 156 | (You write) 157 | 158 | ```dart 159 | import 'package:flutter/foundation.dart'; 160 | 161 | part 'example.g.dart'; 162 | 163 | @swidget 164 | Widget example(int foo, String bar) => Container(); 165 | ``` 166 | 167 | (It generates) 168 | 169 | ```dart 170 | class Example extends StatelessWidget { 171 | const Example(this.foo, this.bar, {Key key}) : super(key: key); 172 | 173 | final int foo; 174 | 175 | final String bar; 176 | 177 | @override 178 | Widget build(BuildContext _context) => example(foo, bar); 179 | @override 180 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 181 | super.debugFillProperties(properties); 182 | properties.add(IntProperty('foo', foo)); 183 | properties.add(StringProperty('bar', bar)); 184 | } 185 | } 186 | ``` 187 | 188 | ### Generate different type of widgets 189 | 190 | By default, the generated widget by `@FunctionalWidget()` is a `StatelessWidget`. 191 | 192 | It is possible to generate: 193 | 194 | - a `HookWidget` (from https://github.com/rrousselGit/flutter_hooks) 195 | - a `HookConsumerWidget` (from [hooks_riverpod](https://pub.dev/packages/hooks_riverpod)) 196 | - a `ConsumerWidget` (from [flutter_riverpod](https://pub.dev/packages/flutter_riverpod)) 197 | 198 | There are a few ways to do so: 199 | 200 | - With the shorthand `@hwidget` decorator: 201 | 202 | ```dart 203 | @hwidget // Creates a HookWidget 204 | Widget example(int foo, String bar) => Container(); 205 | 206 | @hcwidget // Creates a HookConsumerWidget 207 | Widget example(WidgetRef ref, int foo, String bar) => Container(); 208 | 209 | @cwidget // Creates a ConsumerWidget 210 | Widget example(WidgetRef ref, int foo, String bar) => Container(); 211 | ``` 212 | 213 | - Through `build.yaml`: 214 | 215 | ```yaml 216 | # build.yaml 217 | targets: 218 | $default: 219 | builders: 220 | functional_widget: 221 | options: 222 | widgetType: hook 223 | ``` 224 | 225 | then used as: 226 | 227 | ```dart 228 | @FunctionalWidget() 229 | Widget example(int foo, String bar) => Container(); 230 | ``` 231 | 232 | - With parameters on the `@FunctionalWidget` decorator: 233 | 234 | ```dart 235 | @FunctionalWidget(widgetType: FunctionalWidgetType.hook) 236 | Widget example(int foo, String bar) => Container(); 237 | ``` 238 | 239 | In any cases, you will need to install the corresponding package separately, by 240 | adding either `flutter_hooks`/`flutter_riverpod` or `hooks_riverpod` to your `pubspec.yaml` 241 | 242 | ```yaml 243 | dependencies: 244 | flutter_hooks: # some version number 245 | ``` 246 | 247 | ### All the potential function prototypes 248 | 249 | _functional_widget_ will inject widget specific parameters if you ask for them. 250 | You can potentially write any of the following: 251 | 252 | ```dart 253 | Widget foo(); 254 | Widget foo(BuildContext context); 255 | Widget foo(Key key); 256 | Widget foo(BuildContext context, Key key); 257 | Widget foo(Key key, BuildContext context); 258 | ``` 259 | 260 | You can then add however many arguments you like **after** the previously defined arguments. They will then be added to the class constructor and as a widget field: 261 | 262 | - positional 263 | 264 | ```dart 265 | @swidget 266 | Widget foo(int value) => Text(value.toString()); 267 | 268 | // USAGE 269 | 270 | Foo(42); 271 | ``` 272 | 273 | - named: 274 | 275 | ```dart 276 | @swidget 277 | Widget foo({int value}) => Text(value.toString()); 278 | 279 | // USAGE 280 | 281 | Foo(value: 42); 282 | ``` 283 | 284 | - A bit of everything: 285 | 286 | ```dart 287 | @swidget 288 | Widget foo(BuildContext context, int value, { int value2 }) { 289 | return Text('$value $value2'); 290 | } 291 | 292 | // USAGE 293 | 294 | Foo(42, value2: 24); 295 | ``` 296 | 297 | ### Private vs public widgets 298 | 299 | In order to allow for private function definitions but exported widgets, all 300 | decorated widget functions with a single underscore will generate an exported widget. 301 | 302 | ```dart 303 | @swidget 304 | Widget _foo(BuildContext context, int value, { int value2 }) { 305 | return Text('$value $value2'); 306 | } 307 | 308 | // USAGE 309 | 310 | Foo(42, value2: 24); 311 | ``` 312 | 313 | In order to keep generated widget private, do use two underscores: 314 | 315 | ```dart 316 | @swidget 317 | Widget __foo(BuildContext context, int value, { int value2 }) { 318 | return Text('$value $value2'); 319 | } 320 | 321 | // USAGE 322 | 323 | _Foo(42, value2: 24); 324 | ``` 325 | 326 | ## Sponsors 327 | 328 |

329 | 330 | 331 | 332 |

333 | -------------------------------------------------------------------------------- /packages/functional_widget/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | exclude: 3 | - "*.g.dart" 4 | strong-mode: 5 | implicit-casts: false 6 | implicit-dynamic: false 7 | linter: 8 | rules: 9 | - annotate_overrides 10 | - avoid_empty_else 11 | - avoid_function_literals_in_foreach_calls 12 | - avoid_init_to_null 13 | - avoid_null_checks_in_equality_operators 14 | - avoid_relative_lib_imports 15 | - avoid_renaming_method_parameters 16 | - avoid_return_types_on_setters 17 | - avoid_types_as_parameter_names 18 | - avoid_unused_constructor_parameters 19 | - await_only_futures 20 | - camel_case_types 21 | - cancel_subscriptions 22 | - cascade_invocations 23 | - comment_references 24 | - constant_identifier_names 25 | - control_flow_in_finally 26 | - directives_ordering 27 | - empty_catches 28 | - empty_constructor_bodies 29 | - empty_statements 30 | - hash_and_equals 31 | - implementation_imports 32 | - library_names 33 | - library_prefixes 34 | - no_adjacent_strings_in_list 35 | - no_duplicate_case_values 36 | - non_constant_identifier_names 37 | - null_closures 38 | - omit_local_variable_types 39 | - only_throw_errors 40 | - overridden_fields 41 | - package_names 42 | - package_prefixed_library_names 43 | - prefer_adjacent_string_concatenation 44 | - prefer_collection_literals 45 | - prefer_conditional_assignment 46 | - prefer_const_constructors 47 | - prefer_contains 48 | - prefer_final_fields 49 | - prefer_initializing_formals 50 | - prefer_interpolation_to_compose_strings 51 | - prefer_is_empty 52 | - prefer_is_not_empty 53 | - prefer_single_quotes 54 | - prefer_typing_uninitialized_variables 55 | - recursive_getters 56 | - slash_for_doc_comments 57 | - test_types_in_equals 58 | - throw_in_finally 59 | - type_init_formals 60 | - unawaited_futures 61 | - unnecessary_brace_in_string_interps 62 | - unnecessary_const 63 | - unnecessary_getters_setters 64 | - unnecessary_lambdas 65 | - unnecessary_new 66 | - unnecessary_null_aware_assignments 67 | - unnecessary_statements 68 | - unnecessary_this 69 | - unrelated_type_equality_checks 70 | - use_rethrow_when_possible 71 | - valid_regexps 72 | -------------------------------------------------------------------------------- /packages/functional_widget/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | functional_widget: 5 | enabled: true 6 | generate_for: 7 | include: 8 | - example/lib/* 9 | builders: 10 | functional_widget: 11 | import: 'package:functional_widget/builder.dart' 12 | builder_factories: ['functionalWidget'] 13 | build_extensions: { '.dart': ['.functional_widget.g.part'] } 14 | auto_apply: dependents 15 | build_to: cache 16 | applies_builders: ['source_gen|combining_builder'] 17 | -------------------------------------------------------------------------------- /packages/functional_widget/example/analysis_options.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/functional_widget/6e676977e53258f3aa5604927df3abde31939089/packages/functional_widget/example/analysis_options.yaml -------------------------------------------------------------------------------- /packages/functional_widget/example/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | functional_widget: 5 | options: 6 | debugFillProperties: true 7 | widgetType: stateless 8 | equality: equal -------------------------------------------------------------------------------- /packages/functional_widget/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:functional_widget_annotation/functional_widget_annotation.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | 5 | part 'main.g.dart'; 6 | 7 | // we create a widget using a widget decorated by `@swidget` 8 | @swidget 9 | Widget foo(int value) { 10 | return Container(); 11 | } 12 | 13 | void main() => runApp( 14 | // we use the generated class 15 | const Foo(42), 16 | ); 17 | -------------------------------------------------------------------------------- /packages/functional_widget/example/lib/main.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'main.dart'; 4 | 5 | // ************************************************************************** 6 | // FunctionalWidgetGenerator 7 | // ************************************************************************** 8 | 9 | class Foo extends StatelessWidget { 10 | const Foo( 11 | this.value, { 12 | Key? key, 13 | }) : super(key: key); 14 | 15 | final int value; 16 | 17 | @override 18 | Widget build(BuildContext _context) => foo(value); 19 | 20 | @override 21 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 22 | super.debugFillProperties(properties); 23 | properties.add(IntProperty('value', value)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/functional_widget/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: functional_widget_example 2 | description: A new Flutter project. 3 | publish_to: none 4 | 5 | environment: 6 | sdk: ^3.6.0 7 | 8 | resolution: workspace 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | functional_widget_annotation: 14 | 15 | dev_dependencies: 16 | functional_widget: 17 | build_runner: ^2.0.0 18 | -------------------------------------------------------------------------------- /packages/functional_widget/lib/builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:build/build.dart'; 2 | import 'package:functional_widget/function_to_widget_class.dart'; 3 | import 'package:functional_widget/src/utils.dart'; 4 | import 'package:source_gen/source_gen.dart'; 5 | 6 | /// Builds generators for `build_runner` to run 7 | Builder functionalWidget(BuilderOptions options) { 8 | final parse = parseBuilderOptions(options); 9 | return SharedPartBuilder( 10 | [FunctionalWidgetGenerator(parse)], 11 | 'functional_widget', 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /packages/functional_widget/lib/function_to_widget_class.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: invalid_use_of_visible_for_testing_member 2 | import 'package:analyzer/dart/element/element.dart'; 3 | import 'package:analyzer/dart/element/type.dart'; 4 | import 'package:build/build.dart'; 5 | import 'package:code_builder/code_builder.dart'; 6 | import 'package:functional_widget/src/parameters.dart'; 7 | import 'package:functional_widget/src/utils.dart'; 8 | import 'package:functional_widget_annotation/functional_widget_annotation.dart'; 9 | import 'package:source_gen/source_gen.dart'; 10 | 11 | const _kFlutterWidgetsPath = 'package:flutter/material.dart'; 12 | const _kHookWidgetsPath = 'package:flutter_hooks/flutter_hooks.dart'; 13 | const _kHookConsumerWidgetsPath = 'package:hooks_riverpod/hooks_riverpod.dart'; 14 | const _kConsumerWidgetsPath = 'package:flutter_riverpod/flutter_riverpod.dart'; 15 | 16 | final _widgetRef = refer('Widget', _kFlutterWidgetsPath); 17 | final _statelessWidgetRef = refer('StatelessWidget', _kFlutterWidgetsPath); 18 | final _hookWidgetRef = refer('HookWidget', _kHookWidgetsPath); 19 | final _hookConsumerWidgetRef = 20 | refer('HookConsumerWidget', _kHookConsumerWidgetsPath); 21 | final _consumerWidgetRef = refer('ConsumerWidget', _kConsumerWidgetsPath); 22 | final _buildContextRef = refer('BuildContext', _kFlutterWidgetsPath); 23 | final _widgetRefRef = refer('WidgetRef', _kHookWidgetsPath); 24 | 25 | final _typeToRefMap = { 26 | FunctionalWidgetType.hook: _hookWidgetRef, 27 | FunctionalWidgetType.hookConsumer: _hookConsumerWidgetRef, 28 | FunctionalWidgetType.consumer: _consumerWidgetRef, 29 | FunctionalWidgetType.stateless: _statelessWidgetRef, 30 | }; 31 | 32 | // _toTitle converts a string's first character ([a-zA-Z]) to uppercase. 33 | String _toTitle(String string) { 34 | return string.replaceFirstMapped(RegExp('[a-zA-Z]'), (match) { 35 | return match.group(0)!.toUpperCase(); 36 | }); 37 | } 38 | 39 | const _kOverrideDecorator = CodeExpression(Code('override')); 40 | 41 | /// A generator that outputs widgets from a function 42 | /// 43 | /// The function must be decorated by `@swidget` and be a top level function. 44 | /// The type of the widget is infered by the arguments of the function and defaults 45 | /// to `StatelessWidget` 46 | class FunctionalWidgetGenerator 47 | extends GeneratorForAnnotation { 48 | FunctionalWidgetGenerator([FunctionalWidget? options]) 49 | : _defaultOptions = FunctionalWidget( 50 | debugFillProperties: options?.debugFillProperties, 51 | widgetType: options?.widgetType ?? FunctionalWidgetType.stateless, 52 | ); 53 | 54 | final FunctionalWidget _defaultOptions; 55 | final _emitter = DartEmitter( 56 | allocator: Allocator.none, 57 | orderDirectives: false, 58 | useNullSafetySyntax: true, 59 | ); 60 | 61 | // determineClassName tries to determine a class name used for 62 | // class generation. In case a custom name is being provided, 63 | // we will use custom name - otherwise we will try to get name 64 | // from parsed element. 65 | String _determineClassName(Element element, String? customName) { 66 | if (customName != null) { 67 | return customName; 68 | } 69 | var out = element.name ?? ''; 70 | 71 | // We remove the fist _ such that _widget becomes Widget and __widget 72 | // becomes _Widget, giving some control over the privacy of the generated class 73 | if (out.isNotEmpty && out[0] == '_') { 74 | out = out.substring(1); 75 | } 76 | // as a last step, we need to convert lower case characters to upper case. 77 | return _toTitle(out); 78 | } 79 | 80 | @override 81 | Future generateForAnnotatedElement( 82 | Element element, ConstantReader annotation, BuildStep buildStep) async { 83 | // in case we do have a custom name set, we will find it @ functional widget 84 | final customName = parseFunctionalWidgetName(annotation); 85 | // to figure out how to call our class, let's either use customName or 86 | // the element's name (if available). 87 | // _checkValidElement will make sure to validate class name in the next step 88 | final className = _determineClassName(element, customName); 89 | final function = _checkValidElement(element, className); 90 | final type = parseFunctionalWidgetAnnotation(annotation); 91 | 92 | final _class = await _makeClassFromFunctionElement( 93 | function, 94 | type, 95 | buildStep, 96 | className, 97 | ); 98 | 99 | return _class.accept(_emitter).toString(); 100 | } 101 | 102 | FunctionElement _checkValidElement(Element element, String className) { 103 | if (element is! FunctionElement) { 104 | throw InvalidGenerationSourceError( 105 | 'Error, the decorated element is not a function', 106 | element: element, 107 | ); 108 | } 109 | final function = element; 110 | if (function.isAsynchronous || 111 | function.isExternal || 112 | function.isGenerator || 113 | function.returnType.getDisplayString() != 'Widget') { 114 | throw InvalidGenerationSourceError( 115 | 'Invalid prototype. The function must be synchronous, top level, and return a Widget', 116 | element: function, 117 | ); 118 | } 119 | 120 | if (className == function.name) { 121 | throw InvalidGenerationSourceError( 122 | 'The function name must start with a lowercase character.', 123 | element: function, 124 | ); 125 | } 126 | return function; 127 | } 128 | 129 | Future _makeClassFromFunctionElement( 130 | FunctionElement functionElement, 131 | FunctionalWidget annotation, 132 | BuildStep buildStep, 133 | String className, 134 | ) async { 135 | final parameters = await FunctionParameters.parseFunctionElement( 136 | functionElement, 137 | buildStep, 138 | ); 139 | final userDefined = parameters.userDefined; 140 | final positional = _computeBuildPositionalParametersExpression(parameters); 141 | final named = _computeBuildNamedParametersExpression(parameters); 142 | 143 | Method? overrideDebugFillProperties; 144 | if (annotation.debugFillProperties ?? 145 | _defaultOptions.debugFillProperties ?? 146 | false) { 147 | overrideDebugFillProperties = await _overrideDebugFillProperties( 148 | userDefined, functionElement.parameters, buildStep); 149 | } 150 | 151 | return Class( 152 | (b) { 153 | final widgetType = annotation.widgetType; 154 | b 155 | ..name = className 156 | ..types.addAll( 157 | _parseTypeParameters(functionElement.typeParameters).toList()) 158 | ..extend = _typeToRefMap[widgetType] 159 | ..fields.addAll(_paramsToFields(userDefined, 160 | doc: functionElement.documentationComment)) 161 | ..constructors.add(_getConstructor(userDefined, 162 | doc: functionElement.documentationComment, 163 | keyIsRequired: parameters.hasNonNullableKey)) 164 | ..methods.add(_createBuildMethod(functionElement.displayName, 165 | positional: positional, 166 | named: named, 167 | function: functionElement, 168 | hasWidgetRefParameter: [ 169 | FunctionalWidgetType.consumer, 170 | FunctionalWidgetType.hookConsumer 171 | ].contains(widgetType))); 172 | if (functionElement.documentationComment != null) { 173 | b.docs.add(functionElement.documentationComment!); 174 | } 175 | if (overrideDebugFillProperties != null) { 176 | b.methods.add(overrideDebugFillProperties); 177 | } 178 | }, 179 | ); 180 | } 181 | 182 | Map _computeBuildNamedParametersExpression( 183 | FunctionParameters parameters) { 184 | final named = {}; 185 | for (final p in parameters.userDefined.where((p) => p.named)) { 186 | named[p.name] = CodeExpression(Code(p.name)); 187 | } 188 | return named; 189 | } 190 | 191 | List _computeBuildPositionalParametersExpression( 192 | FunctionParameters parameters) { 193 | return [ 194 | ...parameters.nonUserDefinedRenamed 195 | .map((p) => CodeExpression(Code(p.name))), 196 | ...parameters.userDefined 197 | .where((p) => !p.named) 198 | .map((p) => CodeExpression(Code(p.name))) 199 | ]; 200 | } 201 | 202 | Future _overrideDebugFillProperties(List userFields, 203 | List elements, BuildStep buildStep) async { 204 | if (userFields.isEmpty) { 205 | return null; 206 | } 207 | 208 | final _diagnosticProperties = await Future.wait(userFields.map((f) => 209 | _parameterToDiagnostic( 210 | f, elements.firstWhere((e) => e.name == f.name), buildStep))); 211 | 212 | return Method((b) => b 213 | ..annotations.add(_kOverrideDecorator) 214 | ..name = 'debugFillProperties' 215 | ..requiredParameters.add( 216 | Parameter((b) => b 217 | ..name = 'properties' 218 | ..type = refer('DiagnosticPropertiesBuilder')), 219 | ) 220 | ..returns = refer('void') 221 | ..lambda = false 222 | ..body = Block.of( 223 | [ 224 | const Code('super.debugFillProperties(properties);'), 225 | ..._diagnosticProperties, 226 | ], 227 | )); 228 | } 229 | 230 | Future _parameterToDiagnostic(Parameter parameter, 231 | ParameterElement element, BuildStep buildStep) async { 232 | String? propertyType; 233 | switch (parameter.type!.symbol) { 234 | case 'int': 235 | propertyType = 'IntProperty'; 236 | break; 237 | case 'double': 238 | propertyType = 'DoubleProperty'; 239 | break; 240 | case 'String': 241 | propertyType = 'StringProperty'; 242 | break; 243 | // TODO: Duration 244 | default: 245 | propertyType = _tryParseClassToEnumDiagnostic(element, propertyType) ?? 246 | _tryParseFunctionToDiagnostic(element, propertyType) ?? 247 | await _getFallbackElementDiagnostic(element, buildStep); 248 | } 249 | 250 | return Code( 251 | "properties.add($propertyType('${parameter.name}', ${parameter.name}));"); 252 | } 253 | 254 | Future _getFallbackElementDiagnostic( 255 | ParameterElement element, BuildStep buildStep) async { 256 | final parsedDynamicType = await tryParseDynamicType(element, buildStep); 257 | return 'DiagnosticsProperty<${element.type is DynamicType ? parsedDynamicType : element.type.getDisplayString()}>'; 258 | } 259 | 260 | String? _tryParseFunctionToDiagnostic( 261 | ParameterElement element, String? propertyType) { 262 | final kind = element.type.element?.kind; 263 | if (kind == ElementKind.FUNCTION || 264 | kind == ElementKind.FUNCTION_TYPE_ALIAS || 265 | kind == ElementKind.GENERIC_FUNCTION_TYPE) { 266 | // TODO: find a way to remove this dynamic 267 | propertyType = 'ObjectFlagProperty.has'; 268 | } 269 | return propertyType; 270 | } 271 | 272 | String? _tryParseClassToEnumDiagnostic( 273 | ParameterElement element, String? propertyType) { 274 | if (element.type.element is EnumElement) { 275 | propertyType = 'EnumProperty<${element.type.getDisplayString()}>'; 276 | } 277 | return propertyType; 278 | } 279 | 280 | Method _createBuildMethod( 281 | String functionName, { 282 | required List positional, 283 | required Map named, 284 | required FunctionElement function, 285 | bool hasWidgetRefParameter = false, 286 | }) { 287 | return Method( 288 | (b) => b 289 | ..name = 'build' 290 | ..annotations.add(_kOverrideDecorator) 291 | ..returns = _widgetRef 292 | ..requiredParameters.addAll([ 293 | Parameter((b) => b 294 | ..name = FunctionParameters 295 | .nonUserDefinedNames[_buildContextRef.symbol!]! 296 | ..type = _buildContextRef), 297 | if (hasWidgetRefParameter) 298 | Parameter((b) => b 299 | ..name = 300 | FunctionParameters.nonUserDefinedNames[_widgetRefRef.symbol!]! 301 | ..type = _widgetRefRef), 302 | ]) 303 | ..body = CodeExpression(Code(functionName)) 304 | .call( 305 | positional, 306 | named, 307 | function.typeParameters 308 | .map((p) => refer(p.displayName)) 309 | .toList()) 310 | .code, 311 | ); 312 | } 313 | 314 | Iterable _parseTypeParameters( 315 | List typeParameters, 316 | ) { 317 | return typeParameters.map((e) { 318 | final displayName = e.bound?.getDisplayString(); 319 | return displayName != null 320 | ? refer('${e.displayName} extends $displayName') 321 | : refer(e.displayName); 322 | }); 323 | } 324 | 325 | Constructor _getConstructor( 326 | List fields, { 327 | String? doc, 328 | required bool keyIsRequired, 329 | }) { 330 | return Constructor( 331 | (b) => b 332 | ..constant = true 333 | ..optionalParameters.add(Parameter((b) => b 334 | ..required = keyIsRequired 335 | ..named = true 336 | ..name = 'key' 337 | ..docs.clear() 338 | ..type = TypeReference((b) => b 339 | ..symbol = 'Key' 340 | ..url = _kFlutterWidgetsPath 341 | ..isNullable = !keyIsRequired))) 342 | ..docs.add(doc ?? '') 343 | ..requiredParameters 344 | .addAll(fields.where((p) => !p.named).map((p) => p.rebuild((b) => b 345 | ..toThis = true 346 | ..docs.clear() 347 | ..type = null))) 348 | ..optionalParameters 349 | .addAll(fields.where((p) => p.named).map((p) => p.rebuild((b) => b 350 | ..toThis = true 351 | ..docs.clear() 352 | ..type = null))) 353 | ..initializers.add(const Code('super(key: key)')), 354 | ); 355 | } 356 | 357 | Iterable _paramsToFields(List params, {String? doc}) sync* { 358 | for (final param in params) { 359 | yield Field( 360 | (b) => b 361 | ..name = param.name 362 | ..modifier = FieldModifier.final$ 363 | ..docs.add(doc ?? '') 364 | ..type = param.type, 365 | ); 366 | } 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /packages/functional_widget/lib/src/parameters.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/ast/ast.dart'; 2 | import 'package:analyzer/dart/element/element.dart'; 3 | import 'package:analyzer/dart/element/nullability_suffix.dart'; 4 | import 'package:analyzer/dart/element/type.dart' as element_type; 5 | import 'package:build/build.dart'; 6 | import 'package:code_builder/code_builder.dart'; 7 | import 'package:collection/collection.dart'; 8 | 9 | class FunctionParameters { 10 | FunctionParameters._(this._parameters); 11 | 12 | static const nonUserDefinedTypeSymbols = [ 13 | 'Key', 14 | 'Key?', 15 | 'BuildContext', 16 | 'WidgetRef', 17 | ]; 18 | static const nonUserDefinedNames = { 19 | 'Key': 'key!', 20 | 'Key?': 'key', 21 | 'BuildContext': '_context', 22 | 'WidgetRef': '_ref', 23 | }; 24 | 25 | static Future parseFunctionElement( 26 | FunctionElement element, BuildStep buildStep) async { 27 | final parsedParameters = 28 | element.parameters.map((p) => _parseParameter(p, buildStep)); 29 | final elementParams = await Future.wait(parsedParameters); 30 | 31 | return FunctionParameters._(elementParams.toList()); 32 | } 33 | 34 | final List _parameters; 35 | 36 | int get _userDefinedStartIndex { 37 | final remainingSymbols = nonUserDefinedTypeSymbols.fold>( 38 | {}, 39 | (acc, symbol) => {...acc, symbol.replaceAll('?', ''): true}, 40 | ); 41 | 42 | final index = _parameters.indexWhere((p) { 43 | final symbol = p.type?.symbol ?? ''; 44 | final symbolWithoutNullable = symbol.replaceAll('?', ''); 45 | final isNonUser = remainingSymbols[symbolWithoutNullable] ?? false; 46 | 47 | if (isNonUser) { 48 | remainingSymbols[symbolWithoutNullable] = false; 49 | } 50 | 51 | return !isNonUser; 52 | }); 53 | 54 | return index == -1 ? _parameters.length : index; 55 | } 56 | 57 | late final List nonUserDefined = _parameters.sublist( 58 | 0, 59 | _userDefinedStartIndex, 60 | ); 61 | 62 | late final List nonUserDefinedRenamed = nonUserDefined 63 | .map((p) => Parameter((b) => b 64 | ..name = nonUserDefinedNames[p.type?.symbol ?? ''] ?? p.name 65 | ..type = p.type 66 | ..named = p.named)) 67 | .toList(); 68 | 69 | late final String? keySymbol = 70 | nonUserDefined.firstWhereOrNull(_isKey)?.type?.symbol; 71 | late final bool hasKey = keySymbol != null; 72 | late final bool hasNonNullableKey = keySymbol == 'Key'; 73 | 74 | late final List userDefined = 75 | _parameters.sublist(_userDefinedStartIndex); 76 | } 77 | 78 | bool _isKey(Parameter param) => 79 | param.type?.symbol == 'Key' || param.type?.symbol == 'Key?'; 80 | 81 | Future _parseParameter( 82 | ParameterElement parameter, BuildStep buildStep) async { 83 | final _type = await _parameterToReference(parameter, buildStep); 84 | 85 | return Parameter( 86 | (b) => b 87 | ..name = parameter.name 88 | ..defaultTo = parameter.defaultValueCode != null 89 | ? Code(parameter.defaultValueCode!) 90 | : null 91 | ..docs.add(parameter.documentationComment ?? '') 92 | ..annotations.addAll(parameter.metadata.map((meta) { 93 | return CodeExpression(Code(meta.toSource().replaceFirst('@', ''))); 94 | })) 95 | ..named = parameter.isNamed 96 | ..required = parameter.isRequiredNamed 97 | ..type = _type, 98 | ); 99 | } 100 | 101 | Future _parameterToReference( 102 | ParameterElement element, BuildStep buildStep) async { 103 | if (element.type is element_type.DynamicType) { 104 | return refer(await tryParseDynamicType(element, buildStep)); 105 | } 106 | final typeToReference = await _typeToReference(element.type, buildStep); 107 | 108 | return typeToReference; 109 | } 110 | 111 | Future _typeToReference( 112 | element_type.DartType type, BuildStep buildStep) async { 113 | if (type is element_type.FunctionType) { 114 | // final functionTyped = type.element as FunctionTypedElement; 115 | final t = await _functionTypedElementToFunctionType(type, buildStep); 116 | return t.type; 117 | } 118 | final displayName = type.getDisplayString(); 119 | return refer(displayName); 120 | } 121 | 122 | Future _functionTypedElementToFunctionType( 123 | element_type.FunctionType element, BuildStep buildStep) async { 124 | final _returnType = await _typeToReference(element.returnType, buildStep); 125 | final _parameterTypes = await Future.wait(element.typeFormals.map( 126 | (f) => _typeToReference( 127 | f.instantiate(nullabilitySuffix: NullabilitySuffix.none), buildStep), 128 | )); 129 | final _requiredParameterReferences = 130 | await _mapOrListParameterReferences( 131 | element.parameters, 132 | (p) => p.isRequired, 133 | (p) => p.type!, 134 | buildStep, 135 | ); 136 | final _namedParameterEntries = 137 | await _mapOrListParameterReferences>( 138 | element.parameters, 139 | (p) => p.isNamed, 140 | (p) => MapEntry(p.name, p.type!), 141 | buildStep, 142 | ); 143 | final _optionalParameterReferences = 144 | await _mapOrListParameterReferences( 145 | element.parameters, 146 | (p) => p.isOptionalPositional, 147 | (p) => p.type!, 148 | buildStep, 149 | ); 150 | 151 | return FunctionType( 152 | (b) => b 153 | ..returnType = _returnType 154 | ..types.addAll(_parameterTypes) 155 | ..isNullable = element.nullabilitySuffix == NullabilitySuffix.question 156 | ..requiredParameters.addAll(_requiredParameterReferences) 157 | ..namedParameters.addEntries(_namedParameterEntries) 158 | ..optionalParameters.addAll(_optionalParameterReferences), 159 | ); 160 | } 161 | 162 | Future> _mapOrListParameterReferences( 163 | List params, 164 | bool Function(ParameterElement param) filterFunction, 165 | T Function(Parameter p) mapOrListFunction, 166 | BuildStep buildStep, 167 | ) async { 168 | final parsedParams = await Future.wait( 169 | params.where(filterFunction).map((p) => _parseParameter(p, buildStep)), 170 | ); 171 | final mapOrListParameterReferences = parsedParams.map(mapOrListFunction); 172 | 173 | return mapOrListParameterReferences; 174 | } 175 | 176 | Future tryParseDynamicType( 177 | ParameterElement element, BuildStep buildStep) async { 178 | final node = await buildStep.resolver.astNodeFor(element); 179 | final parameter = node is DefaultFormalParameter ? node.parameter : node; 180 | if (parameter is SimpleFormalParameter && parameter.type != null) { 181 | return parameter.type!.beginToken.toString(); 182 | } 183 | return 'dynamic'; 184 | } 185 | -------------------------------------------------------------------------------- /packages/functional_widget/lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:build/build.dart'; 2 | import 'package:collection/collection.dart'; 3 | import 'package:functional_widget_annotation/functional_widget_annotation.dart'; 4 | import 'package:source_gen/source_gen.dart'; 5 | 6 | const _kKnownOptionsName = ['widgetType', 'equality', 'debugFillProperties']; 7 | 8 | FunctionalWidget parseBuilderOptions(BuilderOptions options) { 9 | final unknownOption = options.config.keys 10 | .firstWhereOrNull((key) => !_kKnownOptionsName.contains(key)); 11 | 12 | if (unknownOption != null) { 13 | throw ArgumentError( 14 | 'Unknown option `$unknownOption`: ${options.config[unknownOption]}'); 15 | } 16 | final widgetType = _parseWidgetType(options.config['widgetType']); 17 | final debugFillProperties = 18 | _parseDebugFillProperties(options.config['debugFillProperties']); 19 | return FunctionalWidget( 20 | widgetType: widgetType ?? FunctionalWidgetType.stateless, 21 | debugFillProperties: debugFillProperties, 22 | ); 23 | } 24 | 25 | bool? _parseDebugFillProperties(dynamic value) { 26 | if (value == null) { 27 | // ignore: avoid_returning_null 28 | return null; 29 | } 30 | if (value is bool) { 31 | return value; 32 | } 33 | throw ArgumentError.value(value, 'debugFillProperties', 34 | 'Invalid value. Potential values are `true` or `false`'); 35 | } 36 | 37 | FunctionalWidgetType? _parseWidgetType(dynamic value) { 38 | if (value == null) { 39 | return null; 40 | } 41 | if (value is String) { 42 | switch (value) { 43 | case 'hook': 44 | return FunctionalWidgetType.hook; 45 | case 'stateless': 46 | return FunctionalWidgetType.stateless; 47 | } 48 | } 49 | throw ArgumentError.value(value, 'widgetType', 50 | 'Invalid value. Potential values are `hook` or `stateless`'); 51 | } 52 | 53 | FunctionalWidget parseFunctionalWidgetAnnotation(ConstantReader reader) { 54 | return FunctionalWidget( 55 | widgetType: 56 | _parseEnum(reader.read('widgetType'), FunctionalWidgetType.values) ?? 57 | FunctionalWidgetType.stateless, 58 | ); 59 | } 60 | 61 | String? parseFunctionalWidgetName(ConstantReader reader) { 62 | final val = reader.read('name'); 63 | if (val.isNull) { 64 | return null; 65 | } 66 | if (val.isString) { 67 | return val.stringValue; 68 | } 69 | throw ArgumentError('Unknown type for name: must be string or null'); 70 | } 71 | 72 | T? _parseEnum(ConstantReader reader, List values) { 73 | if (reader.isNull) return null; 74 | 75 | final nameField = reader.objectValue.getField('_name'); 76 | if (nameField == null) return null; 77 | 78 | final name = nameField.toStringValue(); 79 | if (name == null) return null; 80 | 81 | for (final value in values) { 82 | final valueName = value.toString().split('.').last; 83 | if (valueName == name) return value; 84 | } 85 | throw FormatException('Failed to parse the enum as $T'); 86 | } 87 | -------------------------------------------------------------------------------- /packages/functional_widget/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: functional_widget 2 | description: A code generator that generates widget classes from their implementation as a function. 3 | homepage: https://github.com/rrousselGit/functional_widget/tree/master/packages/functional_widget 4 | version: 0.10.3 5 | 6 | environment: 7 | sdk: ^3.6.0 8 | 9 | resolution: workspace 10 | 11 | dependencies: 12 | analyzer: ">=6.9.0 <8.0.0" 13 | build: ^2.0.0 14 | build_config: ^1.0.0 15 | meta: ^1.2.0 16 | functional_widget_annotation: ^0.10.0 17 | source_gen: ^2.0.0 18 | code_builder: ^4.0.0 19 | collection: ^1.0.0 20 | 21 | dev_dependencies: 22 | test: ^1.17.0 23 | dart_style: ^3.0.0 24 | build_runner: ^2.0.0 25 | code_gen_tester: 26 | -------------------------------------------------------------------------------- /packages/functional_widget/test/decorator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:code_gen_tester/code_gen_tester.dart'; 2 | import 'package:functional_widget/function_to_widget_class.dart'; 3 | import 'package:functional_widget_annotation/functional_widget_annotation.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | final tester = SourceGenTester.fromPath('test/src/success.dart'); 8 | 9 | group('decorators', () { 10 | test('swidget generates stateless widget even if default value is hook', 11 | () async { 12 | final _generator = FunctionalWidgetGenerator( 13 | const FunctionalWidget(widgetType: FunctionalWidgetType.hook)); 14 | final _expect = (String name, Matcher matcher) async => 15 | expectGenerateNamed(await tester, name, _generator, matcher); 16 | 17 | await _expect('sXWidget', completion(''' 18 | class SXWidget extends StatelessWidget { 19 | const SXWidget({Key? key}) : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext _context) => sXWidget(); 23 | } 24 | ''')); 25 | }); 26 | 27 | test('swidget generates private widget from double-private fn', () async { 28 | final _generator = FunctionalWidgetGenerator( 29 | const FunctionalWidget(widgetType: FunctionalWidgetType.hook)); 30 | final _expect = (String name, Matcher matcher) async => 31 | expectGenerateNamed(await tester, name, _generator, matcher); 32 | 33 | await _expect('__privateSWidget', completion(''' 34 | class _PrivateSWidget extends StatelessWidget { 35 | const _PrivateSWidget({Key? key}) : super(key: key); 36 | 37 | @override 38 | Widget build(BuildContext _context) => __privateSWidget(); 39 | } 40 | ''')); 41 | }); 42 | 43 | test('swidget generates public stateless widget from private fn', () async { 44 | final _generator = FunctionalWidgetGenerator( 45 | const FunctionalWidget(widgetType: FunctionalWidgetType.hook)); 46 | final _expect = (String name, Matcher matcher) async => 47 | expectGenerateNamed(await tester, name, _generator, matcher); 48 | 49 | await _expect('_publicSWidget', completion(''' 50 | class PublicSWidget extends StatelessWidget { 51 | const PublicSWidget({Key? key}) : super(key: key); 52 | 53 | @override 54 | Widget build(BuildContext _context) => _publicSWidget(); 55 | } 56 | ''')); 57 | }); 58 | 59 | test('hwidget generate hookwidget even if default value is stateless', 60 | () async { 61 | final _generator = FunctionalWidgetGenerator( 62 | const FunctionalWidget(widgetType: FunctionalWidgetType.stateless)); 63 | final _expect = (String name, Matcher matcher) async => 64 | expectGenerateNamed(await tester, name, _generator, matcher); 65 | 66 | await _expect('hXWidget', completion(''' 67 | class HXWidget extends HookWidget { 68 | const HXWidget({Key? key}) : super(key: key); 69 | 70 | @override 71 | Widget build(BuildContext _context) => hXWidget(); 72 | } 73 | ''')); 74 | }); 75 | 76 | test('hwidget generates public hook widget from private fn', () async { 77 | final _generator = FunctionalWidgetGenerator( 78 | const FunctionalWidget(widgetType: FunctionalWidgetType.stateless)); 79 | final _expect = (String name, Matcher matcher) async => 80 | expectGenerateNamed(await tester, name, _generator, matcher); 81 | 82 | await _expect('_publicHWidget', completion(''' 83 | class PublicHWidget extends HookWidget { 84 | const PublicHWidget({Key? key}) : super(key: key); 85 | 86 | @override 87 | Widget build(BuildContext _context) => _publicHWidget(); 88 | } 89 | ''')); 90 | }); 91 | 92 | test('hwidget generates private hook widget from double-private fn', 93 | () async { 94 | final _generator = FunctionalWidgetGenerator( 95 | const FunctionalWidget(widgetType: FunctionalWidgetType.stateless)); 96 | final _expect = (String name, Matcher matcher) async => 97 | expectGenerateNamed(await tester, name, _generator, matcher); 98 | 99 | await _expect('__privateHWidget', completion(''' 100 | class _PrivateHWidget extends HookWidget { 101 | const _PrivateHWidget({Key? key}) : super(key: key); 102 | 103 | @override 104 | Widget build(BuildContext _context) => __privateHWidget(); 105 | } 106 | ''')); 107 | }); 108 | 109 | group('exported private widgets', () { 110 | test('generate hook if conf is hook', () async { 111 | var _generator = FunctionalWidgetGenerator( 112 | const FunctionalWidget(widgetType: FunctionalWidgetType.hook)); 113 | final _expect = (String name, Matcher matcher) async => 114 | expectGenerateNamed(await tester, name, _generator, matcher); 115 | 116 | await _expect('adaptiveWidget', completion(''' 117 | class AdaptiveWidget extends StatelessWidget { 118 | const AdaptiveWidget({Key? key}) : super(key: key); 119 | 120 | @override 121 | Widget build(BuildContext _context) => adaptiveWidget(); 122 | } 123 | ''')); 124 | }); 125 | }); 126 | 127 | group('@swidget', () { 128 | test('generate stateless if conf is stateless', () async { 129 | var _generator = FunctionalWidgetGenerator( 130 | const FunctionalWidget(widgetType: FunctionalWidgetType.stateless)); 131 | final _expect = (String name, Matcher matcher) async => 132 | expectGenerateNamed(await tester, name, _generator, matcher); 133 | 134 | await _expect('adaptiveWidget', completion(''' 135 | class AdaptiveWidget extends StatelessWidget { 136 | const AdaptiveWidget({Key? key}) : super(key: key); 137 | 138 | @override 139 | Widget build(BuildContext _context) => adaptiveWidget(); 140 | } 141 | ''')); 142 | }); 143 | 144 | test('generate hook if conf is hook', () async { 145 | var _generator = FunctionalWidgetGenerator( 146 | const FunctionalWidget(widgetType: FunctionalWidgetType.hook)); 147 | final _expect = (String name, Matcher matcher) async => 148 | expectGenerateNamed(await tester, name, _generator, matcher); 149 | 150 | await _expect('adaptiveWidget', completion(''' 151 | class AdaptiveWidget extends StatelessWidget { 152 | const AdaptiveWidget({Key? key}) : super(key: key); 153 | 154 | @override 155 | Widget build(BuildContext _context) => adaptiveWidget(); 156 | } 157 | ''')); 158 | }); 159 | }); 160 | }); 161 | } 162 | -------------------------------------------------------------------------------- /packages/functional_widget/test/diagnostics_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:code_gen_tester/code_gen_tester.dart'; 2 | import 'package:functional_widget/function_to_widget_class.dart'; 3 | import 'package:functional_widget_annotation/functional_widget_annotation.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | final tester = SourceGenTester.fromPath('test/src/diagnostics.dart'); 8 | 9 | final _generator = FunctionalWidgetGenerator( 10 | const FunctionalWidget(debugFillProperties: true)); 11 | final _expect = (String name, Matcher matcher) async => 12 | expectGenerateNamed(await tester, name, _generator, matcher); 13 | 14 | group('diagnostics', () { 15 | test('int', () async { 16 | await _expect('intTest', completion(''' 17 | class IntTest extends StatelessWidget { 18 | const IntTest( 19 | this.a, { 20 | Key? key, 21 | }) : super(key: key); 22 | 23 | final int a; 24 | 25 | @override 26 | Widget build(BuildContext _context) => intTest(a); 27 | 28 | @override 29 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 30 | super.debugFillProperties(properties); 31 | properties.add(IntProperty('a', a)); 32 | } 33 | } 34 | ''')); 35 | }); 36 | 37 | test('double', () async { 38 | await _expect('doubleTest', completion(''' 39 | class DoubleTest extends StatelessWidget { 40 | const DoubleTest( 41 | this.a, { 42 | Key? key, 43 | }) : super(key: key); 44 | 45 | final double a; 46 | 47 | @override 48 | Widget build(BuildContext _context) => doubleTest(a); 49 | 50 | @override 51 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 52 | super.debugFillProperties(properties); 53 | properties.add(DoubleProperty('a', a)); 54 | } 55 | } 56 | ''')); 57 | }); 58 | 59 | test('String', () async { 60 | await _expect('stringTest', completion(''' 61 | class StringTest extends StatelessWidget { 62 | const StringTest( 63 | this.a, { 64 | Key? key, 65 | }) : super(key: key); 66 | 67 | final String a; 68 | 69 | @override 70 | Widget build(BuildContext _context) => stringTest(a); 71 | 72 | @override 73 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 74 | super.debugFillProperties(properties); 75 | properties.add(StringProperty('a', a)); 76 | } 77 | } 78 | ''')); 79 | }); 80 | 81 | test('enums', () async { 82 | await _expect('enumTest', completion(''' 83 | class EnumTest extends StatelessWidget { 84 | const EnumTest( 85 | this.a, { 86 | Key? key, 87 | }) : super(key: key); 88 | 89 | final _Foo a; 90 | 91 | @override 92 | Widget build(BuildContext _context) => enumTest(a); 93 | 94 | @override 95 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 96 | super.debugFillProperties(properties); 97 | properties.add(EnumProperty<_Foo>('a', a)); 98 | } 99 | } 100 | ''')); 101 | }); 102 | 103 | test( 104 | 'object', 105 | () async { 106 | await _expect('objectTest', completion(''' 107 | class ObjectTest extends StatelessWidget { 108 | const ObjectTest( 109 | this.a, { 110 | Key? key, 111 | }) : super(key: key); 112 | 113 | final Object a; 114 | 115 | @override 116 | Widget build(BuildContext _context) => objectTest(a); 117 | 118 | @override 119 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 120 | super.debugFillProperties(properties); 121 | properties.add(DiagnosticsProperty('a', a)); 122 | } 123 | } 124 | ''')); 125 | }, 126 | skip: 'Fix when we stop relying on code_gen_tester', 127 | ); 128 | 129 | test( 130 | 'function type', 131 | () async { 132 | await _expect('functionTest', completion(''' 133 | class FunctionTest extends StatelessWidget { 134 | const FunctionTest( 135 | this.a, { 136 | Key? key, 137 | }) : super(key: key); 138 | 139 | final void Function() a; 140 | 141 | @override 142 | Widget build(BuildContext _context) => functionTest(a); 143 | 144 | @override 145 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 146 | super.debugFillProperties(properties); 147 | properties.add(DiagnosticsProperty('a', a)); 148 | } 149 | } 150 | ''')); 151 | }, 152 | skip: 'Fix when we stop relying on code_gen_tester', 153 | ); 154 | 155 | test( 156 | 'typedef type', 157 | () async { 158 | // TODO should be `final void Function(T) a;` instead of 159 | // `final void Function(dynamic) a;` 160 | await _expect('typedefTest', completion(''' 161 | class TypedefTest extends StatelessWidget { 162 | const TypedefTest( 163 | this.a, { 164 | Key? key, 165 | }) : super(key: key); 166 | 167 | final void Function(dynamic) a; 168 | 169 | @override 170 | Widget build(BuildContext _context) => typedefTest(a); 171 | 172 | @override 173 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 174 | super.debugFillProperties(properties); 175 | properties.add(DiagnosticsProperty('a', a)); 176 | } 177 | } 178 | ''')); 179 | }, 180 | skip: 'Fix when we stop relying on code_gen_tester', 181 | ); 182 | 183 | test( 184 | 'dynamic type', 185 | () async { 186 | await _expect('dynamicTest', completion(''' 187 | class DynamicTest extends StatelessWidget { 188 | const DynamicTest( 189 | this.a, { 190 | Key? key, 191 | }) : super(key: key); 192 | 193 | final dynamic a; 194 | 195 | @override 196 | Widget build(BuildContext _context) => dynamicTest(a); 197 | 198 | @override 199 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 200 | super.debugFillProperties(properties); 201 | properties.add(DiagnosticsProperty('a', a)); 202 | } 203 | } 204 | ''')); 205 | }, 206 | skip: 'Fix when we stop relying on code_gen_tester', 207 | ); 208 | }); 209 | } 210 | -------------------------------------------------------------------------------- /packages/functional_widget/test/fail_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:code_gen_tester/code_gen_tester.dart'; 2 | import 'package:functional_widget/function_to_widget_class.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | final tester = SourceGenTester.fromPath('test/src/fail.dart'); 7 | 8 | group('fail', () { 9 | test('closure', () async { 10 | await expectGenerateNamed( 11 | await tester, 12 | 'closure', 13 | FunctionalWidgetGenerator(), 14 | throwsInvalidGenerationSourceError( 15 | 'Error, the decorated element is not a function'), 16 | ); 17 | }); 18 | 19 | test('external', () async { 20 | await expectGenerateNamed( 21 | await tester, 22 | 'externalTest', 23 | FunctionalWidgetGenerator(), 24 | throwsInvalidGenerationSourceError(), 25 | ); 26 | }); 27 | 28 | test('return type not widget', () async { 29 | await expectGenerateNamed( 30 | await tester, 31 | 'notAWidget', 32 | FunctionalWidgetGenerator(), 33 | throwsInvalidGenerationSourceError(), 34 | ); 35 | }); 36 | 37 | test('name must start with a lowercase', () async { 38 | await expectGenerateNamed( 39 | await tester, 40 | 'TitleName', 41 | FunctionalWidgetGenerator(), 42 | throwsInvalidGenerationSourceError(), 43 | ); 44 | }); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /packages/functional_widget/test/parse_options_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:build/build.dart'; 2 | import 'package:functional_widget/src/utils.dart'; 3 | import 'package:functional_widget_annotation/functional_widget_annotation.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | group('parseOptions', () { 8 | test('fails for anything unknown', () { 9 | expect( 10 | () => parseBuilderOptions( 11 | const BuilderOptions({'foo': 42})), 12 | throwsA(const TypeMatcher() 13 | .having((f) => f.message, 'message', 'Unknown option `foo`: 42')), 14 | ); 15 | expect( 16 | () => parseBuilderOptions( 17 | const BuilderOptions({'bar': 'foo'})), 18 | throwsA(const TypeMatcher() 19 | .having((f) => f.message, 'message', 'Unknown option `bar`: foo')), 20 | ); 21 | }); 22 | group('debugFillProperties', () { 23 | test('default to null', () { 24 | expect( 25 | parseBuilderOptions(const BuilderOptions({})) 26 | .debugFillProperties, 27 | null, 28 | ); 29 | }); 30 | 31 | test('throws if not bool', () { 32 | expect( 33 | () => parseBuilderOptions(const BuilderOptions( 34 | {'debugFillProperties': 42})), 35 | throwsArgumentError, 36 | ); 37 | }); 38 | 39 | test('parses valid value', () { 40 | expect( 41 | parseBuilderOptions(const BuilderOptions( 42 | {'debugFillProperties': true})) 43 | .debugFillProperties, 44 | true, 45 | ); 46 | expect( 47 | parseBuilderOptions(const BuilderOptions( 48 | {'debugFillProperties': false})) 49 | .debugFillProperties, 50 | false, 51 | ); 52 | }); 53 | }); 54 | group('widgetType', () { 55 | test('default to stateless', () { 56 | expect( 57 | parseBuilderOptions(const BuilderOptions({})) 58 | .widgetType, 59 | FunctionalWidgetType.stateless); 60 | }); 61 | 62 | test('throws if string but not valid', () { 63 | expect( 64 | () => parseBuilderOptions( 65 | const BuilderOptions({'widgetType': 'foo'})), 66 | throwsArgumentError, 67 | ); 68 | }); 69 | 70 | test('throws if not string', () { 71 | expect( 72 | () => parseBuilderOptions( 73 | const BuilderOptions({'widgetType': 42})), 74 | throwsArgumentError, 75 | ); 76 | }); 77 | 78 | test('parses valid value', () { 79 | expect( 80 | parseBuilderOptions( 81 | const BuilderOptions({'widgetType': 'hook'})) 82 | .widgetType, 83 | FunctionalWidgetType.hook, 84 | ); 85 | expect( 86 | parseBuilderOptions(const BuilderOptions( 87 | {'widgetType': 'stateless'})).widgetType, 88 | FunctionalWidgetType.stateless, 89 | ); 90 | }); 91 | }); 92 | }); 93 | } 94 | -------------------------------------------------------------------------------- /packages/functional_widget/test/src/diagnostics.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_field 2 | import 'package:functional_widget_annotation/functional_widget_annotation.dart'; 3 | import 'fake_flutter.dart'; 4 | 5 | @swidget 6 | Widget intTest(int a) => Container(); 7 | 8 | @swidget 9 | Widget doubleTest(double a) => Container(); 10 | 11 | @swidget 12 | Widget stringTest(String a) => Container(); 13 | 14 | enum _Foo { a, b } 15 | 16 | @swidget 17 | Widget enumTest(_Foo a) => Container(); 18 | 19 | @swidget 20 | Widget objectTest(Object a) => Container(); 21 | 22 | typedef Typedef = void Function(T); 23 | 24 | @swidget 25 | Widget typedefTest(Typedef a) => Container(); 26 | 27 | @swidget 28 | Widget functionTest(void Function() a) => Container(); 29 | 30 | @swidget 31 | Widget dynamicTest(dynamic a) => Container(); 32 | -------------------------------------------------------------------------------- /packages/functional_widget/test/src/fail.dart: -------------------------------------------------------------------------------- 1 | import 'package:functional_widget_annotation/functional_widget_annotation.dart'; 2 | import 'fake_flutter.dart'; 3 | 4 | @swidget 5 | final closure = Container.new; 6 | 7 | @swidget 8 | external Widget externalTest(); 9 | 10 | @swidget 11 | int notAWidget() { 12 | return 42; 13 | } 14 | 15 | @swidget 16 | // ignore: non_constant_identifier_names 17 | Widget TitleName() { 18 | return Container(); 19 | } 20 | 21 | @swidget 22 | dynamic dynamicTest() { 23 | return Container(); 24 | } 25 | -------------------------------------------------------------------------------- /packages/functional_widget/test/src/fake_flutter.dart: -------------------------------------------------------------------------------- 1 | class Widget {} 2 | 3 | class Container extends Widget {} 4 | 5 | class BuildContext {} 6 | 7 | class Key {} 8 | 9 | class WidgetRef {} 10 | -------------------------------------------------------------------------------- /packages/functional_widget/test/src/success.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: implicit_dynamic_parameter, unused_element, private elements are used by the tests 2 | 3 | import 'package:functional_widget_annotation/functional_widget_annotation.dart'; 4 | 5 | import 'fake_flutter.dart'; 6 | 7 | @swidget 8 | Widget sXWidget() => Container(); 9 | 10 | @swidget 11 | Widget _publicSWidget() => Container(); 12 | 13 | @swidget 14 | Widget __privateSWidget() => Container(); 15 | 16 | @hwidget 17 | Widget hXWidget() => Container(); 18 | 19 | @hwidget 20 | Widget _publicHWidget() => Container(); 21 | 22 | @hwidget 23 | Widget __privateHWidget() => Container(); 24 | 25 | @swidget 26 | Widget adaptiveWidget() => Container(); 27 | 28 | @swidget 29 | Widget noArgument() => Container(); 30 | 31 | @swidget 32 | Widget namedDefault({int foo = 42}) => Container(); 33 | 34 | @swidget 35 | Widget onlyOneArg(int foo) => Container(); 36 | 37 | @swidget 38 | Widget withContext(BuildContext context) => Container(); 39 | 40 | @swidget 41 | Widget withContextThenOneArg(BuildContext context, int foo) => Container(); 42 | 43 | @swidget 44 | Widget withKey(Key key) => Container(); 45 | 46 | @swidget 47 | Widget withNullableKey(Key? key) => Container(); 48 | 49 | @swidget 50 | Widget withKeyThenOneArg(Key key, int foo) => Container(); 51 | 52 | @swidget 53 | Widget withContextThenKey(BuildContext context, Key key) => Container(); 54 | 55 | @swidget 56 | Widget withContextThenKeyThenOneArg(BuildContext context, Key key, int foo) => 57 | Container(); 58 | 59 | @swidget 60 | Widget withContextThenContext(BuildContext context, BuildContext context2) => 61 | Container(); 62 | 63 | @swidget 64 | Widget withKeyThenContext(Key key, BuildContext context) => Container(); 65 | 66 | @swidget 67 | Widget withKeyThenContextThenOneArg(Key key, BuildContext context, int foo) => 68 | Container(); 69 | 70 | @swidget 71 | Widget withKeyThenKey(Key? key, Key key2) => Container(); 72 | 73 | @swidget 74 | Widget whateverThenContext(int foo, BuildContext bar) => Container(); 75 | 76 | @swidget 77 | Widget whateverThenKey(int foo, Key bar) => Container(); 78 | 79 | /// Hello 80 | /// World 81 | @swidget 82 | Widget documentation(int foo) => Container(); 83 | 84 | @swidget 85 | Widget withRequired({required int foo}) => Container(); 86 | 87 | @swidget 88 | Widget withOptional({int? foo}) => Container(); 89 | 90 | @swidget 91 | Widget withPositionalOptional(int? foo) => Container(); 92 | 93 | @hwidget 94 | Widget hookExample() => Container(); 95 | 96 | @hcwidget 97 | Widget hookConsumerExample() => Container(); 98 | 99 | @hcwidget 100 | Widget hookConsumerExampleWithRef(WidgetRef ref) => Container(); 101 | 102 | @hcwidget 103 | Widget hookConsumerExampleWithRefAndContext( 104 | WidgetRef ref, BuildContext context) => 105 | Container(); 106 | 107 | @cwidget 108 | Widget consumerExample() => Container(); 109 | 110 | @cwidget 111 | Widget consumerExampleWithRef(WidgetRef ref) => Container(); 112 | 113 | @cwidget 114 | Widget consumerExampleWithRefAndContext(WidgetRef ref, BuildContext context) => 115 | Container(); 116 | 117 | typedef Typedef = void Function(T); 118 | 119 | @swidget 120 | Widget typedefFunction(Typedef t) => Container(); 121 | 122 | @swidget 123 | // ignore: use_function_type_syntax_for_parameters 124 | Widget inlineFunction(void t()) => Container(); 125 | 126 | @swidget 127 | Widget inlineFunction2(void Function() t) => Container(); 128 | 129 | @swidget 130 | Widget inlineFunctionWithArgs(void Function(BuildContext?) t) => Container(); 131 | 132 | @swidget 133 | Widget optionalInlineFunction(void Function()? t) => Container(); 134 | 135 | @swidget 136 | Widget nestedFunction(void Function(void Function(int a), int b) t) => 137 | Container(); 138 | 139 | @swidget 140 | Widget generic(T foo) => Container(); 141 | 142 | @swidget 143 | Widget genericMultiple(T foo, S bar) => Container(); 144 | 145 | @swidget 146 | Widget genericExtends(T foo) => Container(); 147 | 148 | @swidget 149 | Widget genericClass(T Function() foo) => Container(); 150 | 151 | @swidget 152 | Widget genericClassWithNullable(T? Function() foo) => Container(); 153 | 154 | // ignore: prefer_generic_function_type_aliases 155 | typedef T _GenericFunction(T foo); 156 | 157 | @swidget 158 | Widget genericFunction(_GenericFunction foo) => Container(); 159 | 160 | typedef _GenericFunction2 = T Function(T foo); 161 | 162 | @swidget 163 | Widget genericFunction2(_GenericFunction2 foo) => Container(); 164 | 165 | typedef _GenericFunction3 = U Function(T foo); 166 | 167 | @swidget 168 | Widget genericFunction3(_GenericFunction3 foo) => Container(); 169 | 170 | typedef _GenericFunction4 = T? Function(T? foo); 171 | 172 | @swidget 173 | Widget genericFunction4(_GenericFunction4? foo) => Container(); 174 | 175 | @FunctionalWidget( 176 | widgetType: FunctionalWidgetType.hook, 177 | name: 'CustomHookWidget', 178 | ) 179 | Widget hookWidgetWithCustomName(BuildContext ctx) => Container(); 180 | 181 | @FunctionalWidget( 182 | widgetType: FunctionalWidgetType.stateless, 183 | name: 'CustomStatelessWidget', 184 | ) 185 | Widget statelessWidgetWithCustomName(BuildContext ctx) => Container(); 186 | 187 | class TestAnnotation { 188 | const TestAnnotation([this.argument = 'default']); 189 | 190 | final String argument; 191 | } 192 | 193 | @swidget 194 | Widget annotation({@TestAnnotation() int foo = 42}) => Container(); 195 | 196 | @swidget 197 | Widget annotationParameter({@TestAnnotation('Test') int foo = 42}) => 198 | Container(); 199 | 200 | const testAnnotation = TestAnnotation('Another test'); 201 | 202 | @swidget 203 | Widget annotationConstant({@testAnnotation int foo = 42}) => Container(); 204 | -------------------------------------------------------------------------------- /packages/functional_widget/test/success_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:code_gen_tester/code_gen_tester.dart'; 2 | import 'package:functional_widget/function_to_widget_class.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | final tester = SourceGenTester.fromPath('test/src/success.dart'); 7 | 8 | final _generator = FunctionalWidgetGenerator(); 9 | final _expect = (String name, Matcher matcher) async => 10 | expectGenerateNamed(await tester, name, _generator, matcher); 11 | group('success', () { 12 | test('noArgument', () async { 13 | await _expect('noArgument', completion(''' 14 | class NoArgument extends StatelessWidget { 15 | const NoArgument({Key? key}) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext _context) => noArgument(); 19 | } 20 | ''')); 21 | }); 22 | 23 | test('namedDefault', () async { 24 | await _expect('namedDefault', completion(''' 25 | class NamedDefault extends StatelessWidget { 26 | const NamedDefault({ 27 | Key? key, 28 | this.foo = 42, 29 | }) : super(key: key); 30 | 31 | final int foo; 32 | 33 | @override 34 | Widget build(BuildContext _context) => namedDefault(foo: foo); 35 | } 36 | ''')); 37 | }); 38 | 39 | test('context', () async { 40 | await _expect('withContext', completion(''' 41 | class WithContext extends StatelessWidget { 42 | const WithContext({Key? key}) : super(key: key); 43 | 44 | @override 45 | Widget build(BuildContext _context) => withContext(_context); 46 | } 47 | ''')); 48 | }); 49 | 50 | test('key', () async { 51 | await _expect('withKey', completion(''' 52 | class WithKey extends StatelessWidget { 53 | const WithKey({required Key key}) : super(key: key); 54 | 55 | @override 56 | Widget build(BuildContext _context) => withKey(key!); 57 | } 58 | ''')); 59 | }); 60 | 61 | test('nullable key', () async { 62 | await _expect('withNullableKey', completion(''' 63 | class WithNullableKey extends StatelessWidget { 64 | const WithNullableKey({Key? key}) : super(key: key); 65 | 66 | @override 67 | Widget build(BuildContext _context) => withNullableKey(key); 68 | } 69 | ''')); 70 | }); 71 | 72 | test('context then key', () async { 73 | await _expect('withContextThenKey', completion(''' 74 | class WithContextThenKey extends StatelessWidget { 75 | const WithContextThenKey({required Key key}) : super(key: key); 76 | 77 | @override 78 | Widget build(BuildContext _context) => withContextThenKey( 79 | _context, 80 | key!, 81 | ); 82 | } 83 | ''')); 84 | }); 85 | 86 | test('context then key then arg', () async { 87 | await _expect('withContextThenKeyThenOneArg', completion(''' 88 | class WithContextThenKeyThenOneArg extends StatelessWidget { 89 | const WithContextThenKeyThenOneArg( 90 | this.foo, { 91 | required Key key, 92 | }) : super(key: key); 93 | 94 | final int foo; 95 | 96 | @override 97 | Widget build(BuildContext _context) => withContextThenKeyThenOneArg( 98 | _context, 99 | key!, 100 | foo, 101 | ); 102 | } 103 | ''')); 104 | }); 105 | 106 | test('context then context', () async { 107 | await _expect('withContextThenContext', completion(''' 108 | class WithContextThenContext extends StatelessWidget { 109 | const WithContextThenContext( 110 | this.context2, { 111 | Key? key, 112 | }) : super(key: key); 113 | 114 | final BuildContext context2; 115 | 116 | @override 117 | Widget build(BuildContext _context) => withContextThenContext( 118 | _context, 119 | context2, 120 | ); 121 | } 122 | ''')); 123 | }); 124 | 125 | test('key then context', () async { 126 | await _expect('withKeyThenContext', completion(''' 127 | class WithKeyThenContext extends StatelessWidget { 128 | const WithKeyThenContext({required Key key}) : super(key: key); 129 | 130 | @override 131 | Widget build(BuildContext _context) => withKeyThenContext( 132 | key!, 133 | _context, 134 | ); 135 | } 136 | ''')); 137 | }); 138 | 139 | test('key then context then arg', () async { 140 | await _expect('withKeyThenContextThenOneArg', completion(''' 141 | class WithKeyThenContextThenOneArg extends StatelessWidget { 142 | const WithKeyThenContextThenOneArg( 143 | this.foo, { 144 | required Key key, 145 | }) : super(key: key); 146 | 147 | final int foo; 148 | 149 | @override 150 | Widget build(BuildContext _context) => withKeyThenContextThenOneArg( 151 | key!, 152 | _context, 153 | foo, 154 | ); 155 | } 156 | ''')); 157 | }); 158 | 159 | test('key then key', () async { 160 | await _expect('withKeyThenKey', completion(''' 161 | class WithKeyThenKey extends StatelessWidget { 162 | const WithKeyThenKey( 163 | this.key2, { 164 | Key? key, 165 | }) : super(key: key); 166 | 167 | final Key key2; 168 | 169 | @override 170 | Widget build(BuildContext _context) => withKeyThenKey( 171 | key, 172 | key2, 173 | ); 174 | } 175 | ''')); 176 | }); 177 | 178 | test('whatever then context', () async { 179 | await _expect('whateverThenContext', completion(''' 180 | class WhateverThenContext extends StatelessWidget { 181 | const WhateverThenContext( 182 | this.foo, 183 | this.bar, { 184 | Key? key, 185 | }) : super(key: key); 186 | 187 | final int foo; 188 | 189 | final BuildContext bar; 190 | 191 | @override 192 | Widget build(BuildContext _context) => whateverThenContext( 193 | foo, 194 | bar, 195 | ); 196 | } 197 | ''')); 198 | }); 199 | 200 | test('whatever then key', () async { 201 | await _expect('whateverThenKey', completion(''' 202 | class WhateverThenKey extends StatelessWidget { 203 | const WhateverThenKey( 204 | this.foo, 205 | this.bar, { 206 | Key? key, 207 | }) : super(key: key); 208 | 209 | final int foo; 210 | 211 | final Key bar; 212 | 213 | @override 214 | Widget build(BuildContext _context) => whateverThenKey( 215 | foo, 216 | bar, 217 | ); 218 | } 219 | ''')); 220 | }); 221 | 222 | test('documentation', () async { 223 | await _expect('documentation', completion(''' 224 | /// Hello 225 | /// World 226 | class Documentation extends StatelessWidget { 227 | /// Hello 228 | /// World 229 | const Documentation( 230 | this.foo, { 231 | Key? key, 232 | }) : super(key: key); 233 | 234 | /// Hello 235 | /// World 236 | final int foo; 237 | 238 | @override 239 | Widget build(BuildContext _context) => documentation(foo); 240 | } 241 | ''')); 242 | }); 243 | 244 | test('required', () async { 245 | await _expect('withRequired', completion(''' 246 | class WithRequired extends StatelessWidget { 247 | const WithRequired({ 248 | Key? key, 249 | required this.foo, 250 | }) : super(key: key); 251 | 252 | final int foo; 253 | 254 | @override 255 | Widget build(BuildContext _context) => withRequired(foo: foo); 256 | } 257 | ''')); 258 | }); 259 | 260 | test('optional', () async { 261 | await _expect('withOptional', completion(''' 262 | class WithOptional extends StatelessWidget { 263 | const WithOptional({ 264 | Key? key, 265 | this.foo, 266 | }) : super(key: key); 267 | 268 | final int? foo; 269 | 270 | @override 271 | Widget build(BuildContext _context) => withOptional(foo: foo); 272 | } 273 | ''')); 274 | }); 275 | 276 | test('positional optional', () async { 277 | await _expect('withPositionalOptional', completion(''' 278 | class WithPositionalOptional extends StatelessWidget { 279 | const WithPositionalOptional( 280 | this.foo, { 281 | Key? key, 282 | }) : super(key: key); 283 | 284 | final int? foo; 285 | 286 | @override 287 | Widget build(BuildContext _context) => withPositionalOptional(foo); 288 | } 289 | ''')); 290 | }); 291 | 292 | test('hook widget', () async { 293 | await _expect('hookExample', completion(''' 294 | class HookExample extends HookWidget { 295 | const HookExample({Key? key}) : super(key: key); 296 | 297 | @override 298 | Widget build(BuildContext _context) => hookExample(); 299 | } 300 | ''')); 301 | }); 302 | 303 | test('consumer hook widget', () async { 304 | await _expect('hookConsumerExample', completion(''' 305 | class HookConsumerExample extends HookConsumerWidget { 306 | const HookConsumerExample({Key? key}) : super(key: key); 307 | 308 | @override 309 | Widget build( 310 | BuildContext _context, 311 | WidgetRef _ref, 312 | ) => 313 | hookConsumerExample(); 314 | } 315 | ''')); 316 | }); 317 | 318 | test('consumer hook widget with WidgetRef', () async { 319 | await _expect('hookConsumerExampleWithRef', completion(''' 320 | class HookConsumerExampleWithRef extends HookConsumerWidget { 321 | const HookConsumerExampleWithRef({Key? key}) : super(key: key); 322 | 323 | @override 324 | Widget build( 325 | BuildContext _context, 326 | WidgetRef _ref, 327 | ) => 328 | hookConsumerExampleWithRef(_ref); 329 | } 330 | ''')); 331 | }); 332 | 333 | test('consumer hook widget with WidgetRef and BuildContext', () async { 334 | await _expect('hookConsumerExampleWithRefAndContext', completion(''' 335 | class HookConsumerExampleWithRefAndContext extends HookConsumerWidget { 336 | const HookConsumerExampleWithRefAndContext({Key? key}) : super(key: key); 337 | 338 | @override 339 | Widget build( 340 | BuildContext _context, 341 | WidgetRef _ref, 342 | ) => 343 | hookConsumerExampleWithRefAndContext( 344 | _ref, 345 | _context, 346 | ); 347 | } 348 | ''')); 349 | }); 350 | 351 | test('consumer widget', () async { 352 | await _expect('consumerExample', completion(''' 353 | class ConsumerExample extends ConsumerWidget { 354 | const ConsumerExample({Key? key}) : super(key: key); 355 | 356 | @override 357 | Widget build( 358 | BuildContext _context, 359 | WidgetRef _ref, 360 | ) => 361 | consumerExample(); 362 | } 363 | ''')); 364 | }); 365 | 366 | test('consumer widget with WidgetRef', () async { 367 | await _expect('consumerExampleWithRef', completion(''' 368 | class ConsumerExampleWithRef extends ConsumerWidget { 369 | const ConsumerExampleWithRef({Key? key}) : super(key: key); 370 | 371 | @override 372 | Widget build( 373 | BuildContext _context, 374 | WidgetRef _ref, 375 | ) => 376 | consumerExampleWithRef(_ref); 377 | } 378 | ''')); 379 | }); 380 | 381 | test('consumer widget with WidgetRef and BuildContext', () async { 382 | await _expect('consumerExampleWithRefAndContext', completion(''' 383 | class ConsumerExampleWithRefAndContext extends ConsumerWidget { 384 | const ConsumerExampleWithRefAndContext({Key? key}) : super(key: key); 385 | 386 | @override 387 | Widget build( 388 | BuildContext _context, 389 | WidgetRef _ref, 390 | ) => 391 | consumerExampleWithRefAndContext( 392 | _ref, 393 | _context, 394 | ); 395 | } 396 | ''')); 397 | }); 398 | 399 | test('generic widget', () async { 400 | // currently not possible to know the type 401 | await _expect('generic', completion(''' 402 | class Generic extends StatelessWidget { 403 | const Generic( 404 | this.foo, { 405 | Key? key, 406 | }) : super(key: key); 407 | 408 | final T foo; 409 | 410 | @override 411 | Widget build(BuildContext _context) => generic(foo); 412 | } 413 | ''')); 414 | }); 415 | 416 | test('generic widget extends', () async { 417 | // currently not possible to know the type 418 | await _expect('genericExtends', completion(''' 419 | class GenericExtends extends StatelessWidget { 420 | const GenericExtends( 421 | this.foo, { 422 | Key? key, 423 | }) : super(key: key); 424 | 425 | final T foo; 426 | 427 | @override 428 | Widget build(BuildContext _context) => genericExtends(foo); 429 | } 430 | ''')); 431 | }); 432 | 433 | group('functions', () { 434 | test('typedef', () async { 435 | await _expect('typedefFunction', completion(''' 436 | class TypedefFunction extends StatelessWidget { 437 | const TypedefFunction( 438 | this.t, { 439 | Key? key, 440 | }) : super(key: key); 441 | 442 | final void Function(T) t; 443 | 444 | @override 445 | Widget build(BuildContext _context) => typedefFunction(t); 446 | } 447 | ''')); 448 | }); 449 | 450 | test('inline', () async { 451 | await _expect('inlineFunction', completion(''' 452 | class InlineFunction extends StatelessWidget { 453 | const InlineFunction( 454 | this.t, { 455 | Key? key, 456 | }) : super(key: key); 457 | 458 | final void Function() t; 459 | 460 | @override 461 | Widget build(BuildContext _context) => inlineFunction(t); 462 | } 463 | ''')); 464 | }); 465 | 466 | test('inline2', () async { 467 | await _expect('inlineFunction2', completion(''' 468 | class InlineFunction2 extends StatelessWidget { 469 | const InlineFunction2( 470 | this.t, { 471 | Key? key, 472 | }) : super(key: key); 473 | 474 | final void Function() t; 475 | 476 | @override 477 | Widget build(BuildContext _context) => inlineFunction2(t); 478 | } 479 | ''')); 480 | }); 481 | 482 | test('inline with args', () async { 483 | await _expect('inlineFunctionWithArgs', completion(''' 484 | class InlineFunctionWithArgs extends StatelessWidget { 485 | const InlineFunctionWithArgs( 486 | this.t, { 487 | Key? key, 488 | }) : super(key: key); 489 | 490 | final void Function(BuildContext?) t; 491 | 492 | @override 493 | Widget build(BuildContext _context) => inlineFunctionWithArgs(t); 494 | } 495 | ''')); 496 | }); 497 | 498 | test('optional inline', () async { 499 | await _expect('optionalInlineFunction', completion(''' 500 | class OptionalInlineFunction extends StatelessWidget { 501 | const OptionalInlineFunction( 502 | this.t, { 503 | Key? key, 504 | }) : super(key: key); 505 | 506 | final void Function()? t; 507 | 508 | @override 509 | Widget build(BuildContext _context) => optionalInlineFunction(t); 510 | } 511 | ''')); 512 | }); 513 | 514 | test('nested function', () async { 515 | await _expect('nestedFunction', completion(''' 516 | class NestedFunction extends StatelessWidget { 517 | const NestedFunction( 518 | this.t, { 519 | Key? key, 520 | }) : super(key: key); 521 | 522 | final void Function( 523 | void Function(int), 524 | int, 525 | ) t; 526 | 527 | @override 528 | Widget build(BuildContext _context) => nestedFunction(t); 529 | } 530 | ''')); 531 | }); 532 | 533 | test('generic class', () async { 534 | // currently not possible to know the type 535 | await _expect('genericClass', completion(''' 536 | class GenericClass extends StatelessWidget { 537 | const GenericClass( 538 | this.foo, { 539 | Key? key, 540 | }) : super(key: key); 541 | 542 | final T Function() foo; 543 | 544 | @override 545 | Widget build(BuildContext _context) => genericClass(foo); 546 | } 547 | ''')); 548 | }); 549 | 550 | test('generic class with nullable', () async { 551 | await _expect('genericClassWithNullable', completion(''' 552 | class GenericClassWithNullable extends StatelessWidget { 553 | const GenericClassWithNullable( 554 | this.foo, { 555 | Key? key, 556 | }) : super(key: key); 557 | 558 | final T? Function() foo; 559 | 560 | @override 561 | Widget build(BuildContext _context) => genericClassWithNullable(foo); 562 | } 563 | ''')); 564 | }); 565 | 566 | test('multiple generic class', () async { 567 | await _expect('genericMultiple', completion(''' 568 | class GenericMultiple extends StatelessWidget { 569 | const GenericMultiple( 570 | this.foo, 571 | this.bar, { 572 | Key? key, 573 | }) : super(key: key); 574 | 575 | final T foo; 576 | 577 | final S bar; 578 | 579 | @override 580 | Widget build(BuildContext _context) => genericMultiple( 581 | foo, 582 | bar, 583 | ); 584 | } 585 | ''')); 586 | }); 587 | 588 | test('generic function', () async { 589 | await _expect('genericFunction', completion(''' 590 | class GenericFunction extends StatelessWidget { 591 | const GenericFunction( 592 | this.foo, { 593 | Key? key, 594 | }) : super(key: key); 595 | 596 | final int Function(int) foo; 597 | 598 | @override 599 | Widget build(BuildContext _context) => genericFunction(foo); 600 | } 601 | ''')); 602 | }); 603 | 604 | test('generic function #2', () async { 605 | await _expect('genericFunction2', completion(''' 606 | class GenericFunction2 extends StatelessWidget { 607 | const GenericFunction2( 608 | this.foo, { 609 | Key? key, 610 | }) : super(key: key); 611 | 612 | final T Function(T) foo; 613 | 614 | @override 615 | Widget build(BuildContext _context) => genericFunction2(foo); 616 | } 617 | ''')); 618 | }); 619 | 620 | test('generic function #3', () async { 621 | await _expect('genericFunction3', completion(''' 622 | class GenericFunction3 extends StatelessWidget { 623 | const GenericFunction3( 624 | this.foo, { 625 | Key? key, 626 | }) : super(key: key); 627 | 628 | final String Function(int) foo; 629 | 630 | @override 631 | Widget build(BuildContext _context) => genericFunction3(foo); 632 | } 633 | ''')); 634 | }); 635 | 636 | test('generic function #4', () async { 637 | await _expect('genericFunction4', completion(''' 638 | class GenericFunction4 extends StatelessWidget { 639 | const GenericFunction4( 640 | this.foo, { 641 | Key? key, 642 | }) : super(key: key); 643 | 644 | final T? Function(T?)? foo; 645 | 646 | @override 647 | Widget build(BuildContext _context) => genericFunction4(foo); 648 | } 649 | ''')); 650 | }); 651 | }); 652 | 653 | group('custom named widgets', () { 654 | test('hook widget', () async { 655 | await _expect('hookWidgetWithCustomName', completion(''' 656 | class CustomHookWidget extends HookWidget { 657 | const CustomHookWidget({Key? key}) : super(key: key); 658 | 659 | @override 660 | Widget build(BuildContext _context) => hookWidgetWithCustomName(_context); 661 | } 662 | ''')); 663 | }); 664 | }); 665 | 666 | group('annotations', () { 667 | test('annotation', () async { 668 | await _expect('annotation', completion(''' 669 | class Annotation extends StatelessWidget { 670 | const Annotation({ 671 | Key? key, 672 | @TestAnnotation() this.foo = 42, 673 | }) : super(key: key); 674 | 675 | final int foo; 676 | 677 | @override 678 | Widget build(BuildContext _context) => annotation(foo: foo); 679 | } 680 | ''')); 681 | }); 682 | 683 | test('stateless widget', () async { 684 | await _expect('statelessWidgetWithCustomName', completion(''' 685 | class CustomStatelessWidget extends StatelessWidget { 686 | const CustomStatelessWidget({Key? key}) : super(key: key); 687 | 688 | @override 689 | Widget build(BuildContext _context) => 690 | statelessWidgetWithCustomName(_context); 691 | } 692 | ''')); 693 | }); 694 | 695 | test('annotationParameter', () async { 696 | await _expect('annotationParameter', completion(''' 697 | class AnnotationParameter extends StatelessWidget { 698 | const AnnotationParameter({ 699 | Key? key, 700 | @TestAnnotation('Test') this.foo = 42, 701 | }) : super(key: key); 702 | 703 | final int foo; 704 | 705 | @override 706 | Widget build(BuildContext _context) => annotationParameter(foo: foo); 707 | } 708 | ''')); 709 | }); 710 | 711 | test('annotationConstant', () async { 712 | await _expect('annotationConstant', completion(''' 713 | class AnnotationConstant extends StatelessWidget { 714 | const AnnotationConstant({ 715 | Key? key, 716 | @testAnnotation this.foo = 42, 717 | }) : super(key: key); 718 | 719 | final int foo; 720 | 721 | @override 722 | Widget build(BuildContext _context) => annotationConstant(foo: foo); 723 | } 724 | ''')); 725 | }); 726 | }); 727 | }); 728 | } 729 | -------------------------------------------------------------------------------- /packages/functional_widget_annotation/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.10.0 2 | 3 | - Changed the logic for how the generated widget name is determined. (thanks to @doejon) 4 | Now, defining: 5 | 6 | ```dart 7 | @swidget 8 | Widget first() {} 9 | 10 | @swidget 11 | Widget _second() {} 12 | 13 | @swidget 14 | Widget __third() {} 15 | ``` 16 | 17 | generates: 18 | 19 | ```dart 20 | class First {...} 21 | class Second {...} 22 | class _Third {...} 23 | ``` 24 | 25 | That gives more control over whether the generated class is public or private. 26 | 27 | - Added support for manually overriding the generated widget name (thanks to @doejon) 28 | 29 | ``` 30 | @FunctionalWidget(name: 'MyClass') 31 | Widget _myWidget() {} 32 | ``` 33 | 34 | - Correctly support advanced annotations on widget parameters (thanks to @Almighty-Alpaca) 35 | 36 | - Upgraded dependencies to support analyzer 3.0.0 and 4.0.0 (thanks to @realshovanshah) 37 | 38 | ## 0.9.2 39 | 40 | Added support for `HookConsumerWidget` and `ConsumerWidget` from [Riverpod](https://pub.dev/packages/riverpod) (thanks to @tim-smart) 41 | 42 | ## 0.9.0+1 43 | 44 | Fix homepage URL 45 | 46 | ## 0.9.0 47 | 48 | Migrated to null-safety (thanks to @tim-smart) 49 | 50 | ## 0.8.0 51 | 52 | - removed `@swidget` 53 | - removed `FunctionalWidgetEquality.equal` 54 | - removed `FunctionalWidgetEquality.identical` 55 | 56 | ## 0.5.3 57 | 58 | - deprecated `@swidget` 59 | - deprecated `FunctionalWidgetEquality.equal` 60 | - deprecated `FunctionalWidgetEquality.identical` 61 | 62 | ## 0.0.1 63 | 64 | Initial release 65 | -------------------------------------------------------------------------------- /packages/functional_widget_annotation/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Remi Rousselet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/functional_widget_annotation/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/rrousselGit/functional_widget.svg?branch=master)](https://travis-ci.org/rrousselGit/functional_widget) 2 | [![pub package](https://img.shields.io/pub/v/functional_widget.svg)](https://pub.dartlang.org/packages/functional_widget) 3 | 4 | 5 | A package containing the necessary annotations for [functional_widget](https://github.com/rrousselGit/functional_widget/tree/master/functional_widget) code generator to work 6 | -------------------------------------------------------------------------------- /packages/functional_widget_annotation/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | analyzer: 3 | strong-mode: 4 | implicit-casts: false 5 | implicit-dynamic: false 6 | linter: 7 | rules: 8 | - always_declare_return_types 9 | - always_put_control_body_on_new_line 10 | - always_put_required_named_parameters_first 11 | - annotate_overrides 12 | - avoid_annotating_with_dynamic 13 | - avoid_bool_literals_in_conditional_expressions 14 | - avoid_catching_errors 15 | - avoid_classes_with_only_static_members 16 | - avoid_double_and_int_checks 17 | - avoid_empty_else 18 | - avoid_field_initializers_in_const_classes 19 | - avoid_function_literals_in_foreach_calls 20 | - avoid_implementing_value_types 21 | - avoid_init_to_null 22 | - avoid_js_rounded_ints 23 | - avoid_null_checks_in_equality_operators 24 | - avoid_private_typedef_functions 25 | - avoid_relative_lib_imports 26 | - avoid_renaming_method_parameters 27 | - avoid_return_types_on_setters 28 | - avoid_returning_null_for_void 29 | - avoid_returning_this 30 | - avoid_setters_without_getters 31 | - avoid_single_cascade_in_expression_statements 32 | - avoid_slow_async_io 33 | - avoid_types_as_parameter_names 34 | - avoid_unused_constructor_parameters 35 | - avoid_void_async 36 | - await_only_futures 37 | - camel_case_types 38 | - cancel_subscriptions 39 | - cascade_invocations 40 | - close_sinks 41 | - comment_references 42 | - constant_identifier_names 43 | - control_flow_in_finally 44 | - curly_braces_in_flow_control_structures 45 | - directives_ordering 46 | - empty_catches 47 | - empty_constructor_bodies 48 | - empty_statements 49 | - file_names 50 | - flutter_style_todos 51 | - hash_and_equals 52 | - implementation_imports 53 | - join_return_with_assignment 54 | - library_names 55 | - library_prefixes 56 | - literal_only_boolean_expressions 57 | - no_adjacent_strings_in_list 58 | - no_duplicate_case_values 59 | - non_constant_identifier_names 60 | - null_closures 61 | - omit_local_variable_types 62 | - one_member_abstracts 63 | - only_throw_errors 64 | - overridden_fields 65 | - package_names 66 | - package_prefixed_library_names 67 | - parameter_assignments 68 | - prefer_adjacent_string_concatenation 69 | - prefer_asserts_in_initializer_lists 70 | - prefer_collection_literals 71 | - prefer_conditional_assignment 72 | - prefer_const_constructors 73 | - prefer_const_constructors_in_immutables 74 | - prefer_const_declarations 75 | - prefer_const_literals_to_create_immutables 76 | - prefer_constructors_over_static_methods 77 | - prefer_contains 78 | - prefer_expression_function_bodies 79 | - prefer_final_fields 80 | - prefer_final_locals 81 | - prefer_foreach 82 | - prefer_function_declarations_over_variables 83 | - prefer_generic_function_type_aliases 84 | - prefer_initializing_formals 85 | - prefer_int_literals 86 | - prefer_interpolation_to_compose_strings 87 | - prefer_is_empty 88 | - prefer_is_not_empty 89 | - prefer_iterable_whereType 90 | - prefer_mixin 91 | - prefer_single_quotes 92 | - prefer_typing_uninitialized_variables 93 | - prefer_void_to_null 94 | - recursive_getters 95 | - slash_for_doc_comments 96 | - sort_constructors_first 97 | - sort_pub_dependencies 98 | - sort_unnamed_constructors_first 99 | - test_types_in_equals 100 | - throw_in_finally 101 | - type_annotate_public_apis 102 | - type_init_formals 103 | - unawaited_futures 104 | - unnecessary_brace_in_string_interps 105 | - unnecessary_const 106 | - unnecessary_getters_setters 107 | - unnecessary_lambdas 108 | - unnecessary_new 109 | - unnecessary_null_aware_assignments 110 | - unnecessary_null_in_if_null_operators 111 | - unnecessary_overrides 112 | - unnecessary_parenthesis 113 | - unnecessary_statements 114 | - unnecessary_this 115 | - unrelated_type_equality_checks 116 | - use_rethrow_when_possible 117 | - use_string_buffers 118 | - use_to_and_as_if_applicable 119 | - valid_regexps 120 | - void_checks 121 | -------------------------------------------------------------------------------- /packages/functional_widget_annotation/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | -------------------------------------------------------------------------------- /packages/functional_widget_annotation/example/analysis_options.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/functional_widget/6e676977e53258f3aa5604927df3abde31939089/packages/functional_widget_annotation/example/analysis_options.yaml -------------------------------------------------------------------------------- /packages/functional_widget_annotation/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:functional_widget_annotation/functional_widget_annotation.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | part 'main.g.dart'; 5 | 6 | // we create a widget using a widget decorated by `@swidget` 7 | @swidget 8 | Widget foo() { 9 | return Container(); 10 | } 11 | 12 | void main() => runApp( 13 | // we use the generated class 14 | const Foo(), 15 | ); 16 | -------------------------------------------------------------------------------- /packages/functional_widget_annotation/example/lib/main.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'main.dart'; 4 | 5 | // ************************************************************************** 6 | // FunctionalWidgetGenerator 7 | // ************************************************************************** 8 | 9 | class Foo extends StatelessWidget { 10 | const Foo({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext _context) => foo(); 14 | } 15 | -------------------------------------------------------------------------------- /packages/functional_widget_annotation/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: functional_widget_annotation_example 2 | description: A new Flutter project. 3 | publish_to: none 4 | 5 | environment: 6 | sdk: ^3.6.0 7 | 8 | resolution: workspace 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | functional_widget_annotation: 14 | 15 | dev_dependencies: 16 | functional_widget: 17 | build_runner: ^2.0.0 18 | -------------------------------------------------------------------------------- /packages/functional_widget_annotation/lib/functional_widget_annotation.dart: -------------------------------------------------------------------------------- 1 | /// Determines which type of widget will be generated 2 | /// 3 | /// Defaults to [stateless]. 4 | enum FunctionalWidgetType { 5 | /// Will generate a `HookWidget`, from `flutter_hooks` package. 6 | /// 7 | /// `HookWidget` must be installed as a separate dependency: 8 | /// ```yaml 9 | /// dependencies: 10 | /// flutter_hooks: any 11 | /// ``` 12 | hook, 13 | 14 | /// Will generate a `HookConsumerWidget`, from `hooks_riverpod` package. 15 | /// 16 | /// `HookConsumerWidget` must be installed as a separate dependency: 17 | /// ```yaml 18 | /// dependencies: 19 | /// hooks_riverpod: any 20 | /// ``` 21 | hookConsumer, 22 | 23 | /// Will generate a `ConsumerWidget`, from `flutter_riverpod` package. 24 | /// 25 | /// `ConsumerWidget` must be installed as a separate dependency: 26 | /// ```yaml 27 | /// dependencies: 28 | /// riverpod: any 29 | /// ``` 30 | consumer, 31 | 32 | /// Will generate a `StatelessWidget`. 33 | stateless, 34 | } 35 | 36 | /// Decorates a function to customize the generated class 37 | class FunctionalWidget { 38 | const FunctionalWidget({ 39 | this.widgetType = FunctionalWidgetType.stateless, 40 | this.debugFillProperties, 41 | this.name, 42 | }); 43 | 44 | /// Configures which types of widget is generated. 45 | /// 46 | /// Defaults to [FunctionalWidgetType.stateless]. 47 | final FunctionalWidgetType widgetType; 48 | 49 | /// Defines if the generated widget should emit diagnostics informations. 50 | final bool? debugFillProperties; 51 | 52 | /// Defines the name of the generated widget. 53 | /// 54 | /// By default, uses the function name, such that: 55 | /// 56 | /// - `Widget _myWidget(...)` generates `class MyWidget` 57 | /// - `Widget __myWidget(...)` generates `class _MyWidget` 58 | final String? name; 59 | } 60 | 61 | /// A decorator for functions to generate a `StatelessWidget`. 62 | /// 63 | /// The name of the generated widget is the name of the decorated function, 64 | /// with an uppercase as first letter. 65 | const FunctionalWidget swidget = FunctionalWidget( 66 | widgetType: FunctionalWidgetType.stateless, 67 | ); 68 | 69 | /// A decorator for functions to generate a `HookWidget`. 70 | /// 71 | /// `HookWidget` must be installed as a separate dependency: 72 | /// ```yaml 73 | /// dependencies: 74 | /// flutter_hooks: any 75 | /// ``` 76 | /// 77 | /// The name of the generated widget is the name of the decorated function, 78 | /// with an uppercase as first letter. 79 | const FunctionalWidget hwidget = FunctionalWidget( 80 | widgetType: FunctionalWidgetType.hook, 81 | ); 82 | 83 | /// A decorator for functions to generate a `HookConsumerWidget`. 84 | /// 85 | /// `HookConsumerWidget` must be installed as a separate dependency: 86 | /// ```yaml 87 | /// dependencies: 88 | /// hooks_riverpod: any 89 | /// ``` 90 | /// 91 | /// The name of the generated widget is the name of the decorated function, 92 | /// with an uppercase as first letter. 93 | const FunctionalWidget hcwidget = FunctionalWidget( 94 | widgetType: FunctionalWidgetType.hookConsumer, 95 | ); 96 | 97 | /// A decorator for functions to generate a `ConsumerWidget`. 98 | /// 99 | /// `ConsumerWidget` must be installed as a separate dependency: 100 | /// ```yaml 101 | /// dependencies: 102 | /// riverpod: any 103 | /// ``` 104 | /// 105 | /// The name of the generated widget is the name of the decorated function, 106 | /// with an uppercase as first letter. 107 | const FunctionalWidget cwidget = FunctionalWidget( 108 | widgetType: FunctionalWidgetType.consumer, 109 | ); 110 | -------------------------------------------------------------------------------- /packages/functional_widget_annotation/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: functional_widget_annotation 2 | description: Annotations for function_widget_generator used to generate widget classes from a function 3 | homepage: https://github.com/rrousselGit/functional_widget/tree/master/packages/functional_widget_annotation 4 | version: 0.10.0 5 | 6 | environment: 7 | sdk: ^3.6.0 8 | 9 | resolution: workspace 10 | 11 | dev_dependencies: 12 | pedantic: ^1.9.2 13 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: _ 2 | publish_to: none 3 | 4 | environment: 5 | sdk: ^3.6.0 6 | 7 | workspace: 8 | - packages/code_gen_tester 9 | - packages/functional_widget 10 | - packages/functional_widget/example 11 | - packages/functional_widget_annotation 12 | - packages/functional_widget_annotation/example 13 | --------------------------------------------------------------------------------