├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── auto_data ├── LICENSE ├── lib │ ├── auto_data.dart │ └── src │ │ └── annotations.dart └── pubspec.yaml ├── auto_data_generator ├── .gitignore ├── .metadata ├── LICENSE ├── build.yaml ├── lib │ ├── auto_data_generator.dart │ └── src │ │ ├── auto_data_generator.dart │ │ └── file_generator.dart ├── pubspec.yaml └── test │ └── auto_data_generator_test.dart ├── example ├── .gitignore ├── lib │ ├── bar.dart │ ├── foo.dart │ ├── main.dart │ ├── person.dart │ └── point.dart └── pubspec.yaml └── tools ├── post-publish.sh └── pre-publish.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .buildlog 2 | .dart_tool/ 3 | .DS_Store 4 | .idea/ 5 | .pub/ 6 | .settings/ 7 | *.iml 8 | **/build 9 | **/packages 10 | **/pubspec.lock 11 | **/.packages -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.3 2 | 3 | * Added support for toJson and fromJson 4 | * Renamed toFirebaseMap/fromFirebaseMap -> toMap/fromMap 5 | 6 | ## 0.0.2 7 | 8 | * Various package structure fixes 9 | 10 | ## [0.0.1] - July 24, 2019. 11 | 12 | * Initial release. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Paul DeMarco. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Buffalo PC Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # auto_data 2 | 3 | Generate simple data classes for Dart. 4 | 5 | A data class is an immutable class meant to hold data, similar to Kotlin's [data class](https://kotlinlang.org/docs/reference/data-classes.html). 6 | 7 | ## Example 8 | 9 | Specify the data class using the `@data` annotation: 10 | 11 | ```dart 12 | @data 13 | class $Point { 14 | double x; 15 | double y; 16 | } 17 | ``` 18 | 19 | Enjoy your generated named constructor, ==/hashCode, toString, copyWith, and serialization: 20 | 21 | ```dart 22 | // GENERATED CODE - DO NOT MODIFY BY HAND 23 | @immutable 24 | class Point { 25 | final double x; 26 | final double y; 27 | 28 | const Point({ 29 | @required this.x, 30 | @required this.y, 31 | }); 32 | 33 | @override 34 | bool operator ==(Object other) => 35 | identical(this, other) || 36 | other is Point && 37 | runtimeType == other.runtimeType && 38 | x == other.x && 39 | y == other.y; 40 | 41 | @override 42 | int get hashCode => x.hashCode ^ y.hashCode; 43 | 44 | @override 45 | String toString() { 46 | return 'Point{x: ' + x.toString() + ', y: ' + y.toString() + '}'; 47 | } 48 | 49 | Point copyWith({ 50 | double x, 51 | double y, 52 | }) { 53 | return Point( 54 | x: x ?? this.x, 55 | y: y ?? this.y, 56 | ); 57 | } 58 | 59 | Point.fromMap(Map m) 60 | : x = m['x'], 61 | y = m['y']; 62 | 63 | Map toMap() => {'x': x, 'y': y}; 64 | 65 | factory Point.fromJson(String json) => Point.fromMap(jsonDecode(json)); 66 | 67 | String toJson() => jsonEncode(toMap()); 68 | } 69 | ``` 70 | 71 | ## Requirements 72 | 73 | Add the following to your pubspec.yaml: 74 | 75 | ```yaml 76 | dependencies: 77 | auto_data: ^0.0.3 78 | 79 | dev_dependencies: 80 | build_runner: ^1.0.0 81 | auto_data_generator: ^0.0.3 82 | ``` 83 | 84 | Create your `point.dart` file with correct imports: 85 | 86 | ```dart 87 | import 'package:meta/meta.dart'; 88 | import 'package:collection/collection.dart'; 89 | import 'package:auto_data/auto_data.dart'; 90 | import 'dart:convert'; 91 | 92 | part 'point.g.dart'; 93 | 94 | @data 95 | class $Point { 96 | double x; 97 | double y; 98 | } 99 | ``` 100 | 101 | Lastly, generate using build_runner: 102 | 103 | pub run build_runner build 104 | 105 | or 106 | 107 | pub run build_runner watch 108 | 109 | Use your generated `Point` class: 110 | 111 | ```dart 112 | import 'point.dart'; 113 | 114 | final p1 = Point(x: 0, y: 1); 115 | final p2 = Point(x: 0, y: 2); 116 | assert(p1 != p2); 117 | final p3 = p1.copyWith(y: 2); 118 | assert(p2 == p3); 119 | print(p3.toString()); 120 | ``` 121 | 122 | ## Advanced usage 123 | 124 | ```dart 125 | import 'package:collection/collection.dart'; 126 | import 'package:meta/meta.dart'; 127 | import 'package:auto_data/auto_data.dart'; 128 | import 'dart:convert'; 129 | import 'foo.dart'; 130 | 131 | part 'person.g.dart'; 132 | 133 | /// All comments are copied to generated code 134 | @data 135 | class $Person { 136 | /// This field gets a default value 137 | String name = 'Paul'; 138 | 139 | /// This field is not required 140 | @nullable 141 | double weight; 142 | 143 | /// Age of the person 144 | int age; 145 | 146 | /// Depend on another generated class 147 | $Foo foo; 148 | 149 | /// Deep comparison of lists 150 | List<$Person> friends; 151 | 152 | /// Custom constructors are copied over 153 | $Person.genius() 154 | : name = 'Albert', 155 | age = 140; 156 | } 157 | ``` 158 | 159 | ## Todo's 160 | 161 | - [ ] Optional constructor types (named, private, const, etc) 162 | - [x] Custom constructors should be copied over ([Issue #1](https://github.com/pauldemarco/auto_data/issues/1)) 163 | - [x] Default values by assigning during declaration: `String name = 'Paul';` 164 | - [x] Add @nullable annotation for fields that are not required 165 | - [ ] Deep immutability for Map 166 | - [x] Deep immutability for List 167 | - [x] Serialization toMap/fromMap 168 | - [x] Serialization toJson/fromJson 169 | 170 | ## Limitations 171 | 172 | ### Cannot set values to null with copyWith 173 | 174 | Ex) Clearing a user's avatar image: 175 | 176 | ```dart 177 | profile = profile.copyWith(imageUrl: null); // This won't have an effect since copyWith ignores null input parameters. 178 | ``` 179 | 180 | ## References 181 | 182 | 1. [Issue: Statically tracked shared immutable objects](https://github.com/dart-lang/language/issues/125) 183 | 1. [Proposal: Shared immutable objects](https://github.com/dart-lang/language/blob/master/working/0125-static-immutability/feature-specification.md) 184 | 1. [Patterns and related features](https://github.com/dart-lang/language/issues/546) -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: 3 | implicit-casts: false 4 | # implicit-dynamic: false 5 | 6 | # Add this to your project `analysis_options.yaml` to enable the built_value 7 | # analyzer plugin. You should also add the top level `plugins` clause below. 8 | # The plugin surfaces generator errors in your IDE, with suggested fixes 9 | # where possible. 10 | plugins: 11 | auto_data_generator 12 | 13 | # Add this to your project analysis_options.yaml to enable the built_value 14 | # analyzer plugin. You should also add the `plugins` clause under `analyzer`, 15 | # above. 16 | plugins: 17 | auto_data_generator: 18 | enabled: true 19 | 20 | linter: 21 | rules: 22 | # Error Rules 23 | - avoid_empty_else 24 | - cancel_subscriptions 25 | # - close_sinks 26 | - comment_references 27 | - control_flow_in_finally 28 | - empty_statements 29 | - hash_and_equals 30 | - invariant_booleans 31 | - iterable_contains_unrelated_type 32 | - list_remove_unrelated_type 33 | - literal_only_boolean_expressions 34 | - test_types_in_equals 35 | - throw_in_finally 36 | - unrelated_type_equality_checks 37 | - valid_regexps 38 | 39 | # Style Rules 40 | # - always_declare_return_types 41 | # - always_specify_types 42 | - annotate_overrides 43 | # - avoid_as 44 | - avoid_init_to_null 45 | - avoid_return_types_on_setters 46 | - await_only_futures 47 | - camel_case_types 48 | # - cascade_invocations 49 | - curly_braces_in_flow_control_structures 50 | - constant_identifier_names 51 | - empty_catches 52 | - empty_constructor_bodies 53 | - implementation_imports 54 | - library_names 55 | - library_prefixes 56 | - non_constant_identifier_names 57 | # - one_member_abstracts 58 | - only_throw_errors 59 | - overridden_fields 60 | - package_api_docs 61 | - package_prefixed_library_names 62 | - prefer_equal_for_default_values 63 | - prefer_is_not_empty 64 | # - parameter_assignments 65 | - prefer_final_fields 66 | - prefer_final_locals 67 | # - public_member_api_docs 68 | - slash_for_doc_comments 69 | # - sort_constructor_first 70 | - sort_unnamed_constructors_first 71 | - super_goes_last 72 | - type_annotate_public_apis 73 | - type_init_formals 74 | - unawaited_futures 75 | - unnecessary_brace_in_string_interps 76 | - unnecessary_getters_setters -------------------------------------------------------------------------------- /auto_data/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Paul DeMarco. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Buffalo PC Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /auto_data/lib/auto_data.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Paul DeMarco. 2 | // 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 | library auto_data; 6 | 7 | part 'src/annotations.dart'; 8 | -------------------------------------------------------------------------------- /auto_data/lib/src/annotations.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Paul DeMarco. 2 | // 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 | part of auto_data; 6 | 7 | class Data { 8 | /// Mark a class for data generation 9 | const Data(); 10 | 11 | String toString() => "Data class"; 12 | } 13 | 14 | /// Marks a class as [Data]. 15 | const Data data = const Data(); 16 | 17 | class Nullable { 18 | /// Allow a field to be nullable 19 | const Nullable(); 20 | 21 | String toString() => "Nullable class"; 22 | } 23 | 24 | /// Marks a field as [Nullable]. 25 | const Nullable nullable = const Nullable(); 26 | -------------------------------------------------------------------------------- /auto_data/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: auto_data 2 | description: > 3 | Automatically generate simple data class files for Dart. 4 | This is the runtime dependency. 5 | version: 0.0.3 6 | author: Paul DeMarco 7 | homepage: https://www.github.com/pauldemarco/auto_data 8 | 9 | environment: 10 | sdk: ">=2.1.0 <3.0.0" 11 | 12 | dependencies: 13 | 14 | dev_dependencies: 15 | test: -------------------------------------------------------------------------------- /auto_data_generator/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | ios/.generated/ 9 | ios/Flutter/Generated.xcconfig 10 | ios/Runner/GeneratedPluginRegistrant.* 11 | -------------------------------------------------------------------------------- /auto_data_generator/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 891036c9b93f9c805d84c8d446676012e1ee36ac 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /auto_data_generator/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Paul DeMarco. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Buffalo PC Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /auto_data_generator/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | auto_data_generator|auto_data: 5 | enabled: true 6 | 7 | builders: 8 | auto_data: 9 | target: ":auto_data_generator" 10 | import: "package:auto_data_generator/auto_data_generator.dart" 11 | builder_factories: ["autoData"] 12 | build_extensions: {".dart": [".auto_data.g.part"]} 13 | auto_apply: dependents 14 | build_to: cache 15 | applies_builders: ["source_gen|combining_builder"] -------------------------------------------------------------------------------- /auto_data_generator/lib/auto_data_generator.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Paul DeMarco. 2 | // 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 | library auto_data_generator; 6 | 7 | import 'dart:async'; 8 | import 'package:build/build.dart'; 9 | import 'package:source_gen/source_gen.dart'; 10 | import 'package:auto_data/auto_data.dart'; 11 | import 'package:analyzer/dart/element/visitor.dart'; 12 | import 'package:analyzer/dart/element/element.dart'; 13 | 14 | part 'src/auto_data_generator.dart'; 15 | part 'src/file_generator.dart'; 16 | -------------------------------------------------------------------------------- /auto_data_generator/lib/src/auto_data_generator.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Paul DeMarco. 2 | // 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 | part of auto_data_generator; 6 | 7 | Builder autoData(BuilderOptions _) => 8 | new SharedPartBuilder([new AutoDataGenerator()], 'auto_data'); 9 | 10 | class DataClass { 11 | final String name; 12 | final List props; 13 | final List constructors; 14 | final List fieldElements; 15 | final List constElements; 16 | final String documentationComment; 17 | 18 | DataClass( 19 | this.name, 20 | this.props, 21 | this.constructors, 22 | this.fieldElements, 23 | this.constElements, { 24 | this.documentationComment, 25 | }); 26 | } 27 | 28 | class DataClassConstructor { 29 | final String declaration; 30 | final String documentationComment; 31 | 32 | DataClassConstructor(this.declaration, [this.documentationComment]); 33 | } 34 | 35 | class DataClassProperty { 36 | final String name; 37 | final String type; 38 | final bool isNullable; 39 | final bool isEnum; 40 | final String assignmentString; 41 | final String documentationComment; 42 | 43 | DataClassProperty( 44 | this.name, 45 | this.type, 46 | this.isNullable, 47 | this.isEnum, [ 48 | this.assignmentString, 49 | this.documentationComment, 50 | ]); 51 | } 52 | 53 | class AutoDataGenerator extends Generator { 54 | const AutoDataGenerator(); 55 | 56 | @override 57 | Future generate(LibraryReader library, BuildStep buildStep) async { 58 | final classes = List(); 59 | library.annotatedWith(TypeChecker.fromRuntime(Data)).forEach((e) { 60 | final visitor = DataElementVisitor(library); 61 | e.element.visitChildren(visitor); 62 | final c = DataClass( 63 | e.element.name.substring(1), 64 | visitor.props, 65 | visitor.constructors, 66 | visitor.fieldElements, 67 | visitor.constElements, 68 | documentationComment: e.element.documentationComment, 69 | ); 70 | classes.add(c); 71 | }); 72 | 73 | final result = FileGenerator.generate(classes); 74 | 75 | if (result.length > 0) { 76 | return result.toString().replaceAll('\$', ''); 77 | } 78 | 79 | return null; 80 | } 81 | } 82 | 83 | class DataElementVisitor extends SimpleElementVisitor { 84 | final List props = []; 85 | final List constructors = []; 86 | final List fieldElements = []; 87 | final List constElements = []; 88 | 89 | final LibraryReader library; 90 | 91 | DataElementVisitor(this.library); 92 | 93 | @override 94 | T visitFieldElement(FieldElement element) { 95 | props.add(_parseFieldElement(element)); 96 | fieldElements.add(element); 97 | } 98 | 99 | @override 100 | T visitConstructorElement(ConstructorElement element) { 101 | final parsedLibrary = 102 | element.session.getParsedLibraryByElement(element.library); 103 | final declaration = parsedLibrary.getElementDeclaration(element); 104 | if (declaration != null) { 105 | var s = declaration.node.toSource(); 106 | s = s.startsWith('\$') ? s.substring(1) : s; 107 | constructors.add(DataClassConstructor(s, element.documentationComment)); 108 | constElements.add(element); 109 | } 110 | } 111 | 112 | DataClassProperty _parseFieldElement(FieldElement element) { 113 | final parsedLibrary = 114 | element.session.getParsedLibraryByElement(element.library); 115 | final declaration = parsedLibrary.getElementDeclaration(element); 116 | final ee = library.findType(element.type.name); 117 | final name = element.name; 118 | final type = element.type.displayName; 119 | final comment = element.documentationComment; 120 | final isNullable = element.metadata.any((a) => a.toSource() == '@nullable'); 121 | final isEnum = ee?.isEnum ?? false; 122 | var assignmentString = declaration.node.toSource(); 123 | assignmentString = assignmentString.substring(name.length); 124 | if (assignmentString.length <= 0) { 125 | assignmentString = null; 126 | } 127 | return DataClassProperty( 128 | name, type, isNullable, isEnum, assignmentString, comment); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /auto_data_generator/lib/src/file_generator.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Paul DeMarco. 2 | // 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 | part of auto_data_generator; 6 | 7 | class FileGenerator { 8 | static final _regexTypeSpecifier = RegExp(r'\<(.*?)\>'); 9 | 10 | static StringBuffer generate(List classes) { 11 | final buffer = new StringBuffer(); 12 | classes.forEach((dataClass) { 13 | buffer.write(_generateClassHeader(dataClass)); 14 | buffer.writeln(_generateFinalFields(dataClass)); 15 | buffer.writeln(_generateNamedConstructor(dataClass)); 16 | buffer.writeln(_generateOtherConstructors(dataClass)); 17 | buffer.writeln(_generateOperatorEquals(dataClass)); 18 | buffer.writeln(_generateHashCode(dataClass)); 19 | buffer.writeln(_generateToString(dataClass)); 20 | buffer.writeln(_generateCopyWith(dataClass)); 21 | buffer.writeln(_generateFromMap(dataClass)); 22 | buffer.writeln(_generateToMap(dataClass)); 23 | buffer.writeln(_generateFromJson(dataClass)); 24 | buffer.writeln(_generateToJson(dataClass)); 25 | buffer.write(_generateClassFooter(dataClass)); 26 | }); 27 | return buffer; 28 | } 29 | 30 | static String camelToSnakeCase(String name) { 31 | final buffer = new StringBuffer(); 32 | for (var c in name.codeUnits) { 33 | if (c >= 65 && c <= 90) { 34 | c += 32; 35 | if (buffer.length > 0) { 36 | buffer.write('_'); 37 | } 38 | } 39 | buffer.writeCharCode(c); 40 | } 41 | return buffer.toString(); 42 | } 43 | 44 | static StringBuffer _generateClassHeader(DataClass c) { 45 | final buffer = new StringBuffer(); 46 | if (c.documentationComment != null) { 47 | buffer.writeln(c.documentationComment); 48 | } 49 | buffer.writeln('@immutable'); 50 | buffer.writeln('class ${c.name} {'); 51 | return buffer; 52 | } 53 | 54 | static String _generateClassFooter(DataClass c) { 55 | return '}\n'; 56 | } 57 | 58 | static StringBuffer _generateFinalFields(DataClass c) { 59 | final buffer = new StringBuffer(); 60 | c.props.forEach((p) { 61 | if (p.documentationComment != null) { 62 | buffer.writeln(p.documentationComment); 63 | } 64 | buffer.write('final ${p.type} ${p.name};\n'); 65 | }); 66 | return buffer; 67 | } 68 | 69 | static StringBuffer _generateNamedConstructor(DataClass c) { 70 | final buffer = new StringBuffer(); 71 | // FIXME: List.unmodifiable causes error: Initializer expressions in constant constructors must be constants. 72 | if (!c.props.any((p) => p.type.startsWith('List'))) { 73 | buffer.write('const '); 74 | } 75 | buffer.write('${c.name}({'); 76 | 77 | c.props.forEach((p) { 78 | if (!p.isNullable && p.assignmentString == null) { 79 | buffer.write('@required '); 80 | } 81 | if (p.type.startsWith('List')) { 82 | buffer.write('${p.type} ${p.name}'); 83 | } else { 84 | buffer.write('this.${p.name}'); 85 | } 86 | if (p.assignmentString != null) { 87 | buffer.write(p.assignmentString); 88 | } 89 | buffer.write(', '); 90 | }); 91 | 92 | buffer.write('})'); 93 | if (c.props.any((p) => p.type.startsWith('List'))) { 94 | buffer.write(':'); 95 | final initializers = c.props.where((p) => p.type.startsWith('List')).map( 96 | (p) => 97 | '${p.name} = (${p.name} != null) ? List.unmodifiable(${p.name}) : null'); 98 | buffer.write(initializers.join(',')); 99 | } 100 | 101 | buffer.writeln(';'); 102 | return buffer; 103 | } 104 | 105 | static StringBuffer _generateOtherConstructors(DataClass c) { 106 | final buffer = new StringBuffer(); 107 | c.constructors.forEach((c) { 108 | if (c.documentationComment != null) { 109 | buffer.writeln(c.documentationComment); 110 | } 111 | buffer.writeln(c.declaration.replaceAll('\$', '')); 112 | buffer.writeln(''); 113 | }); 114 | return buffer; 115 | } 116 | 117 | static StringBuffer _generateOperatorEquals(DataClass c) { 118 | final buffer = new StringBuffer(); 119 | buffer.writeln('@override'); 120 | buffer.writeln('bool operator ==(Object other) =>'); 121 | buffer.writeln('identical(this, other) ||'); 122 | buffer.writeln('other is ${c.name} &&'); 123 | buffer.writeln('runtimeType == other.runtimeType &&'); 124 | 125 | final params = c.props.map((p) { 126 | if (p.type.startsWith('List')) { 127 | return 'const ListEquality().equals(${p.name}, other.${p.name})'; 128 | } else { 129 | return '${p.name} == other.${p.name}'; 130 | } 131 | }).join(' && '); 132 | buffer.write(params); 133 | 134 | buffer.writeln(';'); 135 | return buffer; 136 | } 137 | 138 | static StringBuffer _generateHashCode(DataClass c) { 139 | final buffer = new StringBuffer(); 140 | buffer.writeln('@override'); 141 | buffer.write('int get hashCode => '); 142 | 143 | final params = c.props.map((p) => '${p.name}.hashCode').join(' ^ '); 144 | buffer.write(params); 145 | 146 | buffer.writeln(';'); 147 | return buffer; 148 | } 149 | 150 | static StringBuffer _generateToString(DataClass c) { 151 | final buffer = new StringBuffer(); 152 | buffer.writeln('@override'); 153 | buffer.writeln('String toString() {'); 154 | buffer.write('return \'${c.name}{'); 155 | 156 | final params = 157 | c.props.map((p) => '${p.name}: \'+${p.name}.toString()+\'').join(', '); 158 | buffer.write(params); 159 | 160 | buffer.writeln('}\';'); 161 | 162 | buffer.writeln('}'); 163 | return buffer; 164 | } 165 | 166 | static StringBuffer _generateCopyWith(DataClass c) { 167 | final buffer = new StringBuffer(); 168 | buffer.writeln('${c.name} copyWith({'); 169 | 170 | c.props.forEach((p) { 171 | buffer.writeln('${p.type} ${p.name},'); 172 | }); 173 | 174 | buffer.writeln('}) {'); 175 | 176 | buffer.writeln('return ${c.name}('); 177 | 178 | c.props.forEach((p) { 179 | final name = p.name; 180 | buffer.write('$name: '); 181 | if (p.type.startsWith('List')) { 182 | buffer.write( 183 | '($name != null) ? ($name == this.$name) ? $name.sublist(0) : $name :'); 184 | } else { 185 | buffer.writeln('$name ?? '); 186 | } 187 | buffer.writeln('this.$name, '); 188 | }); 189 | 190 | buffer.writeln(');'); 191 | 192 | buffer.writeln('}'); 193 | return buffer; 194 | } 195 | 196 | static StringBuffer _generateFromMap(DataClass c) { 197 | String _getConverter( 198 | String name, String type, String argument, bool isEnum) { 199 | if (type.startsWith('DateTime')) { 200 | return 'DateTime.fromMillisecondsSinceEpoch($argument)'; 201 | } else if (type.startsWith('\$')) { 202 | return '$type.fromMap($argument)'; 203 | } else if (type.startsWith('List')) { 204 | final buffer = new StringBuffer(); 205 | buffer.write('(m[\'$name\'] as Map).values'); 206 | if (_regexTypeSpecifier.hasMatch(type)) { 207 | final listType = _regexTypeSpecifier.firstMatch(type).group(1); 208 | final converter = _getConverter(name, listType, 'm', false); 209 | buffer.write('.map((m) => $converter)'); 210 | } 211 | buffer.write('.toList()'); 212 | return buffer.toString(); 213 | } else if (isEnum) { 214 | return '$type.values[$argument]'; 215 | } else { 216 | return '$argument'; 217 | } 218 | } 219 | 220 | final buffer = new StringBuffer(); 221 | buffer.writeln('${c.name}.fromMap(Map m):'); 222 | 223 | final params = c.props.map((p) { 224 | var assignment = 225 | _getConverter(p.name, p.type, 'm[\'${p.name}\']', p.isEnum); 226 | if (p.isNullable && assignment != 'm[\'${p.name}\']') { 227 | assignment = 'm[\'${p.name}\'] != null ? $assignment : null'; 228 | } 229 | return '${p.name} = $assignment'; 230 | }).join(','); 231 | 232 | buffer.write(params); 233 | 234 | buffer.writeln(';'); 235 | return buffer; 236 | } 237 | 238 | static StringBuffer _generateToMap(DataClass c) { 239 | String _getConverter(String name, String type, bool isEnum) { 240 | if (type.startsWith('DateTime')) { 241 | return '$name.millisecondsSinceEpoch'; 242 | } else if (type.startsWith('\$')) { 243 | return '$name.toMap()'; 244 | } else if (type.startsWith('List')) { 245 | final buffer = new StringBuffer(); 246 | buffer.write('Map.fromIterable($name,'); 247 | if (_regexTypeSpecifier.hasMatch(type)) { 248 | final listType = _regexTypeSpecifier.firstMatch(type).group(1); 249 | var converter = _getConverter(name, listType, false); 250 | converter = converter.replaceFirst('$name.', 'm.'); 251 | final key = listType.startsWith('\$') ? 'm.id' : 'm.hashCode'; 252 | buffer.write('key: (m) => $key, value: (m) => $converter)'); 253 | return buffer.toString(); 254 | } else { 255 | throw Exception('No type specified for List. Are you sure you imported necessary data models?'); 256 | } 257 | } else if (isEnum) { 258 | return '$name.index'; 259 | } else { 260 | return '$name'; 261 | } 262 | } 263 | 264 | final buffer = new StringBuffer(); 265 | buffer.writeln('Map toMap() => {'); 266 | 267 | final params = c.props.map((p) { 268 | var assignment = _getConverter(p.name, p.type, p.isEnum); 269 | if (p.isNullable && assignment != p.name) { 270 | assignment = '${p.name} != null ? $assignment : null'; 271 | } 272 | return '\'${p.name}\': $assignment'; 273 | }).join(','); 274 | 275 | buffer.write(params); 276 | 277 | buffer.writeln('};'); 278 | return buffer; 279 | } 280 | 281 | static StringBuffer _generateFromJson(DataClass c) { 282 | final buffer = new StringBuffer(); 283 | buffer.writeln('factory ${c.name}.fromJson(String json) => ${c.name}.fromMap(jsonDecode(json));'); 284 | return buffer; 285 | } 286 | 287 | static StringBuffer _generateToJson(DataClass c) { 288 | final buffer = new StringBuffer(); 289 | buffer.writeln('String toJson() => jsonEncode(toMap());'); 290 | return buffer; 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /auto_data_generator/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: auto_data_generator 2 | description: > 3 | Automatically generate simple data class files for Dart. 4 | This is the dev dependency. 5 | version: 0.0.3 6 | author: Paul DeMarco 7 | homepage: https://www.github.com/pauldemarco/auto_data 8 | 9 | environment: 10 | sdk: ">=2.1.0 <3.0.0" 11 | 12 | dependencies: 13 | auto_data: ^0.0.3 14 | build: ^1.0.0 15 | source_gen: ^0.9.4+1 16 | 17 | dev_dependencies: 18 | build_runner: ^1.0.0 19 | test: -------------------------------------------------------------------------------- /auto_data_generator/test/auto_data_generator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () { 5 | expect(3, 3); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | ios/.generated/ 9 | ios/Flutter/Generated.xcconfig 10 | ios/Runner/GeneratedPluginRegistrant.* 11 | -------------------------------------------------------------------------------- /example/lib/bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | import 'package:auto_data/auto_data.dart'; 3 | import 'dart:convert'; 4 | import 'foo.dart'; 5 | 6 | part 'bar.g.dart'; 7 | 8 | @data 9 | class $Bar { 10 | String fab; 11 | $Foo foo; 12 | Map> accuracies; 13 | } 14 | -------------------------------------------------------------------------------- /example/lib/foo.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | import 'package:auto_data/auto_data.dart'; 3 | import 'package:collection/collection.dart'; 4 | import 'dart:convert'; 5 | 6 | part 'foo.g.dart'; 7 | 8 | @data 9 | class $Foo { 10 | String bar; 11 | double baz; 12 | List counters; 13 | } 14 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/foo.dart'; 2 | import 'bar.dart'; 3 | 4 | void main() { 5 | final foo = new Foo(); 6 | final bar = new Bar(foo: foo); 7 | } 8 | -------------------------------------------------------------------------------- /example/lib/person.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | import 'package:auto_data/auto_data.dart'; 3 | import 'dart:convert'; 4 | 5 | part 'person.g.dart'; 6 | 7 | /// All comments are copied to generated code 8 | @data 9 | class $Person { 10 | /// This field gets a default value 11 | String name = 'Paul'; 12 | 13 | /// This field is not required 14 | @nullable 15 | double weight; 16 | 17 | /// Age of the person 18 | int age; 19 | 20 | /// Custom constructors are copied over 21 | $Person.genius() 22 | : name = 'Albert', 23 | weight = null, // Issue #1 24 | age = 140; 25 | } 26 | -------------------------------------------------------------------------------- /example/lib/point.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | import 'package:auto_data/auto_data.dart'; 3 | import 'dart:convert'; 4 | 5 | part 'point.g.dart'; 6 | 7 | @data 8 | class $Point { 9 | double x; 10 | double y; 11 | } 12 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: > 3 | Example project using auto_data that generates simple data classes. 4 | version: 0.0.3 5 | author: Paul DeMarco 6 | homepage: https://www.github.com/pauldemarco/auto_data 7 | 8 | environment: 9 | sdk: ">=2.1.0 <3.0.0" 10 | 11 | dependencies: 12 | auto_data: ^0.0.3 13 | 14 | dev_dependencies: 15 | build_runner: ^1.0.0 16 | auto_data_generator: ^0.0.3 17 | -------------------------------------------------------------------------------- /tools/post-publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Remove copied files 4 | 5 | rm -rf ./auto_data/example 6 | rm -rf ./auto_data_generator/example 7 | 8 | rm ./auto_data/README.md 9 | rm ./auto_data_generator/README.md 10 | 11 | rm ./auto_data/CHANGELOG.md 12 | rm ./auto_data_generator/CHANGELOG.md -------------------------------------------------------------------------------- /tools/pre-publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copy files into each package to be published 4 | 5 | cp -r example auto_data/ 6 | cp -r example auto_data_generator/ 7 | 8 | cp README.md auto_data/ 9 | cp README.md auto_data_generator/ 10 | 11 | cp CHANGELOG.md auto_data/ 12 | cp CHANGELOG.md auto_data_generator/ 13 | 14 | --------------------------------------------------------------------------------