├── README.md ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug_report.md │ ├── feature_request.md │ └── example_request.md ├── workflows │ ├── project.yml │ ├── sponsor.yml │ └── build.yml └── dependabot.yaml ├── packages ├── freezed │ ├── .pubignore │ ├── analysis_options.yaml │ ├── test │ │ ├── integration │ │ │ ├── export_empty.dart │ │ │ ├── external_typedef_two.dart │ │ │ ├── export_freezed_annotation.dart │ │ │ ├── multiple_part.dart │ │ │ ├── part_of.dart │ │ │ ├── data.dart │ │ │ ├── part.dart │ │ │ ├── external_typedef.dart │ │ │ ├── recursive.dart │ │ │ ├── recursive2.dart │ │ │ ├── deep_copy2.dart │ │ │ ├── export.dart │ │ │ ├── special_class_name.dart │ │ │ ├── diagnostics.dart │ │ │ ├── regression399 │ │ │ │ ├── b.dart │ │ │ │ └── a.dart │ │ │ ├── alias.dart │ │ │ ├── bidirectional.dart │ │ │ ├── generics_refs.dart │ │ │ ├── slow_2.dart │ │ │ ├── slow_3.dart │ │ │ ├── decorator.dart │ │ │ ├── typedef_parameter.dart │ │ │ ├── slow_1.dart │ │ │ ├── nullable.dart │ │ │ ├── extend.dart │ │ │ ├── optional_maybe.dart │ │ │ ├── generic.dart │ │ │ ├── equal.dart │ │ │ ├── custom_equals.dart │ │ │ ├── implements_decorator.dart │ │ │ ├── deep_copy.dart │ │ │ ├── common_types.dart │ │ │ ├── concrete.dart │ │ │ └── multiple_constructors.dart │ │ ├── recursive_import_locator_test.dart │ │ ├── special_class_name_test.dart │ │ ├── extend_test.dart │ │ ├── compiles_test.dart │ │ ├── data_test.dart │ │ ├── common.dart │ │ ├── custom_equals_test.dart │ │ ├── concrete_test.dart │ │ ├── generics_refs_test.dart │ │ ├── source_gen_test.dart │ │ ├── bidirectional_test.dart │ │ ├── typedef_parameter_test.dart │ │ ├── mixed_test.dart │ │ ├── hashcode_test.dart │ │ ├── equal_test.dart │ │ ├── implements_decorator_test.dart │ │ ├── decorator_test.dart │ │ ├── optional_maybe_test.dart │ │ ├── nullable_test.dart │ │ ├── generic_test.dart │ │ ├── source_gen_src.dart │ │ ├── manual_test.dart │ │ └── common_types_test.dart │ ├── .gitignore │ ├── example │ │ ├── build.yaml │ │ ├── .metadata │ │ ├── lib │ │ │ ├── equals.dart │ │ │ ├── time_slot.dart │ │ │ ├── diagnosticable.dart │ │ │ ├── non_diagnosticable.dart │ │ │ └── main.dart │ │ ├── pubspec.yaml │ │ ├── test │ │ │ ├── json_test.dart │ │ │ └── diagnosticable_test.dart │ │ └── .gitignore │ ├── lib │ │ ├── src │ │ │ ├── string.dart │ │ │ ├── tools │ │ │ │ ├── imports.dart │ │ │ │ ├── type.dart │ │ │ │ └── recursive_import_locator.dart │ │ │ ├── templates │ │ │ │ ├── abstract_template.dart │ │ │ │ ├── prototypes.dart │ │ │ │ ├── from_json_template.dart │ │ │ │ └── properties.dart │ │ │ ├── ast.dart │ │ │ ├── parse_generator.dart │ │ │ └── freezed_generator.dart │ │ └── builder.dart │ ├── pubspec_overrides.yaml │ ├── build.yaml │ ├── pubspec.yaml │ ├── LICENSE │ └── migration_guide.md ├── freezed_lint │ ├── .pubignore │ ├── analysis_options.yaml │ ├── pubspec_overrides.yaml │ ├── .gitignore │ ├── example │ │ ├── build.yaml │ │ ├── analysis_options.yaml │ │ ├── pubspec.yaml │ │ ├── .gitignore │ │ └── lib │ │ │ ├── missing_mixin.dart │ │ │ └── missing_private_empty_ctor.dart │ ├── lib │ │ ├── src │ │ │ ├── tools │ │ │ │ ├── freezed_annotation_checker.dart │ │ │ │ └── element_extensions.dart │ │ │ ├── missing_mixin.dart │ │ │ └── missing_private_empty_ctor.dart │ │ └── freezed_lint.dart │ ├── test │ │ └── ensure_build_test.dart │ ├── pubspec.yaml │ ├── README.md │ ├── LICENSE │ └── CHANGELOG.md └── freezed_annotation │ ├── README.md │ ├── .gitignore │ ├── build.yaml │ ├── pubspec.yaml │ ├── pubspec_overrides.yaml │ ├── LICENSE │ ├── lib │ └── freezed_annotation.g.dart │ ├── CHANGELOG.md │ └── test │ └── freezed_test.dart ├── .gitignore ├── .vscode └── settings.json ├── resources ├── after.png └── before.png ├── benchmarks ├── .gitignore ├── build.yaml ├── pubspec.yaml └── lib │ └── src │ ├── copy_with.dart │ └── equal.dart ├── package.json ├── sponsor.config.ts └── analysis_options.yaml /README.md: -------------------------------------------------------------------------------- 1 | packages/freezed/README.md -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: rrousselGit 2 | -------------------------------------------------------------------------------- /packages/freezed/.pubignore: -------------------------------------------------------------------------------- 1 | pubspec_overrides.yaml -------------------------------------------------------------------------------- /packages/freezed_lint/.pubignore: -------------------------------------------------------------------------------- 1 | pubspec_overrides.yaml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | build/ 4 | node_modules 5 | /package-lock.json -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "unfreezed" 4 | ] 5 | } -------------------------------------------------------------------------------- /packages/freezed/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../../analysis_options.yaml 2 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/export_empty.dart: -------------------------------------------------------------------------------- 1 | export 'concrete.dart'; 2 | -------------------------------------------------------------------------------- /packages/freezed_lint/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../../analysis_options.yaml 2 | -------------------------------------------------------------------------------- /resources/after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/freezed/HEAD/resources/after.png -------------------------------------------------------------------------------- /resources/before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrousselGit/freezed/HEAD/resources/before.png -------------------------------------------------------------------------------- /packages/freezed/test/integration/external_typedef_two.dart: -------------------------------------------------------------------------------- 1 | typedef ExternalTypedefTwo = void Function(String); 2 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/export_freezed_annotation.dart: -------------------------------------------------------------------------------- 1 | export 'package:freezed_annotation/freezed_annotation.dart'; 2 | -------------------------------------------------------------------------------- /packages/freezed_lint/pubspec_overrides.yaml: -------------------------------------------------------------------------------- 1 | dependency_overrides: 2 | freezed_annotation: 3 | path: ../freezed_annotation 4 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/multiple_part.dart: -------------------------------------------------------------------------------- 1 | part of 'multiple_constructors.dart'; 2 | 3 | class EjectedToPart implements EjectCaseToPart {} 4 | -------------------------------------------------------------------------------- /packages/freezed_lint/.gitignore: -------------------------------------------------------------------------------- 1 | /.dart_tool 2 | .packages 3 | # Remove the following pattern if you wish to check in your lock file 4 | pubspec.lock 5 | custom_lint.log -------------------------------------------------------------------------------- /packages/freezed_annotation/README.md: -------------------------------------------------------------------------------- 1 | Annotations for [freezed].\ 2 | This package does nothing without [freezed]. 3 | 4 | [freezed]: https://pub.dartlang.org/packages/freezed 5 | -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | /.dart_tool 2 | /test/**/*.g.dart 3 | /test/**/*.freezed.dart 4 | .packages 5 | # Remove the following pattern if you wish to check in your lock file 6 | pubspec.lock -------------------------------------------------------------------------------- /packages/freezed/.gitignore: -------------------------------------------------------------------------------- 1 | /.dart_tool 2 | /test/**/*.g.dart 3 | /test/**/*.freezed.dart 4 | .packages 5 | # Remove the following pattern if you wish to check in your lock file 6 | pubspec.lock -------------------------------------------------------------------------------- /packages/freezed/example/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | freezed: 5 | options: 6 | union_key: custom-key 7 | union_value_case: pascal 8 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/part_of.dart: -------------------------------------------------------------------------------- 1 | part of 'part.dart'; 2 | 3 | @freezed 4 | abstract class PartOf with _$PartOf { 5 | const factory PartOf({required int value}) = _PartOf; 6 | } 7 | -------------------------------------------------------------------------------- /packages/freezed_annotation/.gitignore: -------------------------------------------------------------------------------- 1 | /.dart_tool 2 | /test/**/*.g.dart 3 | /test/**/*.freezed.dart 4 | .packages 5 | # Remove the following pattern if you wish to check in your lock file 6 | pubspec.lock -------------------------------------------------------------------------------- /packages/freezed_annotation/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | source_gen|combining_builder: 5 | options: 6 | ignore_for_file: 7 | - "type=lint" 8 | -------------------------------------------------------------------------------- /packages/freezed_lint/example/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | freezed: 5 | enabled: true 6 | generate_for: 7 | exclude: 8 | - lib/missing_private_empty_ctor.dart -------------------------------------------------------------------------------- /.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://github.com/rrousselGit/freezed/discussions 5 | about: Please ask and answer questions here 6 | -------------------------------------------------------------------------------- /benchmarks/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | freezed: 5 | options: 6 | union_key: custom-key 7 | union_value_case: pascal 8 | maybe_map: true 9 | maybe_when: true 10 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/data.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'data.freezed.dart'; 4 | 5 | @unfreezed 6 | abstract class Data with _$Data { 7 | factory Data(int a, final int b) = _Data; 8 | } 9 | -------------------------------------------------------------------------------- /packages/freezed_lint/lib/src/tools/freezed_annotation_checker.dart: -------------------------------------------------------------------------------- 1 | import 'package:custom_lint_builder/custom_lint_builder.dart'; 2 | 3 | const freezedAnnotationChecker = TypeChecker.fromName( 4 | 'Freezed', 5 | packageName: 'freezed_annotation', 6 | ); 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "The package.json is used to configure sponsorkit and nothing else", 3 | "private": true, 4 | "scripts": { 5 | "build": "sponsorkit" 6 | }, 7 | "devDependencies": { 8 | "sponsorkit": "latest" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/part.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'part_of.dart'; 4 | part 'part.freezed.dart'; 5 | 6 | @freezed 7 | abstract class Part with _$Part { 8 | const factory Part({required int value}) = _Part; 9 | } 10 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/external_typedef.dart: -------------------------------------------------------------------------------- 1 | import 'concrete.dart'; 2 | 3 | /// A typedef that imports another type 4 | /// 5 | /// The generated freezed class will not have to import this import as it 6 | /// is hidden behind the typedef 7 | typedef ExternalTypedef = void Function(Concrete); 8 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/recursive.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'recursive2.dart'; 3 | export 'recursive2.dart'; 4 | 5 | part 'recursive.freezed.dart'; 6 | 7 | @freezed 8 | abstract class A with _$A { 9 | factory A({B? parent}) = _A; 10 | } 11 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/recursive2.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'recursive.dart'; 3 | export 'recursive.dart'; 4 | 5 | part 'recursive2.freezed.dart'; 6 | 7 | @freezed 8 | abstract class B with _$B { 9 | factory B({A? parent}) = _B; 10 | } 11 | -------------------------------------------------------------------------------- /packages/freezed_lint/example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | exclude: 3 | - "**/*.freezed.dart" 4 | language: 5 | strict-casts: true 6 | strict-inference: true 7 | strict-raw-types: true 8 | errors: 9 | invalid_annotation_target: ignore 10 | plugins: 11 | - custom_lint -------------------------------------------------------------------------------- /packages/freezed_lint/test/ensure_build_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:build_verify/build_verify.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | test('ensure_build', () { 6 | expectBuildClean( 7 | packageRelativeDirectory: 'packages/freezed_lint', 8 | ); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/freezed/test/recursive_import_locator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'integration/export.dart'; 4 | 5 | void main() { 6 | test('recursive export of json_annotation works', () { 7 | expect(Export.fromJson({'a': 42}), const Export(42)); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/deep_copy2.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | import 'deep_copy.dart'; 4 | 5 | part 'deep_copy2.freezed.dart'; 6 | 7 | @freezed 8 | abstract class Country with _$Country { 9 | factory Country({String? name, Company? company, Director? president}) = 10 | _Country; 11 | } 12 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/export.dart: -------------------------------------------------------------------------------- 1 | import 'export_freezed_annotation.dart'; 2 | 3 | part 'export.freezed.dart'; 4 | 5 | part 'export.g.dart'; 6 | 7 | @freezed 8 | abstract class Export with _$Export { 9 | const factory Export(int a) = _Export; 10 | 11 | factory Export.fromJson(Map json) => _$ExportFromJson(json); 12 | } 13 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/special_class_name.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'special_class_name.freezed.dart'; 4 | 5 | @freezed 6 | abstract class Class$With$Special$Name with _$Class$With$Special$Name { 7 | factory Class$With$Special$Name({String? a, int? b}) = 8 | _Class$With$Special$Name; 9 | } 10 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/diagnostics.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'diagnostics.freezed.dart'; 5 | 6 | @freezed 7 | abstract class DiagnosticsExample with _$DiagnosticsExample { 8 | const factory DiagnosticsExample({int? value}) = _DiagnosticsExample; 9 | } 10 | -------------------------------------------------------------------------------- /packages/freezed/example/.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: 92f7e16312d9c7c396c0100dcb699458c3f10172 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /packages/freezed_lint/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | # The pubspec.yaml of an application using our lints 2 | name: example_app 3 | environment: 4 | sdk: ">=3.0.0 <4.0.0" 5 | publish_to: none 6 | 7 | dependencies: 8 | freezed_annotation: any 9 | 10 | dev_dependencies: 11 | build_runner: any 12 | custom_lint: any 13 | freezed: any 14 | freezed_lint: any 15 | 16 | dependency_overrides: 17 | freezed_lint: 18 | path: ../ 19 | -------------------------------------------------------------------------------- /packages/freezed/example/lib/equals.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'equals.freezed.dart'; 4 | 5 | @freezed 6 | abstract class Equals with _$Equals { 7 | Equals._(); 8 | factory Equals({String? name, int? age}) = _Equals; 9 | 10 | @override 11 | bool operator ==(Object o) => o is Equals && o.name == name; 12 | 13 | @override 14 | int get hashCode => name.hashCode; 15 | } 16 | -------------------------------------------------------------------------------- /packages/freezed/example/lib/time_slot.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'time_slot.freezed.dart'; 5 | 6 | /// This is class has been added to address the issue described in 7 | /// https://github.com/rrousselGit/freezed/issues/220 8 | @freezed 9 | abstract class TimeSlot with _$TimeSlot { 10 | factory TimeSlot({TimeOfDay? start, TimeOfDay? end}) = _TimeSlot; 11 | } 12 | -------------------------------------------------------------------------------- /packages/freezed_lint/lib/src/tools/element_extensions.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element2.dart'; 2 | import 'package:analyzer/dart/element/type.dart'; 3 | 4 | extension DartTypeExt on DartType { 5 | bool hasName(String name) => getDisplayString() == name; 6 | } 7 | 8 | extension ClassElementExt on ClassElement2 { 9 | String? constToken() { 10 | if (constructors2.any((c) => c.isConst)) return 'const '; 11 | return null; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/project.yml: -------------------------------------------------------------------------------- 1 | name: Add new issues to project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - reopened 8 | 9 | jobs: 10 | add-to-project: 11 | name: Add issue to project 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/add-to-project@v1.0.2 15 | with: 16 | project-url: https://github.com/users/rrousselGit/projects/8 17 | github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} 18 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/regression399/b.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'b.freezed.dart'; 4 | 5 | @freezed 6 | abstract class Regression399B with _$Regression399B { 7 | const factory Regression399B() = Regression399BImpl; 8 | } 9 | 10 | @freezed 11 | abstract class GenericRegression399B with _$GenericRegression399B { 12 | const factory GenericRegression399B() = GenericRegression399BImpl; 13 | } 14 | -------------------------------------------------------------------------------- /packages/freezed/test/special_class_name_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: prefer_const_constructors, omit_local_variable_types 2 | import 'package:test/test.dart'; 3 | 4 | import 'integration/special_class_name.dart'; 5 | 6 | Future main() async { 7 | test('toString will properly handle dollar signs', () { 8 | final value = Class$With$Special$Name(a: 'a', b: 1); 9 | 10 | expect(value.toString(), r'Class$With$Special$Name(a: a, b: 1)'); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/alias.dart: -------------------------------------------------------------------------------- 1 | import 'export_empty.dart' as concrete; 2 | import 'export_freezed_annotation.dart'; 3 | import 'generic.dart' show Model; 4 | 5 | part 'alias.freezed.dart'; 6 | 7 | @freezed 8 | abstract class Alias with _$Alias { 9 | @With() 10 | @Implements() 11 | factory Alias([ 12 | @Default(concrete.Empty()) concrete.Empty value, 13 | int? a, 14 | Model? b, 15 | ]) = _Alias; 16 | } 17 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/bidirectional.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'bidirectional.freezed.dart'; 4 | 5 | @freezed 6 | abstract class Person with _$Person { 7 | factory Person({String? name, int? age, Appointment? appointment}) = _Person; 8 | } 9 | 10 | @freezed 11 | abstract class Appointment with _$Appointment { 12 | factory Appointment({String? title, DateTime? date, Person? creator}) = 13 | _Appointment; 14 | } 15 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/generics_refs.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'generics_refs.freezed.dart'; 4 | 5 | @freezed 6 | abstract class PageList with _$PageList { 7 | factory PageList(List pages) = _PageList; 8 | } 9 | 10 | @freezed 11 | abstract class PageMap with _$PageMap { 12 | factory PageMap(Map pages) = _PageMap; 13 | } 14 | 15 | @freezed 16 | abstract class WidgetType with _$WidgetType { 17 | const factory WidgetType.page() = Page; 18 | } 19 | -------------------------------------------------------------------------------- /.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 | assignees: 7 | - rrousselGit 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | 15 | 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | -------------------------------------------------------------------------------- /packages/freezed_lint/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: freezed_lint 2 | version: 0.0.12 3 | description: > 4 | Linter rules for freezed. 5 | repository: https://github.com/rrousselGit/freezed 6 | issue_tracker: https://github.com/rrousselGit/freezed/issues 7 | 8 | environment: 9 | sdk: ">=3.0.0 <4.0.0" 10 | 11 | dependencies: 12 | analyzer: ">=7.5.9 <9.0.0" 13 | analyzer_plugin: ^0.13.0 14 | custom_lint_builder: ^0.8.0 15 | freezed_annotation: 3.1.0 16 | 17 | dev_dependencies: 18 | custom_lint: ^0.8.0 19 | build_verify: ^3.1.0 20 | test: ^1.22.2 21 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/regression399/a.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'b.dart'; 3 | 4 | part 'a.freezed.dart'; 5 | 6 | @freezed 7 | abstract class Regression399A with _$Regression399A { 8 | const factory Regression399A({required Regression399B? b}) = _Regression399A; 9 | } 10 | 11 | @freezed 12 | abstract class GenericRegression399A with _$GenericRegression399A { 13 | const factory GenericRegression399A({required GenericRegression399B? b}) = 14 | _GenericRegression399A; 15 | } 16 | -------------------------------------------------------------------------------- /sponsor.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, presets } from "sponsorkit"; 2 | 3 | export default defineConfig({ 4 | github: { 5 | login: "rrousselGit", 6 | }, 7 | width: 800, 8 | formats: ["svg"], 9 | tiers: [ 10 | { 11 | title: "Backers", 12 | preset: presets.base, 13 | }, 14 | { 15 | title: "Silver Sponsors", 16 | monthlyDollars: 25, 17 | preset: presets.large, 18 | }, 19 | { 20 | title: "Gold Sponsors", 21 | monthlyDollars: 100, 22 | preset: presets.xl, 23 | }, 24 | ], 25 | }); 26 | -------------------------------------------------------------------------------- /packages/freezed/test/extend_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'integration/extend.dart'; 4 | 5 | void main() { 6 | test('Calls super', () { 7 | expect(Subclass2(1, 2), Subclass2(1, 2)); 8 | expect(Subclass2(1, 1), isNot(Subclass2(1, 2))); 9 | expect(Subclass2(2, 2), isNot(Subclass2(1, 2))); 10 | 11 | expect(Subclass2(1, 2).hashCode, Subclass2(1, 2).hashCode); 12 | expect(Subclass2(1, 1).hashCode, isNot(Subclass2(1, 2).hashCode)); 13 | expect(Subclass2(2, 2).hashCode, isNot(Subclass2(1, 2).hashCode)); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/freezed_annotation/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: freezed_annotation 2 | description: > 3 | Annotations for the freezed code-generator. 4 | This package does nothing without freezed too. 5 | version: 3.1.0 6 | repository: https://github.com/rrousselGit/freezed 7 | issue_tracker: https://github.com/rrousselGit/freezed/issues 8 | 9 | environment: 10 | sdk: ">=3.0.0 <4.0.0" 11 | 12 | dependencies: 13 | collection: ^1.15.0 14 | json_annotation: ^4.8.0 15 | meta: ^1.7.0 16 | 17 | dev_dependencies: 18 | build_runner: ^2.3.3 19 | json_serializable: ^6.3.2 20 | test: ^1.21.0 21 | -------------------------------------------------------------------------------- /packages/freezed/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | publish_to: none 4 | 5 | environment: 6 | sdk: ">=3.6.0 <4.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | freezed_annotation: 12 | path: ../../freezed_annotation 13 | json_annotation: ^4.9.0 14 | 15 | dev_dependencies: 16 | freezed: 17 | path: ../ 18 | json_serializable: ^6.9.4 19 | build_runner: 20 | flutter_test: 21 | sdk: flutter 22 | 23 | dependency_overrides: 24 | freezed: 25 | path: ../ 26 | freezed_annotation: 27 | path: ../../freezed_annotation 28 | -------------------------------------------------------------------------------- /benchmarks/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | publish_to: none 4 | 5 | environment: 6 | sdk: ^3.5.0 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | freezed_annotation: 12 | path: ../packages/freezed_annotation 13 | json_annotation: ^4.4.0 14 | 15 | dev_dependencies: 16 | freezed: 17 | path: ../packages/freezed 18 | json_serializable: ^6.1.3 19 | build_runner: 20 | flutter_test: 21 | sdk: flutter 22 | 23 | dependency_overrides: 24 | freezed: 25 | path: ../packages/freezed 26 | freezed_annotation: 27 | path: ../packages/freezed_annotation 28 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/slow_2.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'slow_2.freezed.dart'; 4 | 5 | @freezed 6 | abstract class A with _$A { 7 | const factory A.a() = _A; 8 | const factory A.b() = _B; 9 | const factory A.c() = _C; 10 | const factory A.d() = _D; 11 | const factory A.e() = _E; 12 | const factory A.f() = _F; 13 | const factory A.g() = _G; 14 | const factory A.h() = _H; 15 | const factory A.i() = _I; 16 | const factory A.j() = _J; 17 | const factory A.k() = _K; 18 | const factory A.l() = _L; 19 | const factory A.m() = _M; 20 | const factory A.n() = _N; 21 | } 22 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/slow_3.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'slow_3.freezed.dart'; 4 | 5 | @freezed 6 | abstract class B with _$B { 7 | const factory B.a() = _A; 8 | const factory B.b() = _B; 9 | const factory B.c() = _C; 10 | const factory B.d() = _D; 11 | const factory B.e() = _E; 12 | const factory B.f() = _F; 13 | const factory B.g() = _G; 14 | const factory B.h() = _H; 15 | const factory B.i() = _I; 16 | const factory B.j() = _J; 17 | const factory B.k() = _K; 18 | const factory B.l() = _L; 19 | const factory B.m() = _M; 20 | const factory B.n() = _N; 21 | } 22 | -------------------------------------------------------------------------------- /packages/freezed/lib/src/string.dart: -------------------------------------------------------------------------------- 1 | final _upperCase = RegExp('[A-Z]'); 2 | 3 | extension StringX on String { 4 | String get kebabCase => _fixCase('-'); 5 | 6 | String get snakeCase => _fixCase('_'); 7 | 8 | String get screamingSnake => snakeCase.toUpperCase(); 9 | 10 | String get pascalCase { 11 | if (isEmpty) return ''; 12 | 13 | return this[0].toUpperCase() + substring(1); 14 | } 15 | 16 | String _fixCase(String separator) => replaceAllMapped(_upperCase, (match) { 17 | var lower = match.group(0)!.toLowerCase(); 18 | 19 | if (match.start > 0) { 20 | lower = '$separator$lower'; 21 | } 22 | 23 | return lower; 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /packages/freezed_annotation/pubspec_overrides.yaml: -------------------------------------------------------------------------------- 1 | dependency_overrides: 2 | # pub downgrade: 3 | # build_runner 2.2.0 => glob 2.0.0 => file 6.0.0 4 | # freezed requires file: ^6.1.3 5 | file: ^6.1.3 6 | # build_runner 2.3.3 => code_builder 4.2.0 => matcher 0.12.11 7 | # freezed requires matcher: ^0.12.14 8 | matcher: ^0.12.14 9 | # build_runner 2.3.3 => pub_semver 2.0.0 10 | # freezed requires pub_semver: ^2.1.3 11 | pub_semver: ^2.1.3 12 | # watcher 1.0.0 extends, implements, or mixens sealed class 'FileSystemEvent' 13 | watcher: ^1.1.0 14 | # https://github.com/dart-lang/build/issues/3733#issuecomment-2272082820 15 | frontend_server_client: ^4.0.0 16 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/decorator.dart: -------------------------------------------------------------------------------- 1 | library decorator; 2 | 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | 5 | part 'decorator.freezed.dart'; 6 | 7 | @freezed 8 | abstract class Decorator with _$Decorator { 9 | factory Decorator({@deprecated @_WeirdDecorator('a', b: 0.42) String? a}) = 10 | Decorator0; 11 | } 12 | 13 | class _WeirdDecorator { 14 | const _WeirdDecorator(this.a, {this.b}); 15 | 16 | final String a; 17 | final double? b; 18 | } 19 | 20 | class Foo { 21 | const Foo(); 22 | } 23 | 24 | @freezed 25 | abstract class ListDecorator with _$ListDecorator { 26 | factory ListDecorator(@Foo() List a) = ListDecorator0; 27 | } 28 | -------------------------------------------------------------------------------- /packages/freezed/pubspec_overrides.yaml: -------------------------------------------------------------------------------- 1 | dependency_overrides: 2 | freezed_annotation: 3 | path: ../freezed_annotation 4 | # pub downgrade: 5 | # build_runner 2.2.0 => glob 2.0.0 => file 6.0.0 6 | # freezed requires file: ^6.1.3 7 | file: ^6.1.3 8 | # analyzer 5.2.0 => pub_semver 2.0.0 9 | # freezed requires pub_semver: ^2.1.3 10 | pub_semver: ^2.1.3 11 | # expect_error 1.0.4 => pubspec 2.0.1 => uri 1.0.0 => quiver 3.0.0 12 | # freezed requires quiver: ^3.2.0 13 | quiver: ^3.2.0 14 | # watcher 1.0.0 extends, implements, or mixens sealed class 'FileSystemEvent' 15 | watcher: ^1.1.0 16 | # https://github.com/dart-lang/build/issues/3733#issuecomment-2272082820 17 | frontend_server_client: ^4.0.0 18 | -------------------------------------------------------------------------------- /packages/freezed/test/compiles_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'common.dart'; 4 | 5 | void main() { 6 | group('compile utility', () { 7 | test('compiles', () async { 8 | await expectLater( 9 | compile(r''' 10 | import 'single_class_constructor.dart'; 11 | 12 | void main() { 13 | final test = MyClass(a: 'a', b: 42); 14 | } 15 | '''), 16 | completes, 17 | ); 18 | }); 19 | 20 | test('does not compile', () async { 21 | await expectLater( 22 | compile(r''' 23 | import 'single_class_constructor.dart'; 24 | 25 | void main() { 26 | final test = 27 | } 28 | '''), 29 | throwsCompileError, 30 | ); 31 | }); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/typedef_parameter.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'external_typedef.dart'; 3 | import 'external_typedef_two.dart' as two; 4 | 5 | part 'typedef_parameter.freezed.dart'; 6 | 7 | typedef MyTypedef = Object? Function(String); 8 | typedef GenericTypedef = S Function(T); 9 | 10 | @freezed 11 | abstract class ClassWithTypedef with _$ClassWithTypedef { 12 | ClassWithTypedef._(); 13 | factory ClassWithTypedef( 14 | MyTypedef myTypedef, 15 | MyTypedef? maybeTypedef, 16 | ExternalTypedef externalTypedef, 17 | two.ExternalTypedefTwo externalTypedefTwo, 18 | GenericTypedef genericTypedef, 19 | ) = _ClassWithTypedef; 20 | } 21 | -------------------------------------------------------------------------------- /.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 | assignees: 7 | - rrousselGit 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/slow_1.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | import './slow_2.dart'; 4 | import 'slow_3.dart'; 5 | 6 | part 'slow_1.freezed.dart'; 7 | 8 | @freezed 9 | abstract class Root with _$Root { 10 | const factory Root.a(A a) = _A; 11 | const factory Root.b(B b) = _B; 12 | const factory Root.c(A a) = _C; 13 | const factory Root.d(B b) = _D; 14 | const factory Root.e(A a) = _E; 15 | const factory Root.f(A a) = _F; 16 | const factory Root.g(B b) = _G; 17 | const factory Root.h(A a) = _H; 18 | const factory Root.i(B b) = _I; 19 | const factory Root.j(A a) = _J; 20 | const factory Root.k(B b) = _K; 21 | const factory Root.l(A a) = _L; 22 | const factory Root.m(B b) = _M; 23 | } 24 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | enable-beta-ecosystems: true 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | reviewers: 9 | - "rrousselGit" 10 | 11 | - package-ecosystem: "pub" 12 | directory: "/packages/_internal" 13 | schedule: 14 | interval: "weekly" 15 | reviewers: 16 | - "rrousselGit" 17 | 18 | - package-ecosystem: "pub" 19 | directory: "/packages/freezed" 20 | schedule: 21 | interval: "weekly" 22 | reviewers: 23 | - "rrousselGit" 24 | 25 | - package-ecosystem: "pub" 26 | directory: "/packages/freezed_annotations" 27 | schedule: 28 | interval: "weekly" 29 | reviewers: 30 | - "rrousselGit" -------------------------------------------------------------------------------- /.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 | assignees: 9 | - rrousselGit 10 | --- 11 | 12 | **Describe what scenario you think is uncovered by the existing examples/articles** 13 | A clear and concise description of the problem that you want explained. 14 | 15 | **Describe why existing examples/articles do not cover this case** 16 | Explain which examples/articles you have seen before making this request, and 17 | why they did not help you with your problem. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the documentation request here. 21 | -------------------------------------------------------------------------------- /packages/freezed/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | freezed: 5 | enabled: true 6 | generate_for: 7 | exclude: 8 | - example 9 | - test/source_gen_src.dart 10 | include: 11 | - test/* 12 | - test/**/* 13 | options: 14 | format: true 15 | source_gen|combining_builder: 16 | options: 17 | ignore_for_file: 18 | - "type=lint" 19 | 20 | builders: 21 | freezed: 22 | import: "package:freezed/builder.dart" 23 | builder_factories: ["freezed"] 24 | build_extensions: { ".dart": [".freezed.dart"] } 25 | auto_apply: dependents 26 | build_to: source 27 | runs_before: ["json_serializable|json_serializable"] 28 | -------------------------------------------------------------------------------- /packages/freezed/example/test/json_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/main.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | test('Union toJson', () { 6 | expect(const Union(42).toJson(), { 7 | 'custom-key': 'Default', 8 | 'value': 42, 9 | }); 10 | 11 | expect(const Union.loading().toJson(), { 12 | 'custom-key': 'Loading', 13 | }); 14 | }); 15 | 16 | test('Union fromJson', () { 17 | expect( 18 | Union.fromJson({'custom-key': 'Default', 'value': 42}), 19 | const Union(42), 20 | ); 21 | 22 | expect( 23 | Union.fromJson({'custom-key': 'Loading'}), 24 | const Union.loading(), 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /packages/freezed_lint/lib/freezed_lint.dart: -------------------------------------------------------------------------------- 1 | import 'package:custom_lint_builder/custom_lint_builder.dart'; 2 | 3 | import 'package:freezed_lint/src/missing_mixin.dart'; 4 | import 'package:freezed_lint/src/missing_private_empty_ctor.dart'; 5 | 6 | /// This is the entrypoint of our plugin. 7 | /// All plugins must specify a `createPlugin` function in their `lib/.dart` file 8 | PluginBase createPlugin() => _FreezedLint(); 9 | 10 | /// The class listing all the [LintRule]s and [Assist]s defined by our plugin. 11 | class _FreezedLint extends PluginBase { 12 | @override 13 | List getLintRules(configs) => [ 14 | MissingMixin(), 15 | MissingPrivateEmptyCtor(), 16 | ]; 17 | 18 | @override 19 | List getAssists() => []; 20 | } 21 | -------------------------------------------------------------------------------- /packages/freezed_lint/README.md: -------------------------------------------------------------------------------- 1 | # Lints for [freezed]. 2 | 3 | Written with [custom_lint] to help with miss-typed/configured freezed classes. 4 | 5 | ## How to use 6 | 7 | Add freezed_lint and custom_lint to your projects `pubspec.yaml`. 8 | 9 | ```console 10 | flutter pub add dev:custom_lint 11 | flutter pub add dev:freezed_lint 12 | ``` 13 | 14 | And add custom_lint as a plugin in the `analysis_options.yaml` 15 | 16 | ```yaml 17 | analyzer: 18 | plugins: 19 | - custom_lint 20 | ``` 21 | 22 | You might need to restart your IDE for it to pick up the new lints 23 | 24 | ## List of supported lint 25 | 26 | - `freezed_missing_mixin` ✅ 27 | - `freezed_missing_private_empty_constructor` ✅ 28 | 29 | [freezed]: https://pub.dev/packages/freezed 30 | [custom_lint]: https://pub.dev/packages/custom_lint 31 | -------------------------------------------------------------------------------- /packages/freezed_lint/example/.gitignore: -------------------------------------------------------------------------------- 1 | Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /packages/freezed/example/lib/diagnosticable.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'diagnosticable.freezed.dart'; 5 | 6 | @freezed 7 | abstract class Example with _$Example { 8 | Example._(); 9 | factory Example(int a, String b) = _Example; 10 | factory Example.named(T c) = _Example2; 11 | 12 | @override 13 | late final theAnswer = 42; 14 | } 15 | 16 | @freezed 17 | abstract class ConcreteExample with _$ConcreteExample { 18 | ConcreteExample._(); 19 | factory ConcreteExample() = _ConcreteExample; 20 | } 21 | 22 | @freezed 23 | abstract class ToString with _$ToString { 24 | ToString._(); 25 | factory ToString() = _ToString; 26 | 27 | @override 28 | String toString() => 'MyToString()'; 29 | } 30 | -------------------------------------------------------------------------------- /packages/freezed/example/.gitignore: -------------------------------------------------------------------------------- 1 | *.freezed.dart 2 | *.g.dart 3 | 4 | # Miscellaneous 5 | *.class 6 | *.log 7 | *.pyc 8 | *.swp 9 | .DS_Store 10 | .atom/ 11 | .buildlog/ 12 | .history 13 | .svn/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | .dart_tool/ 29 | .flutter-plugins 30 | .flutter-plugins-dependencies 31 | .packages 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Web related 37 | lib/generated_plugin_registrant.dart 38 | 39 | # Exceptions to above rules. 40 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 41 | -------------------------------------------------------------------------------- /benchmarks/lib/src/copy_with.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'copy_with.freezed.dart'; 4 | 5 | const _kUseNaiveImpl = false; 6 | 7 | @freezed 8 | abstract class Model with _$Model { 9 | factory Model({required int counter}) = _Model; 10 | } 11 | 12 | @freezed 13 | abstract class ModelWrapper with _$ModelWrapper { 14 | factory ModelWrapper({ 15 | required Model model, 16 | }) = _ModelWrapper; 17 | } 18 | 19 | void main(List arguments) { 20 | var wrapper = ModelWrapper(model: Model(counter: 0)); 21 | 22 | for (var i = 0; i < 10000000; i++) { 23 | if (_kUseNaiveImpl) 24 | wrapper = ModelWrapper(model: Model(counter: wrapper.model.counter + 1)); 25 | else 26 | wrapper = wrapper.copyWith.model(counter: wrapper.model.counter + 1); 27 | } 28 | print(wrapper.model.counter); 29 | } 30 | -------------------------------------------------------------------------------- /packages/freezed/test/data_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'common.dart'; 4 | import 'integration/data.dart'; 5 | 6 | void main() { 7 | test('Can mutate non-final parameters', () { 8 | final data = Data(0, 1); 9 | 10 | expect(data.b, 1); 11 | expect(data.a, 0); 12 | 13 | data.a = 42; 14 | 15 | expect(data.a, 42); 16 | }); 17 | 18 | test('Final parameters generate immutable properties', () async { 19 | await expectLater( 20 | compile(r''' 21 | import 'data.dart'; 22 | 23 | void main() { 24 | final value = Data(0, 1); 25 | } 26 | '''), 27 | completes, 28 | ); 29 | 30 | await expectLater( 31 | compile(r''' 32 | import 'data.dart'; 33 | 34 | void main() { 35 | final value = Data(0, 1); 36 | value.b = 42; 37 | } 38 | '''), 39 | throwsCompileError, 40 | ); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /packages/freezed_lint/example/lib/missing_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'missing_mixin.freezed.dart'; 4 | 5 | @freezed 6 | // expect_lint: freezed_missing_mixin 7 | class MissingMixin { 8 | const factory MissingMixin() = _MissingMixin; 9 | } 10 | 11 | @freezed 12 | abstract class WithMixin with _$WithMixin { 13 | const factory WithMixin() = _WithMixin; 14 | } 15 | 16 | mixin SomeMixin { 17 | int get id; 18 | } 19 | 20 | @freezed 21 | // expect_lint: freezed_missing_mixin 22 | class FooModel with SomeMixin { 23 | const FooModel._(); 24 | 25 | const factory FooModel(int id) = _FooModel; 26 | 27 | @override 28 | int get id => id; 29 | } 30 | 31 | @freezed 32 | abstract class BarModel with _$BarModel, SomeMixin { 33 | const BarModel._(); 34 | 35 | const factory BarModel(int id) = _BarModel; 36 | 37 | @override 38 | int get id => id; 39 | } 40 | -------------------------------------------------------------------------------- /packages/freezed/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: freezed 2 | description: > 3 | Code generation for immutable classes that has a simple syntax/API without 4 | compromising on the features. 5 | version: 3.2.3 6 | repository: https://github.com/rrousselGit/freezed 7 | issue_tracker: https://github.com/rrousselGit/freezed/issues 8 | 9 | environment: 10 | sdk: ">=3.8.0 <4.0.0" 11 | 12 | dependencies: 13 | analyzer: ">=7.5.9 <9.0.0" 14 | build: ">=3.0.0 <5.0.0" 15 | build_config: ^1.1.0 16 | collection: ^1.15.0 17 | meta: ^1.9.1 18 | source_gen: ">=3.0.0 <5.0.0" 19 | freezed_annotation: 3.1.0 20 | json_annotation: ^4.8.0 21 | dart_style: ^3.0.0 22 | pub_semver: ^2.2.0 23 | 24 | dev_dependencies: 25 | json_serializable: ^6.10.0 26 | build_test: ^3.3.0 27 | build_runner: ^2.3.3 28 | test: ^1.21.0 29 | matcher: ^0.12.14 30 | source_gen_test: ^1.2.0 31 | expect_error: ^1.0.10 32 | flutter: 33 | sdk: flutter 34 | -------------------------------------------------------------------------------- /benchmarks/lib/src/equal.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'equal.freezed.dart'; 4 | 5 | @freezed 6 | abstract class ModelWithList with _$ModelWithList { 7 | factory ModelWithList({ 8 | @Default([]) List someList, 9 | @Default(0) int counter, 10 | }) = _ModelWithList; 11 | } 12 | 13 | void main(List arguments) { 14 | var model = ModelWithList(someList: List.filled(1000000, 0)); 15 | 16 | final stopwatch = Stopwatch()..start(); 17 | 18 | var elapsed = 0; 19 | 20 | for (var i = 0; i < 10000; i++) { 21 | final oldModel = model; 22 | model = model.copyWith(counter: model.counter + 1); 23 | 24 | if (oldModel == model) { 25 | throw Exception('New model should not equal old model.'); 26 | } 27 | 28 | model.hashCode; 29 | 30 | final elapsedThisIteration = stopwatch.elapsedMicroseconds; 31 | 32 | if (i % 10 == 0) { 33 | print('Delta: ${(elapsedThisIteration - elapsed) / 1000000} seconds'); 34 | } 35 | 36 | elapsed = elapsedThisIteration; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/freezed/lib/src/tools/imports.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element2.dart'; 2 | 3 | extension LibraryHasImport on LibraryElement2 { 4 | LibraryElement2? findTransitiveExportWhere( 5 | bool Function(LibraryElement2 library) visitor, 6 | ) { 7 | if (visitor(this)) return this; 8 | 9 | final visitedLibraries = {}; 10 | LibraryElement2? visitLibrary(LibraryElement2 library) { 11 | if (!visitedLibraries.add(library)) return null; 12 | 13 | if (visitor(library)) return library; 14 | 15 | for (final export in library.exportedLibraries2) { 16 | final result = visitLibrary(export); 17 | if (result != null) return result; 18 | } 19 | 20 | return null; 21 | } 22 | 23 | for (final import in exportedLibraries2) { 24 | final result = visitLibrary(import); 25 | if (result != null) return result; 26 | } 27 | 28 | return null; 29 | } 30 | 31 | bool anyTransitiveExport(bool Function(LibraryElement2 library) visitor) { 32 | return findTransitiveExportWhere(visitor) != null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/sponsor.yml: -------------------------------------------------------------------------------- 1 | name: Update sponsors in README 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | push: 8 | branches: [master] 9 | 10 | jobs: 11 | update-sponsors: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v5 15 | 16 | - name: Set node 17 | uses: actions/setup-node@v6 18 | with: 19 | node-version: 16.x 20 | 21 | - run: npm install yarn --global 22 | - run: yarn 23 | 24 | - name: Update sponsors 25 | run: npm run build 26 | env: 27 | SPONSORKIT_GITHUB_TOKEN: ${{ secrets.SPONSORS_TOKEN }} 28 | SPONSORKIT_GITHUB_LOGIN: rrousselGit 29 | SPONSORKIT_DIR: "./sponsorkit" 30 | 31 | - run: ls 32 | - run: ls sponsorkit 33 | 34 | - name: Commit 35 | uses: EndBug/add-and-commit@v9 36 | with: 37 | message: "chore: update sponsors.svg" 38 | add: "./sponsorkit/sponsors.*" 39 | default_author: github_actions 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | -------------------------------------------------------------------------------- /packages/freezed/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 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/freezed_lint/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 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/freezed_annotation/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 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/freezed_lint/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.12 - 2025-09-10 2 | 3 | Broader the version range for `analyzer` 4 | 5 | ## 0.0.11 - 2025-09-09 6 | 7 | Support new Analyzer API and custom_lint 8 | 9 | ## 0.0.10 - 2025-07-02 10 | 11 | - `freezed_annotation` upgraded to `3.1.0` 12 | 13 | ## 0.0.9 - 2025-02-27 14 | 15 | Support latest analyzer_plugin/custom_lint 16 | 17 | ## 0.0.8 - 2025-02-25 18 | 19 | - `freezed_annotation` upgraded to `3.0.0` 20 | 21 | ## 0.0.7 - 2025-01-08 22 | 23 | Bump analyzer version 24 | 25 | ## 0.0.6 - 2025-01-06 26 | 27 | - fix false positive `freezed_missing_private_empty_constructor` if accessor/method/field is static (thanks to @SunlightBro) 28 | 29 | ## 0.0.5 - 2024-07-15 30 | 31 | - `freezed_annotation` upgraded to `2.4.4` 32 | 33 | ## 0.0.4 - 2024-07-09 34 | 35 | - `freezed_annotation` upgraded to `2.4.3` 36 | 37 | ## 0.0.3 - 2024-07-02 38 | 39 | - `freezed_annotation` upgraded to `2.4.2` 40 | 41 | ## 0.0.2 - 2024-05-14 42 | 43 | - Support analyzer 6.5.0 44 | 45 | ## 0.0.1 - 2024-02-04 46 | 47 | - Support latest custom_lint version. 48 | 49 | ## 0.0.1-dev+1 50 | 51 | Upgrade analyzer and custom_lint versions 52 | 53 | ## 0.0.1-dev 54 | 55 | - freezed_missing_mixin 56 | - freezed_missing_private_empty_constructor 57 | -------------------------------------------------------------------------------- /packages/freezed/test/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/results.dart'; 2 | import 'package:analyzer/diagnostic/diagnostic.dart'; 3 | import 'package:analyzer/error/error.dart'; 4 | import 'package:build_test/build_test.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | final throwsCompileError = throwsA(isA()); 8 | 9 | Future compile(String src) async { 10 | final main = await resolveSources( 11 | { 12 | 'freezed|test/integration/main.dart': 13 | ''' 14 | library main; 15 | 16 | $src 17 | ''', 18 | }, 19 | (r) => r.findLibraryByName('main'), 20 | readAllSourcesFromFilesystem: true, 21 | ); 22 | 23 | final errorResult = 24 | await main!.session.getErrors('/freezed/test/integration/main.dart') 25 | as ErrorsResult; 26 | final criticalErrors = errorResult.errors 27 | .where((element) => element.severity == Severity.error) 28 | .toList(); 29 | 30 | if (criticalErrors.isNotEmpty) { 31 | throw CompileError(criticalErrors); 32 | } 33 | } 34 | 35 | class CompileError extends Error { 36 | CompileError(this.errors); 37 | final List errors; 38 | 39 | @override 40 | String toString() { 41 | return 'CompileError: \n${errors.join('\n')}'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/nullable.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'nullable.freezed.dart'; 4 | 5 | @freezed 6 | abstract class RequiredPositional with _$RequiredPositional { 7 | factory RequiredPositional(int a) = _RequiredPositional; 8 | } 9 | 10 | @freezed 11 | abstract class NullableRequiredPositional with _$NullableRequiredPositional { 12 | factory NullableRequiredPositional(int? a) = _NullableRequiredPositional; 13 | } 14 | 15 | @freezed 16 | abstract class Positional with _$Positional { 17 | factory Positional([int? a]) = _Positional; 18 | } 19 | 20 | @freezed 21 | abstract class DefaultPositional with _$DefaultPositional { 22 | factory DefaultPositional([@Default(0) int a]) = _DefaultPositional; 23 | } 24 | 25 | @freezed 26 | abstract class Named with _$Named { 27 | factory Named({int? a}) = _Named; 28 | } 29 | 30 | @freezed 31 | abstract class DefaultNamed with _$DefaultNamed { 32 | factory DefaultNamed({@Default(0) int a}) = _DefaultNamed; 33 | } 34 | 35 | @freezed 36 | abstract class RequiredNamed with _$RequiredNamed { 37 | factory RequiredNamed({required int a}) = _RequiredNamed; 38 | } 39 | 40 | @freezed 41 | abstract class NullableRequiredNamed with _$NullableRequiredNamed { 42 | factory NullableRequiredNamed({required int? a}) = _NullableRequiredNamed; 43 | } 44 | -------------------------------------------------------------------------------- /packages/freezed/lib/builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:build/build.dart'; 2 | import 'package:dart_style/dart_style.dart' show DartFormatter; 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | import 'package:pub_semver/pub_semver.dart' show Version; 5 | import 'package:source_gen/source_gen.dart'; 6 | 7 | import 'src/freezed_generator.dart'; 8 | 9 | /// Builds generators for `build_runner` to run 10 | Builder freezed(BuilderOptions options) { 11 | return PartBuilder( 12 | [ 13 | FreezedGenerator( 14 | Freezed.fromJson(options.config), 15 | format: options.config['format'] as bool? ?? false, 16 | ), 17 | ], 18 | '.freezed.dart', 19 | formatOutput: _defaultFormatOutput, 20 | header: ''' 21 | // GENERATED CODE - DO NOT MODIFY BY HAND 22 | // coverage:ignore-file 23 | // ignore_for_file: type=lint 24 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark 25 | ''', 26 | options: options, 27 | ); 28 | } 29 | 30 | String _defaultFormatOutput(String code, Version version) => 31 | DartFormatter(languageVersion: version).format(code); 32 | -------------------------------------------------------------------------------- /packages/freezed/test/custom_equals_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'integration/custom_equals.dart'; 3 | 4 | void main() { 5 | test('simple custom equals', () { 6 | expect(CustomEquals(name: 'a', id: 1), CustomEquals(name: 'a', id: 2)); 7 | }); 8 | 9 | test('custom equals in unions', () { 10 | expect( 11 | CustomEqualsFirst(name: 'a', id: 1), 12 | CustomEqualsSecond(name: 'a', active: true), 13 | ); 14 | }); 15 | 16 | test('custom equals with mixin and union', () { 17 | expect( 18 | EqualsWithUnionMixin.first(1) != EqualsWithUnionMixin.second('2'), 19 | isTrue, 20 | ); 21 | 22 | expect( 23 | EqualsWithUnionMixin.first(1) == EqualsWithUnionMixin.first(1), 24 | isFalse, 25 | ); 26 | 27 | expect( 28 | EqualsWithUnionMixin.second('1') == EqualsWithUnionMixin.second('2'), 29 | isTrue, 30 | ); 31 | }); 32 | 33 | test('custom equals with base class and union', () { 34 | expect( 35 | EqualsWithUnionExtends.first(1) != EqualsWithUnionExtends.second('2'), 36 | isTrue, 37 | ); 38 | 39 | expect( 40 | EqualsWithUnionExtends.first(1) == EqualsWithUnionExtends.first(1), 41 | isFalse, 42 | ); 43 | 44 | expect( 45 | EqualsWithUnionExtends.second('1') == EqualsWithUnionExtends.second('2'), 46 | isTrue, 47 | ); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /packages/freezed/migration_guide.md: -------------------------------------------------------------------------------- 1 | ## Migrate from v2 to v3 2 | 3 | ### Require keyword (`sealed` / `abstract`) 4 | 5 | Classes using the factory constructor now require a keyword `sealed` / `abstract`. 6 | 7 | ```diff 8 | @freezed 9 | -class Person with _$Person { 10 | +abstract class Person with _$Person { 11 | const factory Person({ 12 | required String firstName, 13 | required String lastName, 14 | required int age, 15 | }) = _Person; 16 | 17 | factory Person.fromJson(Map json) 18 | => _$PersonFromJson(json); 19 | } 20 | ``` 21 | 22 | ```diff 23 | @freezed 24 | -class Model with _$Model { 25 | +sealed class Model with _$Model { 26 | factory Model.first(String a) = First; 27 | factory Model.second(int b, bool c) = Second; 28 | } 29 | ``` 30 | 31 | ### Pattern matching 32 | 33 | Freezed no longer generates `.map`/`.when` extensions and their derivatives for freezed classes used for pattern matching. Instead, use Dart's built-in [pattern matching](https://dart.dev/language/patterns#matching) syntax. 34 | 35 | ```diff 36 | final model = Model.first('42'); 37 | 38 | -final res = model.map( 39 | - first: (String a) => 'first $a', 40 | - second: (int b, bool c) => 'second $b $c', 41 | -); 42 | +final res = switch (model) { 43 | + First(:final a) => 'first $a', 44 | + Second(:final b, :final c) => 'second $b $c', 45 | +}; 46 | 47 | ``` 48 | -------------------------------------------------------------------------------- /packages/freezed/test/concrete_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'integration/concrete.dart'; 4 | 5 | void main() { 6 | test('EmptyExtends', () { 7 | expect( 8 | EmptyExtends(42), 9 | isA().having( 10 | (source) => (source as EmptyExtends).value, 11 | 'value', 12 | 42, 13 | ), 14 | ); 15 | }); 16 | 17 | test('Concrete', () { 18 | expect( 19 | Concrete(42), 20 | isA() 21 | .having((source) => source.method(), 'method', 42) 22 | .having((source) => source.value, 'value', 42), 23 | ); 24 | }); 25 | 26 | test('can have const', () { 27 | const ConstConcrete(); 28 | }); 29 | 30 | test('MixedIn', () { 31 | expect(MixedIn().method(), 42); 32 | }); 33 | 34 | test('does not override toString if one is user-defined', () { 35 | expect(CustomToString().toString(), '42'); 36 | expect(MixedInToString().toString(), '42'); 37 | expect(BaseToString().toString(), '42'); 38 | expect(const Dollar($test: 'demo').toString(), r'Dollar($test: demo)'); 39 | expect( 40 | const DollarMiddle(te$st: 'demo').toString(), 41 | r'DollarMiddle(te$st: demo)', 42 | ); 43 | expect( 44 | const DollarEnd(test$: 'demo').toString(), 45 | r'DollarEnd(test$: demo)', 46 | ); 47 | expect(const $DollarClass().toString(), r'$DollarClass()'); 48 | expect(const DollarFactory.$().toString(), r'DollarFactory.$()'); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/extend.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'extend.freezed.dart'; 4 | 5 | class Base { 6 | const Base.named(this.value); 7 | final int value; 8 | } 9 | 10 | @freezed 11 | abstract class Subclass extends Base with _$Subclass { 12 | const Subclass._(super.value) : super.named(); 13 | const factory Subclass(int value) = _Subclass; 14 | } 15 | 16 | class BaseWithEqual { 17 | const BaseWithEqual([this.another = 0]); 18 | final int another; 19 | 20 | @override 21 | bool operator ==(Object other) { 22 | if (identical(this, other)) return true; 23 | if (other is! BaseWithEqual) return false; 24 | return another == other.another; 25 | } 26 | 27 | @override 28 | int get hashCode => another.hashCode; 29 | } 30 | 31 | @freezed 32 | class Subclass2 extends BaseWithEqual with _$Subclass2 { 33 | Subclass2([this.value = 0, super.another = 0]) : super(); 34 | 35 | @override 36 | final int value; 37 | } 38 | 39 | class EqualWithoutHashCode { 40 | @override 41 | // ignore: hash_and_equals 42 | bool operator ==(Object other) { 43 | if (identical(this, other)) return true; 44 | if (other is! EqualWithoutHashCode) return false; 45 | return true; 46 | } 47 | } 48 | 49 | class HashCodeWithoutEqual { 50 | @override 51 | // ignore: hash_and_equals 52 | int get hashCode => 42; 53 | } 54 | 55 | @freezed 56 | class Subclass3 extends EqualWithoutHashCode with _$Subclass3 { 57 | Subclass3(this.value) : super(); 58 | 59 | @override 60 | final int value; 61 | } 62 | 63 | @freezed 64 | class Subclass4 extends HashCodeWithoutEqual with _$Subclass4 { 65 | Subclass4(this.value) : super(); 66 | 67 | @override 68 | final int value; 69 | } 70 | -------------------------------------------------------------------------------- /packages/freezed/lib/src/templates/abstract_template.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed/src/freezed_generator.dart'; 2 | import 'package:freezed/src/models.dart'; 3 | 4 | import 'concrete_template.dart'; 5 | import 'copy_with.dart'; 6 | import 'properties.dart'; 7 | 8 | class Abstract { 9 | Abstract({ 10 | required this.data, 11 | required this.copyWith, 12 | required this.commonProperties, 13 | required this.globalData, 14 | }); 15 | 16 | final Class data; 17 | final CopyWith? copyWith; 18 | final List commonProperties; 19 | final Library globalData; 20 | 21 | @override 22 | String toString() { 23 | final needsAbstractGetters = 24 | data.options.toJson || 25 | copyWith != null || 26 | data.options.asString || 27 | data.options.equal; 28 | 29 | final abstractProperties = commonProperties 30 | // If toJson/==/toString/copyWith are enabled, always generate a `T get field`, 31 | // to enable those methods to use all properties. 32 | // Otherwise only do so for fields generated by factory constructors. 33 | .where((e) => needsAbstractGetters || e.isSynthetic) 34 | .expand((e) => [e.abstractGetter, if (!e.isFinal) e.abstractSetter]) 35 | .join(); 36 | 37 | var interfaces = [ 38 | if (globalData.hasDiagnostics && data.options.asString) 39 | 'DiagnosticableTreeMixin', 40 | ].join(); 41 | if (interfaces.isNotEmpty) interfaces = ' implements $interfaces'; 42 | 43 | return ''' 44 | /// @nodoc 45 | mixin _\$${data.name.public}${data.genericsDefinitionTemplate}$interfaces { 46 | 47 | $abstractProperties 48 | ${copyWith?.copyWithGetter(needsCast: true) ?? ''} 49 | ${methods(data, globalData, properties: commonProperties, name: data.name, escapedName: data.escapedName, source: Source.mixin)} 50 | } 51 | 52 | ${copyWith?.commonInterface ?? ''} 53 | ${copyWith?.commonConcreteImpl ?? ''} 54 | '''; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/optional_maybe.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'optional_maybe.freezed.dart'; 4 | part 'optional_maybe.g.dart'; 5 | 6 | @Freezed(map: FreezedMapOptions.none) 7 | abstract class OptionalMaybeMap with _$OptionalMaybeMap { 8 | const factory OptionalMaybeMap.first() = OptionalMaybeMap1; 9 | const factory OptionalMaybeMap.second() = OptionalMaybeMap2; 10 | } 11 | 12 | @Freezed(when: FreezedWhenOptions.none) 13 | abstract class OptionalMaybeWhen with _$OptionalMaybeWhen { 14 | const factory OptionalMaybeWhen.first() = OptionalMaybeWhen1; 15 | const factory OptionalMaybeWhen.second() = OptionalMaybeWhen2; 16 | } 17 | 18 | @Freezed(copyWith: false) 19 | abstract class OptionalCopyWith with _$OptionalCopyWith { 20 | const factory OptionalCopyWith([int? a]) = _OptionalCopyWith; 21 | } 22 | 23 | @Freezed(toStringOverride: false) 24 | class OptionalToString with _$OptionalToString { 25 | const factory OptionalToString() = _OptionalToString; 26 | } 27 | 28 | @Freezed(equal: false) 29 | class OptionalEqual with _$OptionalEqual { 30 | factory OptionalEqual() = _OptionalEqual; 31 | } 32 | 33 | @Freezed(map: FreezedMapOptions.all, when: FreezedWhenOptions.all) 34 | abstract class ForceUnionMethod with _$ForceUnionMethod { 35 | factory ForceUnionMethod() = _ForceUnionMethod; 36 | } 37 | 38 | @Freezed(map: FreezedMapOptions.all, when: FreezedWhenOptions.all) 39 | abstract class ForceUnionMethod2 with _$ForceUnionMethod2 { 40 | factory ForceUnionMethod2.two() = _ForceUnionMethod2; 41 | } 42 | 43 | @Freezed(toJson: false) 44 | class OptionalToJson with _$OptionalToJson { 45 | factory OptionalToJson() = _OptionalToJson; 46 | 47 | factory OptionalToJson.fromJson(Map json) => 48 | _$OptionalToJsonFromJson(json); 49 | } 50 | 51 | @Freezed(toJson: true) 52 | abstract class ForceToJson with _$ForceToJson { 53 | factory ForceToJson(int a) = _ForceToJson; 54 | } 55 | -------------------------------------------------------------------------------- /packages/freezed/test/generics_refs_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/results.dart'; 2 | import 'package:build_test/build_test.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | import 'common.dart'; 6 | import 'integration/generics_refs.dart'; 7 | 8 | void main() { 9 | test('has no issue', () async { 10 | final main = await resolveSources( 11 | {'freezed|test/integration/generics_refs.dart': useAssetReader}, 12 | (r) => r.libraries.firstWhere( 13 | (element) => 14 | element.firstFragment.source.toString().contains('generics_refs'), 15 | ), 16 | ); 17 | 18 | final errorResult = 19 | await main.session.getErrors( 20 | '/freezed/test/integration/generics_refs.freezed.dart', 21 | ) 22 | as ErrorsResult; 23 | 24 | expect(errorResult.errors, isEmpty); 25 | }); 26 | 27 | test('handles lists', () async { 28 | final page = const Page(); 29 | 30 | expect(PageList([page]).pages, [page]); 31 | 32 | await expectLater( 33 | compile(r''' 34 | import 'generics_refs.dart'; 35 | 36 | void main() { 37 | const page = Page(); 38 | PageList([page]); 39 | } 40 | '''), 41 | completes, 42 | ); 43 | 44 | await expectLater( 45 | compile(r''' 46 | import 'generics_refs.dart'; 47 | 48 | void main() { 49 | PageList([42]); 50 | } 51 | '''), 52 | throwsCompileError, 53 | ); 54 | }); 55 | 56 | test('handles maps', () async { 57 | final page = const Page(); 58 | 59 | expect(PageMap({'foo': page}).pages, {'foo': page}); 60 | 61 | await expectLater( 62 | compile(r''' 63 | import 'generics_refs.dart'; 64 | 65 | void main() { 66 | const page = Page(); 67 | PageMap({'foo': page}); 68 | } 69 | '''), 70 | completes, 71 | ); 72 | 73 | await expectLater( 74 | compile(r''' 75 | import 'generics_refs.dart'; 76 | 77 | void main() { 78 | PageMap({'foo': 42}); 79 | } 80 | '''), 81 | throwsCompileError, 82 | ); 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /packages/freezed_annotation/lib/freezed_annotation.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ignore_for_file: type=lint 4 | 5 | part of 'freezed_annotation.dart'; 6 | 7 | // ************************************************************************** 8 | // JsonSerializableGenerator 9 | // ************************************************************************** 10 | 11 | FreezedMapOptions _$FreezedMapOptionsFromJson(Map json) => FreezedMapOptions( 12 | map: json['map'] as bool?, 13 | mapOrNull: json['map_or_null'] as bool?, 14 | maybeMap: json['maybe_map'] as bool?, 15 | ); 16 | 17 | FreezedWhenOptions _$FreezedWhenOptionsFromJson(Map json) => FreezedWhenOptions( 18 | when: json['when'] as bool?, 19 | whenOrNull: json['when_or_null'] as bool?, 20 | maybeWhen: json['maybe_when'] as bool?, 21 | ); 22 | 23 | Freezed _$FreezedFromJson(Map json) => Freezed( 24 | unionKey: json['union_key'] as String? ?? 'runtimeType', 25 | unionValueCase: $enumDecodeNullable( 26 | _$FreezedUnionCaseEnumMap, json['union_value_case']), 27 | fallbackUnion: json['fallback_union'] as String?, 28 | copyWith: json['copy_with'] as bool?, 29 | equal: json['equal'] as bool?, 30 | toStringOverride: json['to_string_override'] as bool?, 31 | fromJson: json['from_json'] as bool?, 32 | toJson: json['to_json'] as bool?, 33 | map: const _FreezedMapOptionsConverter().fromJson(json['map']), 34 | when: const _FreezedWhenOptionsConverter().fromJson(json['when']), 35 | makeCollectionsUnmodifiable: 36 | json['make_collections_unmodifiable'] as bool? ?? true, 37 | addImplicitFinal: json['add_implicit_final'] as bool? ?? true, 38 | genericArgumentFactories: 39 | json['generic_argument_factories'] as bool? ?? false, 40 | ); 41 | 42 | const _$FreezedUnionCaseEnumMap = { 43 | FreezedUnionCase.none: 'none', 44 | FreezedUnionCase.kebab: 'kebab', 45 | FreezedUnionCase.pascal: 'pascal', 46 | FreezedUnionCase.snake: 'snake', 47 | FreezedUnionCase.screamingSnake: 'screaming_snake', 48 | }; 49 | -------------------------------------------------------------------------------- /packages/freezed/test/source_gen_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io' show Directory, File; 3 | 4 | import 'package:freezed/src/freezed_generator.dart'; 5 | import 'package:freezed_annotation/freezed_annotation.dart'; 6 | import 'package:source_gen_test/source_gen_test.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | Future main() async { 10 | final reader = await initializeLibraryReaderForDirectory( 11 | 'test', 12 | 'source_gen_src.dart', 13 | ); 14 | 15 | initializeBuildLogTracking(); 16 | 17 | testAnnotatedElements( 18 | reader, 19 | FreezedGenerator(Freezed.fromJson({}), format: false), 20 | ); 21 | 22 | test('generated files do not contain default source_gen header', () { 23 | // Read generated files from the integration folder. 24 | final generatedFiles = Directory('test/integration') 25 | .listSync(recursive: true) 26 | .whereType() 27 | .where((e) => e.path.endsWith('.freezed.dart')) 28 | .toList(); 29 | 30 | // Ensure we have files to test 31 | expect( 32 | generatedFiles, 33 | isNotEmpty, 34 | reason: 'Expected to find .freezed.dart files in test/integration', 35 | ); 36 | 37 | for (final file in generatedFiles) { 38 | final content = file.readAsStringSync(); 39 | 40 | // Verify the custom header is present 41 | expect(content, contains('// coverage:ignore-file')); 42 | expect( 43 | content.substring( 44 | 0, 45 | '// GENERATED CODE - DO NOT MODIFY BY HAND'.length + 1, 46 | ), 47 | startsWith('// GENERATED CODE - DO NOT MODIFY BY HAND'), 48 | reason: 'Start with GENERATED for Github Linguist', 49 | ); 50 | expect(content, contains('// ignore_for_file: type=lint')); 51 | 52 | // Verify the default source_gen header is NOT present 53 | expect( 54 | content, 55 | isNot(contains('// dart format width')), 56 | reason: 57 | 'File ${file.path} should not contain default source_gen header', 58 | ); 59 | } 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/generic.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'generic.freezed.dart'; 4 | 5 | class Model { 6 | Model(this.value); 7 | final T value; 8 | } 9 | 10 | @freezed 11 | abstract class AnyGeneric with _$AnyGeneric { 12 | factory AnyGeneric(T value) = _AnyGeneric; 13 | } 14 | 15 | @freezed 16 | abstract class NullableGeneric with _$NullableGeneric { 17 | factory NullableGeneric(T value) = _NullableGeneric; 18 | } 19 | 20 | @freezed 21 | abstract class NonNullableGeneric 22 | with _$NonNullableGeneric { 23 | factory NonNullableGeneric(T value) = _NonNullableGeneric; 24 | } 25 | 26 | @freezed 27 | abstract class GenericOrNull with _$GenericOrNull { 28 | factory GenericOrNull(T? value) = _GenericOrNull; 29 | } 30 | 31 | @freezed 32 | abstract class Generic> with _$Generic { 33 | factory Generic(T model) = _Generic; 34 | } 35 | 36 | @freezed 37 | abstract class MultiGeneric> with _$MultiGeneric { 38 | factory MultiGeneric(T model) = _MultiGeneric; 39 | } 40 | 41 | @freezed 42 | abstract class MultipleConstructors with _$MultipleConstructors { 43 | factory MultipleConstructors(bool flag) = Default; 44 | factory MultipleConstructors.first(A a) = First; 45 | factory MultipleConstructors.second(B b) = Second; 46 | } 47 | 48 | @freezed 49 | abstract class Union with _$Union { 50 | const factory Union(T value) = Data; 51 | const factory Union.loading() = Loading; 52 | const factory Union.error([String? message]) = ErrorDetails; 53 | } 54 | 55 | @freezed 56 | abstract class ComplexParameters with _$ComplexParameters { 57 | const factory ComplexParameters(List value) = _ComplexParameters; 58 | } 59 | 60 | @freezed 61 | abstract class Inner with _$Inner { 62 | const factory Inner({I? data}) = _Inner; 63 | } 64 | 65 | @freezed 66 | abstract class Outer with _$Outer { 67 | const factory Outer({Inner? innerData}) = _Outer; 68 | } 69 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/equal.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'equal.freezed.dart'; 4 | 5 | @freezed 6 | abstract class Simple with _$Simple { 7 | factory Simple(int value) = _Simple; 8 | } 9 | 10 | @freezed 11 | abstract class ListEqual with _$ListEqual { 12 | factory ListEqual(List value) = _ListEqual; 13 | } 14 | 15 | @freezed 16 | abstract class MapEqual with _$MapEqual { 17 | factory MapEqual(Map value) = _MapEqual; 18 | } 19 | 20 | @freezed 21 | abstract class SetEqual with _$SetEqual { 22 | factory SetEqual(Set value) = _SetEqual; 23 | } 24 | 25 | @freezed 26 | abstract class IterableEqual with _$IterableEqual { 27 | factory IterableEqual(Iterable value) = _IterableEqual; 28 | } 29 | 30 | @freezed 31 | abstract class ListObjectEqual with _$ListObjectEqual { 32 | factory ListObjectEqual(List value) = _ListObjectEqual; 33 | } 34 | 35 | @freezed 36 | abstract class ListDynamicEqual with _$ListDynamicEqual { 37 | factory ListDynamicEqual(List value) = _ListDynamicEqual; 38 | } 39 | 40 | @freezed 41 | abstract class ObjectEqual with _$ObjectEqual { 42 | factory ObjectEqual(Object value) = _ObjectEqual; 43 | } 44 | 45 | @freezed 46 | abstract class NullableObjectEqual with _$NullableObjectEqual { 47 | factory NullableObjectEqual(Object? value) = _NullableObjectEqual; 48 | } 49 | 50 | @freezed 51 | abstract class DynamicEqual with _$DynamicEqual { 52 | factory DynamicEqual(dynamic value) = _DynamicEqual; 53 | } 54 | 55 | @freezed 56 | abstract class Generic with _$Generic { 57 | factory Generic(T value) = _Generic; 58 | } 59 | 60 | @freezed 61 | abstract class GenericObject with _$GenericObject { 62 | factory GenericObject(T value) = _GenericObject; 63 | } 64 | 65 | @freezed 66 | abstract class GenericIterable> 67 | with _$GenericIterable { 68 | factory GenericIterable(T value) = _GenericIterable; 69 | } 70 | 71 | @freezed 72 | abstract class ObjectWithOtherProperty with _$ObjectWithOtherProperty { 73 | factory ObjectWithOtherProperty(List other) = _ObjectWithOtherProperty; 74 | } 75 | -------------------------------------------------------------------------------- /packages/freezed/example/lib/non_diagnosticable.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'non_diagnosticable.freezed.dart'; 4 | 5 | @freezed 6 | abstract class Example with _$Example { 7 | factory Example(int a, String b) = _Example; 8 | 9 | factory Example.named(T c) = _Example2; 10 | } 11 | 12 | @freezed 13 | abstract class SimpleImplements with _$SimpleImplements { 14 | const factory SimpleImplements.person(String name, int age) = SimplePerson; 15 | 16 | @With>() 17 | const factory SimpleImplements.street(String name) = SimpleStreet; 18 | 19 | @With() 20 | const factory SimpleImplements.city(String name, int population) = SimpleCity; 21 | 22 | @With() 23 | @Implements() 24 | const factory SimpleImplements.country(String name, int population) = 25 | SimpleCountry; 26 | } 27 | 28 | @freezed 29 | abstract class CustomMethodImplements with _$CustomMethodImplements { 30 | const CustomMethodImplements._(); 31 | 32 | const factory CustomMethodImplements.person(String name, int age) = 33 | PersonCustomMethod; 34 | 35 | @With() 36 | @With>() 37 | const factory CustomMethodImplements.street(String name) = StreetCustomMethod; 38 | 39 | @With() 40 | @Implements() 41 | const factory CustomMethodImplements.city(String name, int population) = 42 | CityCustomMethod; 43 | 44 | @Implements() 45 | @Implements() 46 | const factory CustomMethodImplements.duplex(String name) = DuplexCustomMethod; 47 | 48 | void function() {} 49 | } 50 | 51 | @freezed 52 | abstract class GenericImplements with _$GenericImplements { 53 | const factory GenericImplements.person(String name, int age) = 54 | GenericPerson; 55 | 56 | @With() 57 | @Implements() 58 | const factory GenericImplements.city(String name, int population) = 59 | GenericCity; 60 | } 61 | 62 | abstract class GeographicArea { 63 | String get name; 64 | } 65 | 66 | mixin class AdministrativeArea {} 67 | 68 | mixin class House {} 69 | 70 | mixin class Shop {} 71 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/custom_equals.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'custom_equals.freezed.dart'; 4 | 5 | @freezed 6 | abstract class CustomEquals with _$CustomEquals { 7 | CustomEquals._(); 8 | factory CustomEquals({String? name, int? id}) = _CustomEquals; 9 | 10 | @override 11 | bool operator ==(Object o) => o is CustomEquals && o.name == name; 12 | 13 | @override 14 | int get hashCode => name.hashCode; 15 | } 16 | 17 | @freezed 18 | abstract class CustomEqualsWithUnion with _$CustomEqualsWithUnion { 19 | CustomEqualsWithUnion._(); 20 | factory CustomEqualsWithUnion.first({String? name, int? id}) = 21 | CustomEqualsFirst; 22 | 23 | factory CustomEqualsWithUnion.second({String? name, bool? active}) = 24 | CustomEqualsSecond; 25 | 26 | @override 27 | bool operator ==(Object o) => o is CustomEqualsWithUnion && o.name == name; 28 | 29 | @override 30 | int get hashCode => name.hashCode; 31 | } 32 | 33 | mixin MyClass { 34 | @override 35 | bool operator ==(Object o) => false; 36 | 37 | @override 38 | int get hashCode => super.hashCode; 39 | } 40 | 41 | @freezed 42 | abstract class EqualsWithUnionMixin with _$EqualsWithUnionMixin { 43 | EqualsWithUnionMixin._(); 44 | 45 | @With() 46 | factory EqualsWithUnionMixin.first(int a) = UnionMixinFirst; 47 | 48 | factory EqualsWithUnionMixin.second(String b) = UnionMixinSecond; 49 | 50 | @override 51 | bool operator ==(Object o) => true; 52 | 53 | @override 54 | int get hashCode => super.hashCode; 55 | } 56 | 57 | @freezed 58 | abstract class EqualsWithUnionExtends extends CustomExtends 59 | with _$EqualsWithUnionExtends { 60 | EqualsWithUnionExtends._(); 61 | 62 | @With() 63 | factory EqualsWithUnionExtends.first(int a) = UnionExtendsFirst; 64 | 65 | factory EqualsWithUnionExtends.second(String b) = UnionExtendsSecond; 66 | 67 | @override 68 | bool operator ==(Object o) => true; 69 | 70 | @override 71 | int get hashCode => super.hashCode; 72 | } 73 | 74 | class CustomExtends { 75 | @override 76 | bool operator ==(Object o) => true; 77 | 78 | @override 79 | int get hashCode => 0; 80 | } 81 | -------------------------------------------------------------------------------- /packages/freezed/test/bidirectional_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/results.dart'; 2 | import 'package:build_test/build_test.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | import 'integration/bidirectional.dart'; 6 | 7 | void main() { 8 | test('has no issue', () async { 9 | final main = await resolveSources( 10 | {'freezed|test/integration/bidirectional.dart': useAssetReader}, 11 | (r) => r.libraries.firstWhere( 12 | (element) => 13 | element.firstFragment.source.toString().contains('bidirectional'), 14 | ), 15 | ); 16 | 17 | final errorResult = 18 | await main.session.getErrors( 19 | '/freezed/test/integration/bidirectional.freezed.dart', 20 | ) 21 | as ErrorsResult; 22 | 23 | expect(errorResult.errors, isEmpty); 24 | }); 25 | 26 | test('bidirectional deep_copy', () { 27 | final person = Person( 28 | name: 'Adam', 29 | age: 36, 30 | appointment: Appointment( 31 | title: 'Some Appointment', 32 | creator: Person( 33 | name: 'Bob', 34 | age: 24, 35 | appointment: Appointment(title: 'Other Appointment'), 36 | ), 37 | ), 38 | ); 39 | 40 | expect( 41 | person.copyWith.appointment!.creator!(name: 'Steve'), 42 | Person( 43 | name: 'Adam', 44 | age: 36, 45 | appointment: Appointment( 46 | title: 'Some Appointment', 47 | creator: Person( 48 | name: 'Steve', 49 | age: 24, 50 | appointment: Appointment(title: 'Other Appointment'), 51 | ), 52 | ), 53 | ), 54 | ); 55 | 56 | expect( 57 | person.copyWith.appointment!.creator!.appointment!( 58 | title: 'Some New Appointment', 59 | ), 60 | Person( 61 | name: 'Adam', 62 | age: 36, 63 | appointment: Appointment( 64 | title: 'Some Appointment', 65 | creator: Person( 66 | name: 'Bob', 67 | age: 24, 68 | appointment: Appointment(title: 'Some New Appointment'), 69 | ), 70 | ), 71 | ), 72 | ); 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/implements_decorator.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'implements_decorator.freezed.dart'; 4 | 5 | @freezed 6 | abstract class SimpleImplements with _$SimpleImplements { 7 | const factory SimpleImplements.person(String name, int age) = SimplePerson; 8 | 9 | @With>() 10 | const factory SimpleImplements.street(String name) = SimpleStreet; 11 | 12 | @With() 13 | const factory SimpleImplements.city(String name, int population) = SimpleCity; 14 | 15 | @With() 16 | @Implements() 17 | const factory SimpleImplements.country(String name, int population) = 18 | SimpleCountry; 19 | } 20 | 21 | @freezed 22 | abstract class CustomMethodImplements with _$CustomMethodImplements { 23 | const CustomMethodImplements._(); 24 | 25 | const factory CustomMethodImplements.person(String name, int age) = 26 | PersonCustomMethod; 27 | 28 | @With() 29 | @With>() 30 | const factory CustomMethodImplements.street(String name) = StreetCustomMethod; 31 | 32 | @With() 33 | @Implements() 34 | @Implements() 35 | const factory CustomMethodImplements.city(String name, int population) = 36 | CityCustomMethod; 37 | 38 | void function() {} 39 | } 40 | 41 | @freezed 42 | abstract class GenericImplements with _$GenericImplements { 43 | const factory GenericImplements.person(String name, int age) = 44 | GenericPerson; 45 | 46 | @With.fromString('AdministrativeArea') 47 | @Implements.fromString('Generic') 48 | const factory GenericImplements.street(String name, T value) = 49 | GenericStreet; 50 | 51 | @With() 52 | @Implements() 53 | const factory GenericImplements.city(String name, int population) = 54 | GenericCity; 55 | } 56 | 57 | abstract class Generic { 58 | T get value; 59 | } 60 | 61 | abstract class GeographicArea { 62 | String get name; 63 | } 64 | 65 | abstract class Named { 66 | String get name; 67 | } 68 | 69 | mixin AdministrativeArea { 70 | T method(T value) => value; 71 | } 72 | 73 | mixin class House {} 74 | 75 | mixin class Shop {} 76 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/deep_copy.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'deep_copy.freezed.dart'; 4 | 5 | @freezed 6 | abstract class DeepWithManualField with _$DeepWithManualField { 7 | const factory DeepWithManualField(ManualField manual) = _DeepWithManualField; 8 | } 9 | 10 | @freezed 11 | abstract class ManualField with _$ManualField { 12 | const ManualField._({required this.value}); 13 | const factory ManualField(int value) = _ManualField; 14 | 15 | @override 16 | final int value; 17 | } 18 | 19 | @freezed 20 | abstract class Company with _$Company { 21 | factory Company({String? name, Director? director}) = CompanySubclass; 22 | } 23 | 24 | @freezed 25 | abstract class Director with _$Director { 26 | factory Director({String? name, Assistant? assistant}) = _Director; 27 | } 28 | 29 | @freezed 30 | abstract class Assistant with _$Assistant { 31 | factory Assistant({String? name, int? age}) = _Assistant; 32 | } 33 | 34 | @freezed 35 | abstract class NoCommonProperty with _$NoCommonProperty { 36 | factory NoCommonProperty() = NoCommonPropertyEmpty; 37 | factory NoCommonProperty.assistant(Assistant assistant) = 38 | NoCommonPropertyAssistant; 39 | } 40 | 41 | @freezed 42 | abstract class Union with _$Union { 43 | factory Union.first(Assistant shared, Assistant first) = UnionFirst; 44 | factory Union.second(Assistant shared, Assistant second) = UnionSecond; 45 | } 46 | 47 | @freezed 48 | abstract class _Private with _$Private { 49 | factory _Private(Assistant assistant) = __Private; 50 | } 51 | 52 | @freezed 53 | abstract class DeepGeneric with _$DeepGeneric { 54 | factory DeepGeneric(Generic value, T second) = _DeepGeneric; 55 | } 56 | 57 | @freezed 58 | abstract class Generic with _$Generic { 59 | factory Generic(T value, T value2) = _Generic; 60 | } 61 | 62 | @freezed 63 | abstract class Recursive with _$Recursive { 64 | factory Recursive([Recursive? value]) = _Recursive; 65 | } 66 | 67 | @freezed 68 | abstract class DisabledDeepCopyWith with _$DisabledDeepCopyWith { 69 | factory DisabledDeepCopyWith(DisabledCopy disabled) = _DisabledDeepCopyWith; 70 | } 71 | 72 | @Freezed(copyWith: false) 73 | abstract class DisabledCopy with _$DisabledCopy { 74 | factory DisabledCopy(DisabledCopy disabled) = _DisabledCopy; 75 | } 76 | -------------------------------------------------------------------------------- /packages/freezed_lint/lib/src/missing_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/error/error.dart' hide LintCode; 2 | import 'package:analyzer/error/listener.dart' show ErrorReporter; 3 | import 'package:custom_lint_builder/custom_lint_builder.dart'; 4 | import 'package:freezed_lint/src/tools/freezed_annotation_checker.dart'; 5 | 6 | class MissingMixin extends DartLintRule { 7 | MissingMixin() : super(code: _code); 8 | 9 | static const _code = LintCode( 10 | name: 'freezed_missing_mixin', 11 | problemMessage: 'Freezed class should mixin with {0}', 12 | ); 13 | 14 | @override 15 | void run( 16 | CustomLintResolver resolver, 17 | ErrorReporter reporter, 18 | CustomLintContext context, 19 | ) { 20 | context.registry.addClassDeclaration((node) { 21 | final element = node.declaredFragment?.element; 22 | if (element == null) return; 23 | 24 | final annotation = freezedAnnotationChecker.hasAnnotationOfExact(element); 25 | if (!annotation) return; 26 | 27 | final name = '_\$${element.name3}'; 28 | final withClause = node.withClause; 29 | if (withClause == null) { 30 | reporter.atElement2(element, _code, arguments: [name]); 31 | return; 32 | } 33 | 34 | final mixins = withClause.mixinTypes; 35 | if (mixins.any((m) => name == m.name2.lexeme)) return; 36 | reporter.atElement2(element, _code, arguments: [name]); 37 | }); 38 | } 39 | 40 | @override 41 | List getFixes() => [_AddMixinFreezedClassFix()]; 42 | } 43 | 44 | class _AddMixinFreezedClassFix extends DartFix { 45 | @override 46 | void run( 47 | CustomLintResolver resolver, 48 | ChangeReporter reporter, 49 | CustomLintContext context, 50 | AnalysisError analysisError, 51 | List others, 52 | ) { 53 | context.registry.addClassDeclaration((node) { 54 | if (!analysisError.sourceRange.intersects(node.sourceRange)) return; 55 | reporter.createChangeBuilder( 56 | message: 'Add mixin _\$${node.name}', 57 | priority: 1, 58 | )..addDartFileEdit((builder) { 59 | final element = node.declaredFragment?.element; 60 | if (element == null) return; 61 | 62 | final name = element.displayName; 63 | final offset = node.leftBracket.offset - 1; 64 | if (node.withClause != null) { 65 | builder.addSimpleInsertion(offset, ', _\$$name'); 66 | } else { 67 | builder.addSimpleInsertion(offset, ' with _\$$name'); 68 | } 69 | }); 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/freezed/example/test/diagnosticable_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/diagnosticable.dart' as diagnosticable; 2 | import 'package:example/non_diagnosticable.dart' as non_diagnosticable; 3 | import 'package:example/time_slot.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_test/flutter_test.dart'; 7 | 8 | void main() { 9 | test('overriding toString works', () { 10 | // regression test for https://github.com/rrousselGit/freezed/issues/221 11 | 12 | expect(diagnosticable.ToString().toString(), 'MyToString()'); 13 | }); 14 | 15 | test('use Diagnosticable instead of toString if possible', () { 16 | var value = diagnosticable.Example(42, '21'); 17 | 18 | expect(value, isA()); 19 | expect(value.toString(), 'Example(a: 42, b: 21)'); 20 | 21 | value = diagnosticable.Example.named(42); 22 | expect(value, isA()); 23 | expect(value.toString(), 'Example.named(c: 42)'); 24 | }); 25 | 26 | test('debugFillProperties', () { 27 | final properties = DiagnosticPropertiesBuilder(); 28 | var value = diagnosticable.Example(42, '21'); 29 | 30 | // ignore: invalid_use_of_protected_member 31 | (value as Diagnosticable).debugFillProperties(properties); 32 | 33 | expect(properties.properties, [ 34 | isA>() 35 | .having((d) => d.name, 'name', 'type') 36 | .having((d) => d.value, 'value', 'Example'), 37 | isA>() 38 | .having((d) => d.name, 'name', 'a') 39 | .having((d) => d.value, 'value', 42), 40 | isA>() 41 | .having((d) => d.name, 'name', 'b') 42 | .having((d) => d.value, 'value', '21'), 43 | ]); 44 | }); 45 | 46 | test('noop if Diagnosticable not available', () { 47 | var value = non_diagnosticable.Example(42, '21'); 48 | 49 | expect(value, isNot(isA())); 50 | expect(value.toString(), 'Example(a: 42, b: 21)'); 51 | 52 | value = non_diagnosticable.Example.named(42); 53 | expect(value, isNot(isA())); 54 | expect(value.toString(), 'Example.named(c: 42)'); 55 | }); 56 | 57 | test('timeslot is not Diagnosticable', () { 58 | final timeslot = TimeSlot( 59 | start: const TimeOfDay(hour: 10, minute: 30), 60 | end: const TimeOfDay(hour: 12, minute: 45), 61 | ); 62 | 63 | expect(timeslot, isNot(isA())); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | schedule: 9 | # runs the CI everyday at 10AM 10 | - cron: "0 10 * * *" 11 | 12 | jobs: 13 | freezed: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | package: 19 | - packages/freezed 20 | - packages/freezed/example 21 | - packages/freezed_annotation 22 | - packages/freezed_lint 23 | channel: 24 | - stable 25 | dependencies: 26 | - get 27 | - downgrade 28 | exclude: 29 | - channel: stable 30 | dependencies: downgrade 31 | - package: packages/freezed_lint 32 | channel: stable 33 | 34 | steps: 35 | - uses: actions/checkout@v5 36 | 37 | - uses: subosito/flutter-action@v2 38 | with: 39 | channel: ${{ matrix.channel }} 40 | cache: ${{ matrix.channel == 'stable' }} 41 | 42 | # It is executed separately 43 | - name: Removing example folder 44 | # Retain example for lint golden test 45 | if: matrix.package != 'packages/freezed_lint' 46 | run: rm -rf example 47 | working-directory: ${{ matrix.package }} 48 | 49 | - name: Install dependencies 50 | run: | 51 | flutter pub ${{ matrix.dependencies }} 52 | working-directory: ${{ matrix.package }} 53 | 54 | - name: Check format 55 | # Check dart format only on stable 56 | if: matrix.channel == 'stable' 57 | run: dart format --set-exit-if-changed . 58 | working-directory: ${{ matrix.package }} 59 | 60 | - name: Generate 61 | run: | 62 | if grep -q build_runner "pubspec.yaml"; then 63 | dart pub run build_runner build --delete-conflicting-outputs 64 | fi 65 | working-directory: ${{ matrix.package }} 66 | 67 | - name: Analyze 68 | run: flutter analyze 69 | working-directory: ${{ matrix.package }} 70 | 71 | - name: Run tests 72 | run: | 73 | if grep -q "name: example" "pubspec.yaml"; then 74 | flutter test 75 | else 76 | dart test 77 | fi 78 | working-directory: ${{ matrix.package }} 79 | 80 | - name: Lint golden test 81 | if: matrix.package == 'packages/freezed_lint' 82 | run: | 83 | dart pub get 84 | dart analyze 85 | dart run custom_lint 86 | working-directory: 'packages/freezed_lint/example' 87 | -------------------------------------------------------------------------------- /packages/freezed/test/typedef_parameter_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/results.dart'; 2 | import 'package:analyzer/dart/element/nullability_suffix.dart'; 3 | import 'package:analyzer/dart/element/type.dart'; 4 | import 'package:build_test/build_test.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | void main() { 8 | test('has no issue', () async { 9 | final main = await resolveSources( 10 | {'freezed|test/integration/typedef_parameter.dart': useAssetReader}, 11 | (r) => r.libraries.firstWhere( 12 | (element) => element.firstFragment.source.toString().contains( 13 | 'typedef_parameter', 14 | ), 15 | ), 16 | ); 17 | 18 | final errorResult = 19 | await main.session.getErrors( 20 | '/freezed/test/integration/typedef_parameter_test.freezed.dart', 21 | ) 22 | as ErrorsResult; 23 | 24 | expect(errorResult.errors, isEmpty); 25 | }); 26 | 27 | test('generates correct typedefs', () async { 28 | final main = await resolveSources( 29 | {'freezed|test/integration/typedef_parameter.dart': useAssetReader}, 30 | (r) => r.libraries.firstWhere( 31 | (element) => element.firstFragment.source.toString().contains( 32 | 'typedef_parameter', 33 | ), 34 | ), 35 | readAllSourcesFromFilesystem: true, 36 | ); 37 | 38 | var freezedClass = main.classes.firstWhere( 39 | (element) => element.name3 == '_ClassWithTypedef', 40 | ); 41 | 42 | var constructor = freezedClass.constructors2.firstWhere( 43 | (element) => element.name3 == 'new', 44 | ); 45 | 46 | var a = constructor.formalParameters.first.type; 47 | expect(a, isA()); 48 | expect(a.alias!.element2.name3, equals('MyTypedef')); 49 | 50 | var b = constructor.formalParameters[1].type; 51 | expect(b, isA()); 52 | expect(b.alias!.element2.name3, equals('MyTypedef')); 53 | expect(b.nullabilitySuffix, equals(NullabilitySuffix.question)); 54 | 55 | var c = constructor.formalParameters[2].type; 56 | expect(c, isA()); 57 | expect(c.alias!.element2.name3, equals('ExternalTypedef')); 58 | 59 | var d = constructor.formalParameters[3].type; 60 | expect(d, isA()); 61 | expect(d.alias!.element2.name3, equals('ExternalTypedefTwo')); 62 | 63 | var e = constructor.formalParameters[4].type; 64 | expect(e, isA()); 65 | expect(e.alias!.element2.name3, equals('GenericTypedef')); 66 | expect(e.alias!.typeArguments.toString(), equals('[int, bool]')); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /packages/freezed/test/mixed_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | part 'mixed_test.freezed.dart'; 5 | 6 | @freezed 7 | sealed class Mixed with _$Mixed { 8 | Mixed._(); 9 | factory Mixed.a(num common, int value, String a) = MixedA; 10 | factory Mixed.b(num common, double value, int b) = FreezedImplements; 11 | factory Mixed.c(num common, double value, bool c) = ManualClass; 12 | factory Mixed.d(num common, double value, int d) = FreezedExtends; 13 | } 14 | 15 | @freezed 16 | class FreezedImplements with _$FreezedImplements implements Mixed { 17 | FreezedImplements(this.common, this.value, this.b); 18 | 19 | @override 20 | final num common; 21 | @override 22 | final double value; 23 | @override 24 | final int b; 25 | } 26 | 27 | class ManualClass extends Mixed { 28 | ManualClass(this.common, this.value, this.c) : super._(); 29 | 30 | @override 31 | final num common; 32 | @override 33 | final double value; 34 | final bool c; 35 | } 36 | 37 | @freezed 38 | class FreezedExtends extends Mixed with _$FreezedExtends { 39 | FreezedExtends(this.common, this.value, this.d) : super._(); 40 | 41 | @override 42 | final num common; 43 | @override 44 | final double value; 45 | @override 46 | final int d; 47 | } 48 | 49 | @freezed 50 | sealed class DiamondA with _$DiamondA { 51 | factory DiamondA(int common) = DiamondLeaf; 52 | } 53 | 54 | @freezed 55 | sealed class DiamondB with _$DiamondB { 56 | factory DiamondB(int common) = DiamondLeaf; 57 | } 58 | 59 | @freezed 60 | sealed class DiamondLeaf with _$DiamondLeaf implements DiamondA, DiamondB { 61 | factory DiamondLeaf(int common) = _DiamondLeaf; 62 | } 63 | 64 | void main() { 65 | group('Mixed', () { 66 | test('a', () { 67 | final object = Mixed.a(42, 21, 'a'); 68 | expect(object, isA()); 69 | expect(object.toString(), 'Mixed.a(common: 42, value: 21, a: a)'); 70 | }); 71 | 72 | test('b', () { 73 | final object = Mixed.b(42, 21, 42); 74 | expect(object, isA()); 75 | expect( 76 | object.toString(), 77 | 'FreezedImplements(common: 42, value: 21.0, b: 42)', 78 | ); 79 | }); 80 | 81 | test('c', () { 82 | final object = Mixed.c(42, 21, true); 83 | expect(object, isA()); 84 | expect(object.toString(), 'Mixed(common: 42, value: 21.0)'); 85 | }); 86 | 87 | test('d', () { 88 | final object = Mixed.d(42, 21, 42); 89 | expect(object, isA()); 90 | expect( 91 | object.toString(), 92 | 'FreezedExtends(common: 42, value: 21.0, d: 42)', 93 | ); 94 | }); 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | exclude: 3 | - "**/*.g.dart" 4 | language: 5 | strict-casts: true 6 | strict-inference: true 7 | strict-raw-types: true 8 | errors: 9 | invalid_annotation_target: ignore 10 | # Remove when we require analyzer >=8 11 | deprecated_member_use: ignore 12 | 13 | linter: 14 | rules: 15 | # - public_member_api_docs 16 | - annotate_overrides 17 | - avoid_empty_else 18 | - avoid_function_literals_in_foreach_calls 19 | - avoid_init_to_null 20 | - avoid_null_checks_in_equality_operators 21 | - avoid_relative_lib_imports 22 | # - avoid_renaming_method_parameters 23 | - use_function_type_syntax_for_parameters 24 | - avoid_return_types_on_setters 25 | - avoid_types_as_parameter_names 26 | - avoid_unused_constructor_parameters 27 | - await_only_futures 28 | - camel_case_types 29 | - cancel_subscriptions 30 | - cascade_invocations 31 | - comment_references 32 | - constant_identifier_names 33 | - control_flow_in_finally 34 | - directives_ordering 35 | - empty_catches 36 | - empty_constructor_bodies 37 | - empty_statements 38 | - hash_and_equals 39 | - implementation_imports 40 | # Deprecated rule 41 | # - invariant_booleans 42 | - collection_methods_unrelated_type 43 | - library_names 44 | - library_prefixes 45 | # - lines_longer_than_80_chars 46 | - no_adjacent_strings_in_list 47 | - no_duplicate_case_values 48 | - non_constant_identifier_names 49 | - null_closures 50 | - omit_local_variable_types 51 | - only_throw_errors 52 | - overridden_fields 53 | - package_names 54 | - package_prefixed_library_names 55 | - prefer_adjacent_string_concatenation 56 | - prefer_collection_literals 57 | - prefer_conditional_assignment 58 | - prefer_const_constructors 59 | - prefer_contains 60 | - prefer_final_fields 61 | - prefer_initializing_formals 62 | - prefer_interpolation_to_compose_strings 63 | - prefer_is_empty 64 | - prefer_is_not_empty 65 | - prefer_single_quotes 66 | - prefer_typing_uninitialized_variables 67 | - recursive_getters 68 | - slash_for_doc_comments 69 | - test_types_in_equals 70 | - throw_in_finally 71 | - type_init_formals 72 | - unawaited_futures 73 | - unnecessary_brace_in_string_interps 74 | - unnecessary_const 75 | - unnecessary_getters_setters 76 | - unnecessary_lambdas 77 | - unnecessary_new 78 | - unnecessary_null_aware_assignments 79 | - unnecessary_statements 80 | - unnecessary_this 81 | - unrelated_type_equality_checks 82 | - use_rethrow_when_possible 83 | - valid_regexps 84 | - cast_nullable_to_non_nullable 85 | -------------------------------------------------------------------------------- /packages/freezed/lib/src/templates/prototypes.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element2.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:source_gen/source_gen.dart'; 4 | 5 | import 'parameter_template.dart'; 6 | 7 | List parseDecorators(List metadata) { 8 | return [ 9 | for (final meta in metadata) 10 | if (!meta.isRequired && !meta.isDefault) meta.toSource(), 11 | ]; 12 | } 13 | 14 | String wrapClassField(String name) { 15 | return name.contains(r'$') ? '\${$name}' : '\$$name'; 16 | } 17 | 18 | extension FreezedElementAnnotation on ElementAnnotation { 19 | /// if the element is decorated with `@Default(value)` 20 | bool get isDefault { 21 | return const TypeChecker.typeNamed( 22 | Default, 23 | inPackage: 'freezed_annotation', 24 | ).isExactlyType(computeConstantValue()!.type!); 25 | } 26 | 27 | /// if the element is decorated with `@With` 28 | bool get isWith { 29 | return const TypeChecker.typeNamed( 30 | With, 31 | inPackage: 'freezed_annotation', 32 | ).isExactlyType(computeConstantValue()!.type!); 33 | } 34 | 35 | /// if the element is decorated with `@Implements` 36 | bool get isImplements { 37 | return const TypeChecker.typeNamed( 38 | Implements, 39 | inPackage: 'freezed_annotation', 40 | ).isExactlyType(computeConstantValue()!.type!); 41 | } 42 | } 43 | 44 | bool isDefaultConstructor(ConstructorElement2 constructor) { 45 | return constructor.name3 == 'new'; 46 | } 47 | 48 | String constructorNameToCallbackName(String constructorName) { 49 | return constructorName.isEmpty ? '\$default' : constructorName; 50 | } 51 | 52 | String toJsonParameters( 53 | GenericsParameterTemplate parameters, 54 | bool genericArgumentFactories, 55 | ) { 56 | if (!genericArgumentFactories) { 57 | return ''; 58 | } 59 | return '${parameters.typeParameters.map((t) => 'Object? Function($t) toJson$t').join(',')}'; 60 | } 61 | 62 | String toJsonArguments( 63 | GenericsParameterTemplate parameters, 64 | bool genericArgumentFactories, 65 | ) { 66 | if (!genericArgumentFactories) { 67 | return ''; 68 | } 69 | return '${parameters.typeParameters.map((t) => 'toJson$t').join(',')}'; 70 | } 71 | 72 | String fromJsonParameters( 73 | GenericsParameterTemplate parameters, 74 | bool genericArgumentFactories, 75 | ) { 76 | if (!genericArgumentFactories) { 77 | return ''; 78 | } 79 | return ',${parameters.typeParameters.map((t) => '$t Function(Object?) fromJson$t').join(',')}'; 80 | } 81 | 82 | String fromJsonArguments( 83 | GenericsParameterTemplate parameters, 84 | bool genericArgumentFactories, 85 | ) { 86 | if (!genericArgumentFactories) { 87 | return ''; 88 | } 89 | return ',${parameters.typeParameters.map((t) => 'fromJson$t').join(',')}'; 90 | } 91 | -------------------------------------------------------------------------------- /packages/freezed/lib/src/ast.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/ast/ast.dart'; 2 | import 'package:analyzer/dart/ast/token.dart'; 3 | import 'package:analyzer/dart/element/element2.dart'; 4 | 5 | extension AstX on AstNode { 6 | String? get documentation { 7 | final builder = StringBuffer(); 8 | 9 | for ( 10 | Token? token = beginToken.precedingComments; 11 | token != null; 12 | token = token.next 13 | ) { 14 | builder.writeln(token); 15 | } 16 | 17 | if (builder.isEmpty) return null; 18 | 19 | return builder.toString(); 20 | } 21 | } 22 | 23 | extension ClassX on ClassDeclaration { 24 | bool get hasCustomToString { 25 | final element = declaredFragment!.element; 26 | 27 | for (final type in [ 28 | element, 29 | ...element.allSupertypes 30 | .where((e) => !e.isDartCoreObject) 31 | .map((e) => e.element3), 32 | ]) { 33 | for (final method in type.methods2) { 34 | if (method.name3 == 'toString') { 35 | return true; 36 | } 37 | } 38 | } 39 | 40 | return false; 41 | } 42 | 43 | bool get hasSuperEqual => declaredFragment!.element.allSupertypes 44 | .where((e) => !e.isDartCoreObject) 45 | .map((e) => e.element3) 46 | .any((e) => e.hasEqual); 47 | 48 | bool get hasCustomEquals => declaredFragment!.element.hasEqual; 49 | 50 | bool get hasSuperHashCode => declaredFragment!.element.allSupertypes 51 | .where((e) => !e.isDartCoreObject) 52 | .map((e) => e.element3) 53 | .any((e) => e.hasHashCode); 54 | } 55 | 56 | extension on InterfaceElement2 { 57 | bool get hasEqual => methods2.any(((e) => e.isOperator && e.name3 == '==')); 58 | 59 | bool get hasHashCode => getters2.any((e) => e.name3 == 'hashCode'); 60 | } 61 | 62 | extension ConstructorX on ConstructorDeclaration { 63 | String get fullName { 64 | final classElement = declaredFragment!.element.enclosingElement2; 65 | 66 | var generics = classElement.typeParameters2 67 | .map((e) => '\$${e.name3}') 68 | .join(', '); 69 | if (generics.isNotEmpty) { 70 | generics = '<$generics>'; 71 | } 72 | 73 | final className = classElement.enclosingElement2.name3; 74 | 75 | return name == null ? '$className$generics' : '$className$generics.$name'; 76 | } 77 | 78 | String get escapedName { 79 | final classElement = declaredFragment!.element.enclosingElement2; 80 | 81 | var generics = classElement.typeParameters2 82 | .map((e) => '\$${e.name3}') 83 | .join(', '); 84 | if (generics.isNotEmpty) { 85 | generics = '<$generics>'; 86 | } 87 | 88 | final escapedElementName = classElement.name3!.replaceAll(r'$', r'\$'); 89 | final escapedConstructorName = name?.lexeme.replaceAll(r'$', r'\$'); 90 | 91 | return escapedConstructorName == null 92 | ? '$escapedElementName$generics' 93 | : '$escapedElementName$generics.$escapedConstructorName'; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/freezed_lint/example/lib/missing_private_empty_ctor.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_element 2 | 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | 5 | @freezed 6 | // expect_lint: freezed_missing_private_empty_constructor 7 | class RequiresPrivateCtor with _$RequiresPrivateCtor { 8 | const factory RequiresPrivateCtor() = _RequiresPrivateCtor; 9 | 10 | void method() { 11 | print('hello world'); 12 | } 13 | } 14 | 15 | @freezed 16 | abstract class HasPrivateCtor with _$HasPrivateCtor { 17 | const HasPrivateCtor._(); 18 | 19 | const factory HasPrivateCtor() = _HasPrivateCtor; 20 | 21 | void method() { 22 | print('hello world'); 23 | } 24 | } 25 | 26 | @freezed 27 | // expect_lint: freezed_missing_private_empty_constructor 28 | class HasAccessor with _$HasAccessor { 29 | const factory HasAccessor(int id) = _HasAccessor; 30 | 31 | String get idString => id.toString(); 32 | } 33 | 34 | abstract class IdClass { 35 | int get id; 36 | } 37 | 38 | mixin IdMixin { 39 | int get id; 40 | } 41 | 42 | @freezed 43 | abstract class WithIdMixin with _$WithIdMixin, IdMixin { 44 | const factory WithIdMixin(int id) = _WithIdMixin; 45 | } 46 | 47 | @freezed 48 | // expect_lint: freezed_missing_private_empty_constructor 49 | class ExtendsIdClass extends IdClass with _$ExtendsIdClass { 50 | // ExtendsIdClass._(); 51 | const factory ExtendsIdClass(int id) = _ExtendsIdClass; 52 | 53 | @override 54 | int get id => id; 55 | } 56 | 57 | @freezed 58 | abstract class WithStaticElements with _$WithStaticElements { 59 | static String field = 'field'; 60 | 61 | static String get accessor => 'accessor'; 62 | 63 | static String method() => 'method'; 64 | 65 | const factory WithStaticElements() = _WithStaticElements; 66 | } 67 | 68 | // fake generated code: 69 | 70 | mixin _$RequiresPrivateCtor {} 71 | mixin _$HasPrivateCtor {} 72 | mixin _$HasAccessor { 73 | int get id => throw UnimplementedError(); 74 | } 75 | mixin _$WithIdMixin { 76 | int get id => throw UnimplementedError(); 77 | } 78 | mixin _$ExtendsIdClass {} 79 | mixin _$WithStaticElements {} 80 | 81 | class _RequiresPrivateCtor implements RequiresPrivateCtor { 82 | const _RequiresPrivateCtor(); 83 | @override 84 | void method() {} 85 | } 86 | 87 | class _HasPrivateCtor implements HasPrivateCtor { 88 | const _HasPrivateCtor(); 89 | @override 90 | void method() {} 91 | } 92 | 93 | class _HasAccessor implements HasAccessor { 94 | const _HasAccessor(this.id); 95 | final int id; 96 | @override 97 | String get idString => throw UnimplementedError(); 98 | } 99 | 100 | class _WithIdMixin implements WithIdMixin { 101 | const _WithIdMixin(int id); 102 | @override 103 | int get id => throw UnimplementedError(); 104 | } 105 | 106 | class _ExtendsIdClass implements ExtendsIdClass { 107 | const _ExtendsIdClass(int id); 108 | @override 109 | int get id => throw UnimplementedError(); 110 | } 111 | 112 | class _WithStaticElements implements WithStaticElements { 113 | const _WithStaticElements(); 114 | } 115 | -------------------------------------------------------------------------------- /packages/freezed/test/hashcode_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'integration/equal.dart'; 4 | import 'integration/single_class_constructor.dart' show Large; 5 | 6 | void main() { 7 | test('supports classes with large number of properties', () { 8 | Large().hashCode; 9 | }); 10 | 11 | test('Simple', () { 12 | expect(Simple(42).hashCode, Simple(42).hashCode); 13 | }); 14 | 15 | test('ListEqual', () { 16 | expect(ListEqual([1]).hashCode, ListEqual([1]).hashCode); 17 | }); 18 | 19 | test('MapEqual', () { 20 | expect(MapEqual({1: 1}).hashCode, MapEqual({1: 1}).hashCode); 21 | }); 22 | 23 | test('SetEqual', () { 24 | expect(SetEqual({1}).hashCode, SetEqual({1}).hashCode); 25 | }); 26 | 27 | test('IterableEqual', () { 28 | expect(IterableEqual([1]).hashCode, IterableEqual([1]).hashCode); 29 | }); 30 | 31 | test('ListObjectEqual', () { 32 | expect(ListObjectEqual([1]).hashCode, ListObjectEqual([1.0]).hashCode); 33 | expect( 34 | ListObjectEqual([ 35 | [1], 36 | ]).hashCode, 37 | ListObjectEqual([ 38 | [1], 39 | ]).hashCode, 40 | ); 41 | }); 42 | 43 | test('ListDynamicEqual', () { 44 | expect( 45 | ListDynamicEqual([1]).hashCode, 46 | ListDynamicEqual([1.0]).hashCode, 47 | ); 48 | expect( 49 | ListDynamicEqual([ 50 | [1], 51 | ]).hashCode, 52 | ListDynamicEqual([ 53 | [1], 54 | ]).hashCode, 55 | ); 56 | }); 57 | 58 | test('ObjectEqual', () { 59 | expect(ObjectEqual(1).hashCode, ObjectEqual(1).hashCode); 60 | expect(ObjectEqual([1]).hashCode, ObjectEqual([1]).hashCode); 61 | expect( 62 | ObjectEqual([ 63 | [1], 64 | ]).hashCode, 65 | ObjectEqual([ 66 | [1], 67 | ]).hashCode, 68 | ); 69 | }); 70 | 71 | test('DynamicEqual', () { 72 | expect(DynamicEqual(1).hashCode, DynamicEqual(1).hashCode); 73 | expect(DynamicEqual([1]).hashCode, DynamicEqual([1]).hashCode); 74 | expect( 75 | DynamicEqual([ 76 | [1], 77 | ]).hashCode, 78 | DynamicEqual([ 79 | [1], 80 | ]).hashCode, 81 | ); 82 | }); 83 | 84 | test('Generic', () { 85 | expect(Generic(42).hashCode, Generic(42).hashCode); 86 | expect(Generic([1]).hashCode, Generic([1]).hashCode); 87 | expect( 88 | Generic([ 89 | [1], 90 | ]).hashCode, 91 | Generic([ 92 | [1], 93 | ]).hashCode, 94 | ); 95 | }); 96 | 97 | test('GenericObject', () { 98 | expect(GenericObject(42).hashCode, GenericObject(42).hashCode); 99 | expect(GenericObject([1]).hashCode, GenericObject([1]).hashCode); 100 | expect( 101 | GenericObject([ 102 | [1], 103 | ]).hashCode, 104 | GenericObject([ 105 | [1], 106 | ]).hashCode, 107 | ); 108 | }); 109 | 110 | test('GenericIterable', () { 111 | expect(GenericIterable([1]).hashCode, GenericIterable([1]).hashCode); 112 | }); 113 | } 114 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/common_types.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'common_types.freezed.dart'; 4 | 5 | @freezed 6 | abstract class Union with _$Union { 7 | factory Union.foo({int? arg}) = _UnionFoo; 8 | factory Union.bar({required int arg}) = _UnionBar; 9 | } 10 | 11 | @freezed 12 | abstract class Union2 with _$Union2 { 13 | factory Union2.foo({required int arg}) = _Union2Foo; 14 | factory Union2.bar({double? arg}) = _Union2Bar; 15 | } 16 | 17 | @freezed 18 | abstract class Union3 with _$Union3 { 19 | factory Union3.bar({double? arg}) = _Union3Bar; 20 | factory Union3.foo({required int arg}) = _Union3Foo; 21 | } 22 | 23 | @freezed 24 | abstract class Union4 with _$Union4 { 25 | factory Union4.eventOne({ 26 | required int count, 27 | required String? id, 28 | required String? name, 29 | }) = Union4One; 30 | 31 | factory Union4.eventTwo({ 32 | required int? count, 33 | required String id, 34 | required String name, 35 | }) = Union4Two; 36 | } 37 | 38 | @freezed 39 | abstract class Union5 with _$Union5 { 40 | factory Union5.first(int value) = _Union5First; 41 | factory Union5.second(double? value) = _Union5Second; 42 | factory Union5.third(String value) = _Union5Third; 43 | } 44 | 45 | @freezed 46 | abstract class UnionDeepCopy with _$UnionDeepCopy { 47 | factory UnionDeepCopy.first(CommonSuperSubtype value42) = _UnionWrapperFirst; 48 | factory UnionDeepCopy.second(CommonSuperSubtype? value42) = 49 | _UnionWrapperSecond; 50 | } 51 | 52 | @freezed 53 | abstract class Check with _$Check { 54 | factory Check.first({required dynamic value}) = _CheckFirst; // NOT OK 55 | factory Check.second({required int value}) = _CheckSecond; // OK 56 | factory Check.third({required double value}) = _CheckThird; // OK 57 | factory Check.fourth({required dynamic value}) = _CheckFourth; // OK 58 | } 59 | 60 | @freezed 61 | abstract class CommonSuperSubtype with _$CommonSuperSubtype { 62 | const factory CommonSuperSubtype({ 63 | required int nullabilityDifference, 64 | required int typeDifference, 65 | String? unknown, 66 | }) = CommonSuperSubtype0; 67 | 68 | const factory CommonSuperSubtype.named({ 69 | required int? nullabilityDifference, 70 | required double typeDifference, 71 | }) = CommonSuperSubtype1; 72 | } 73 | 74 | @freezed 75 | abstract class DeepCopySharedProperties with _$DeepCopySharedProperties { 76 | const factory DeepCopySharedProperties(CommonSuperSubtype value) = 77 | _DeepCopySharedProperties; 78 | } 79 | 80 | @unfreezed 81 | abstract class CommonUnfreezed with _$CommonUnfreezed { 82 | factory CommonUnfreezed.one({required int a, required double b}) = 83 | CommonUnfreezedOne; 84 | factory CommonUnfreezed.two({required num a, required double b}) = 85 | CommonUnfreezedTwo; 86 | } 87 | 88 | // Checking that the constructor order does not matter 89 | @unfreezed 90 | abstract class CommonUnfreezed2 with _$CommonUnfreezed2 { 91 | factory CommonUnfreezed2.two({required num a, required double b}) = 92 | CommonUnfreezedTwo2; 93 | factory CommonUnfreezed2.one({required int a, required double b}) = 94 | CommonUnfreezedOne2; 95 | } 96 | -------------------------------------------------------------------------------- /packages/freezed_lint/lib/src/missing_private_empty_ctor.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/error/error.dart' hide LintCode; 2 | import 'package:analyzer/error/listener.dart' show ErrorReporter; 3 | import 'package:custom_lint_builder/custom_lint_builder.dart'; 4 | import 'package:freezed_lint/src/tools/element_extensions.dart'; 5 | import 'package:freezed_lint/src/tools/freezed_annotation_checker.dart'; 6 | 7 | class MissingPrivateEmptyCtor extends DartLintRule { 8 | MissingPrivateEmptyCtor() : super(code: _code); 9 | 10 | static const _code = LintCode( 11 | name: 'freezed_missing_private_empty_constructor', 12 | problemMessage: 'Private empty constructor required', 13 | correctionMessage: 14 | 'Freezed classes containing methods, fields or accessors,' 15 | 'requires a {0}', 16 | errorSeverity: ErrorSeverity.ERROR, 17 | ); 18 | 19 | @override 20 | void run( 21 | CustomLintResolver resolver, 22 | ErrorReporter reporter, 23 | CustomLintContext context, 24 | ) { 25 | context.registry.addClassDeclaration((node) { 26 | final element = node.declaredFragment?.element; 27 | if (element == null) return; 28 | 29 | final annotation = freezedAnnotationChecker.hasAnnotationOfExact(element); 30 | if (!annotation) return; 31 | 32 | final methods = element.methods2.where((method) => !method.isStatic); 33 | final fields = element.fields2.where((field) => !field.isStatic); 34 | 35 | final accessors = [ 36 | ...element.getters2, 37 | ...element.setters2, 38 | ].where((accessor) => !accessor.isStatic); 39 | if (methods.isEmpty && fields.isEmpty && accessors.isEmpty) return; 40 | 41 | final ctors = element.constructors2.where((ctor) => 42 | ctor.isPrivate && ctor.formalParameters.isEmpty && ctor.name3 == '_'); 43 | if (ctors.isNotEmpty) return; 44 | 45 | final constToken = element.constToken(); 46 | final name = '$constToken${element.displayName}._();'; 47 | reporter.atElement2(element, _code, arguments: [name]); 48 | }); 49 | } 50 | 51 | @override 52 | List getFixes() => [_AddPrivateEmptyCtorFix()]; 53 | } 54 | 55 | class _AddPrivateEmptyCtorFix extends DartFix { 56 | @override 57 | void run( 58 | CustomLintResolver resolver, 59 | ChangeReporter reporter, 60 | CustomLintContext context, 61 | AnalysisError analysisError, 62 | List others, 63 | ) { 64 | context.registry.addClassDeclaration((node) { 65 | if (!analysisError.sourceRange.intersects(node.sourceRange)) return; 66 | final element = node.declaredFragment?.element; 67 | if (element == null) return; 68 | final name = element.displayName; 69 | final constToken = element.constToken(); 70 | reporter.createChangeBuilder( 71 | message: 'Add $constToken$name._();', 72 | priority: 2, 73 | )..addDartFileEdit((builder) { 74 | final nextLine = 75 | resolver.lineInfo.getOffsetOfLineAfter(node.leftBracket.offset); 76 | builder.addSimpleInsertion( 77 | nextLine, 78 | ' $constToken$name._();\n', 79 | ); 80 | }); 81 | }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/freezed/lib/src/parse_generator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:analyzer/dart/analysis/results.dart'; 4 | import 'package:analyzer/dart/ast/ast.dart'; 5 | import 'package:analyzer/dart/constant/value.dart'; 6 | import 'package:analyzer/dart/element/element2.dart'; 7 | import 'package:build/build.dart'; 8 | import 'package:collection/collection.dart'; 9 | import 'package:source_gen/source_gen.dart'; 10 | 11 | typedef AnnotationMeta = ({Declaration declaration, DartObject annotation}); 12 | 13 | abstract class ParserGenerator 14 | extends GeneratorForAnnotation { 15 | @override 16 | FutureOr generate( 17 | LibraryReader oldLibrary, 18 | BuildStep buildStep, 19 | ) async { 20 | if (oldLibrary.classes.isEmpty) return ''; 21 | 22 | final units = await Stream.fromFutures( 23 | oldLibrary.element.fragments.map( 24 | (e) => buildStep.resolver.astNodeFor(e, resolve: true), 25 | ), 26 | ).cast().toList(); 27 | 28 | final values = StringBuffer(); 29 | final datas = []; 30 | 31 | for (final unit in units) { 32 | for (var declaration in unit.declarations) { 33 | final declaredFragment = declaration.declaredFragment; 34 | if (declaredFragment == null) continue; 35 | 36 | final annotation = typeChecker.firstAnnotationOf( 37 | declaredFragment.element, 38 | throwOnUnresolved: false, 39 | ); 40 | if (annotation == null) continue; 41 | 42 | datas.add((annotation: annotation, declaration: declaration)); 43 | } 44 | } 45 | 46 | for (final value in generateAll(units, datas)) { 47 | values.writeln(value); 48 | } 49 | 50 | return values.toString(); 51 | } 52 | 53 | Iterable generateAll( 54 | List units, 55 | List annotatedElements, 56 | ) sync* {} 57 | 58 | @override 59 | Stream generateForAnnotatedElement( 60 | Element2 element, 61 | ConstantReader annotation, 62 | BuildStep buildStep, 63 | ) async* { 64 | // implemented for source_gen_test – otherwise unused 65 | 66 | final annotation = typeChecker.firstAnnotationOf( 67 | element, 68 | throwOnUnresolved: false, 69 | ); 70 | if (annotation == null) return; 71 | 72 | final unit = await element.session!.getResolvedUnit( 73 | element.firstFragment.libraryFragment!.source.fullName, 74 | ); 75 | unit as ResolvedUnitResult; 76 | final Object? ast = unit.unit.declarations.firstWhereOrNull( 77 | (declaration) => 78 | declaration.declaredFragment?.element.name3 == element.name3!, 79 | ); 80 | if (ast == null) { 81 | throw InvalidGenerationSourceError('Ast not found', element: element); 82 | } 83 | if (ast is! Declaration) { 84 | throw InvalidGenerationSourceError( 85 | 'Ast is not a Declaration', 86 | element: element, 87 | ); 88 | } 89 | 90 | final datas = [(declaration: ast, annotation: annotation)]; 91 | for (final value in generateAll([unit.unit], datas)) { 92 | yield value.toString(); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/freezed/test/equal_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'integration/equal.dart'; 4 | 5 | void main() { 6 | test('Simple', () { 7 | expect(Simple(42), Simple(42)); 8 | }); 9 | 10 | test('ListEqual', () { 11 | expect(ListEqual([1]), ListEqual([1])); 12 | }); 13 | 14 | test('MapEqual', () { 15 | expect(MapEqual({1: 1}), MapEqual({1: 1})); 16 | }); 17 | 18 | test('SetEqual', () { 19 | expect(SetEqual({1}), SetEqual({1})); 20 | }); 21 | 22 | test('IterableEqual', () { 23 | expect(IterableEqual([1]), IterableEqual([1])); 24 | }); 25 | 26 | test('ListObjectEqual', () { 27 | expect(ListObjectEqual([1]), ListObjectEqual([1.0])); 28 | expect( 29 | ListObjectEqual([ 30 | [1], 31 | ]), 32 | ListObjectEqual([ 33 | [1], 34 | ]), 35 | ); 36 | }); 37 | 38 | test('ListDynamicEqual', () { 39 | expect(ListDynamicEqual([1]), ListDynamicEqual([1.0])); 40 | expect( 41 | ListDynamicEqual([ 42 | [1], 43 | ]), 44 | ListDynamicEqual([ 45 | [1], 46 | ]), 47 | ); 48 | }); 49 | 50 | test('ObjectEqual', () { 51 | expect(ObjectEqual(1), ObjectEqual(1)); 52 | expect(ObjectEqual([1]), ObjectEqual([1])); 53 | expect( 54 | ObjectEqual([ 55 | [1], 56 | ]), 57 | ObjectEqual([ 58 | [1], 59 | ]), 60 | ); 61 | }); 62 | 63 | test('DynamicEqual', () { 64 | expect(DynamicEqual(1), DynamicEqual(1)); 65 | expect(DynamicEqual([1]), DynamicEqual([1])); 66 | expect( 67 | DynamicEqual([ 68 | [1], 69 | ]), 70 | DynamicEqual([ 71 | [1], 72 | ]), 73 | ); 74 | }); 75 | 76 | test('Generic', () { 77 | expect(Generic(42), Generic(42)); 78 | expect(Generic([1]), Generic([1])); 79 | expect( 80 | Generic([ 81 | [1], 82 | ]), 83 | Generic([ 84 | [1], 85 | ]), 86 | ); 87 | }); 88 | 89 | test('GenericObject', () { 90 | expect(GenericObject(42), GenericObject(42)); 91 | expect(GenericObject([1]), GenericObject([1])); 92 | expect( 93 | GenericObject([ 94 | [1], 95 | ]), 96 | GenericObject([ 97 | [1], 98 | ]), 99 | ); 100 | 101 | expect(GenericObject(42), isNot(GenericObject(42))); 102 | expect(GenericObject(42), isNot(GenericObject(42))); 103 | }); 104 | 105 | test('GenericIterable', () { 106 | expect(GenericIterable([1]), GenericIterable([1])); 107 | }); 108 | 109 | test('ObjectWithOtherProperty', () { 110 | expect( 111 | ObjectWithOtherProperty(const [2, 3]), 112 | ObjectWithOtherProperty(const [2, 3]), 113 | ); 114 | expect( 115 | ObjectWithOtherProperty(const [2, 3]), 116 | isNot(ObjectWithOtherProperty(const [2, 4])), 117 | ); 118 | expect(ObjectWithOtherProperty([1, 2]), ObjectWithOtherProperty([1, 2])); 119 | expect( 120 | ObjectWithOtherProperty([1, 2]), 121 | isNot(ObjectWithOtherProperty([1])), 122 | ); 123 | }); 124 | } 125 | -------------------------------------------------------------------------------- /packages/freezed/test/implements_decorator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'integration/implements_decorator.dart'; 4 | 5 | void main() { 6 | group('Simple', () { 7 | test('fromString', () { 8 | final house = House(); 9 | 10 | expect( 11 | const SimpleImplements.street('Sarah'), 12 | isA>().having( 13 | (s) => s.method(house), 14 | 'method(house)', 15 | house, 16 | ), 17 | ); 18 | }); 19 | 20 | test('fromType', () { 21 | var object = const SimpleImplements.city('Morning', 140000); 22 | expect(object, isA()); 23 | 24 | object = const SimpleImplements.country('Morning', 140000); 25 | expect(object, isA()); 26 | expect( 27 | object, 28 | isA().having((s) => s.name, 'name', 'Morning'), 29 | ); 30 | }); 31 | }); 32 | 33 | group('CustomMethod', () { 34 | test('fromString', () { 35 | const object = CustomMethodImplements.street('Sarah'); 36 | expect(object, isA()); 37 | }); 38 | 39 | test('mixedFromStringWithFromType', () { 40 | const object = CustomMethodImplements.street('Sarah'); 41 | expect(object, isA()); 42 | expect(object, isA>()); 43 | }); 44 | 45 | test('mixedWithAndImplements', () { 46 | const object = CustomMethodImplements.city('Morning', 140000); 47 | expect(object, isA()); 48 | expect( 49 | object, 50 | isA().having((s) => s.name, 'name', 'Morning'), 51 | ); 52 | }); 53 | 54 | test( 55 | 'redirected class can be assigned to the interfaces/mixins without cast', 56 | () { 57 | const city = CityCustomMethod('name', 42); 58 | 59 | // ignore: unused_local_variable 60 | House house = city; 61 | Named named = city; 62 | expect(named.name, 'name'); 63 | GeographicArea area = city; 64 | expect(area.name, 'name'); 65 | }, 66 | ); 67 | }); 68 | 69 | group('Generic type', () { 70 | test('static @With and @Implements', () { 71 | const object = GenericCity('Morning', 140000); 72 | // testing that GenericStreet's abstract class implements the types too 73 | // ignore: unused_local_variable 74 | House house = object; 75 | // ignore: unused_local_variable 76 | GeographicArea area = object; 77 | 78 | expect(object, isA()); 79 | expect( 80 | object, 81 | isA().having((s) => s.name, 'name', 'Morning'), 82 | ); 83 | }); 84 | 85 | test('generic @With and @Implements using fromString', () { 86 | const object = GenericStreet('Oak', 42); 87 | // testing that GenericStreet's abstract class implements the types too 88 | // ignore: unused_local_variable 89 | AdministrativeArea area = object; 90 | // ignore: unused_local_variable 91 | Generic generic = object; 92 | 93 | expect(object, isA>()); 94 | expect(object, isA>().having((s) => s.value, 'value', 42)); 95 | }); 96 | }); 97 | } 98 | -------------------------------------------------------------------------------- /packages/freezed/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'main.freezed.dart'; 5 | part 'main.g.dart'; 6 | 7 | @freezed 8 | abstract class MyClass with _$MyClass { 9 | factory MyClass({String? a, int? b}) = _MyClass; 10 | } 11 | 12 | @freezed 13 | sealed class Union with _$Union { 14 | const factory Union(int value) = Data; 15 | const factory Union.loading() = Loading; 16 | const factory Union.error([String? message]) = ErrorDetails; 17 | const factory Union.complex(int a, String b) = Complex; 18 | 19 | factory Union.fromJson(Map json) => _$UnionFromJson(json); 20 | } 21 | 22 | @freezed 23 | abstract class SharedProperty with _$SharedProperty { 24 | factory SharedProperty.person({String? name, int? age}) = SharedProperty0; 25 | 26 | factory SharedProperty.city({String? name, int? population}) = 27 | SharedProperty1; 28 | } 29 | 30 | void main() { 31 | final myClassexample = MyClass(a: '42', b: 42); 32 | 33 | // clone 34 | print(myClassexample.copyWith(a: null)); // MyClass(a: null, b: 42) 35 | print(myClassexample.copyWith()); // MyClass(a: '42', b: 42) 36 | 37 | // ------------------ 38 | 39 | // == override 40 | print(MyClass(a: '42', b: 42) == MyClass(a: '42', b: 42)); // true 41 | print(MyClass(a: '42', b: 42) == MyClass()); // false 42 | 43 | // ------------------ 44 | 45 | // destructuring pattern-matching 46 | const unionExample = Union(42); 47 | print(switch (unionExample) { 48 | Data value => '$value', 49 | Loading _ => 'loading', 50 | ErrorDetails(:final message) => 'Error: $message', 51 | Complex(:final a, :final b) => 'complex $a $b', 52 | }); // 42 53 | 54 | print(switch (unionExample) { 55 | Loading _ => 'loading', 56 | // voluntarily didn't handle error/complex cases 57 | _ => 42, 58 | }); // 42 59 | 60 | // ------------------ 61 | 62 | // non-destructuring pattern-matching 63 | // works the same as `when`, but the callback is slightly different 64 | print(switch (unionExample) { 65 | Data value => '$value', 66 | Loading _ => 'loading', 67 | ErrorDetails(:final message) => 'Error: $message', 68 | Complex(:final a, :final b) => 'complex $a $b', 69 | }); // 42 70 | 71 | print(switch (unionExample) { 72 | ErrorDetails(:final message) => message, 73 | // voluntarily didn't handle error/complex cases 74 | _ => 'fallthrough', 75 | }); // fallthrough 76 | 77 | // ------------------ 78 | 79 | // nice toString 80 | print(const Union(42)); // Union(value: 42) 81 | print(const Union.loading()); // Union.loading() 82 | print( 83 | const Union.error('Failed to fetch'), 84 | ); // Union.error(message: Failed to fetch) 85 | 86 | // ------------------ 87 | 88 | // shared properties between union possibilities 89 | var example = SharedProperty.person(name: 'Remi', age: 24); 90 | // OK, `name` is shared between both .person and .city constructor 91 | print(example.name); // Remi 92 | example = SharedProperty.city(name: 'London', population: 8900000); 93 | print(example.name); // London 94 | 95 | // COMPILE ERROR 96 | // print(example.age); 97 | // print(example.population); 98 | } 99 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/concrete.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'concrete.freezed.dart'; 4 | 5 | @freezed 6 | abstract class EmptyExtends extends Empty with _$EmptyExtends { 7 | EmptyExtends._(); 8 | factory EmptyExtends(int value) = _EmptyExtends; 9 | } 10 | 11 | class Empty { 12 | const Empty(); 13 | } 14 | 15 | @freezed 16 | abstract class Concrete extends A with _$Concrete { 17 | Concrete._(); 18 | factory Concrete(int value) = _Concrete; 19 | } 20 | 21 | abstract class A { 22 | int get value; 23 | int method() { 24 | return 42; 25 | } 26 | } 27 | 28 | @freezed 29 | abstract class EmptyConcrete extends Empty with _$EmptyConcrete { 30 | EmptyConcrete._(); 31 | factory EmptyConcrete() = _EmptyConcrete; 32 | } 33 | 34 | @freezed 35 | abstract class ConstConcrete extends Empty with _$ConstConcrete { 36 | const ConstConcrete._(); 37 | const factory ConstConcrete() = _ConstConcrete; 38 | } 39 | 40 | @freezed 41 | abstract class MixedIn with _$MixedIn, Mixin { 42 | MixedIn._(); 43 | factory MixedIn() = _MixedIn; 44 | } 45 | 46 | mixin Mixin { 47 | int method() => 42; 48 | } 49 | 50 | @freezed 51 | abstract class ConcreteGetter with _$ConcreteGetter { 52 | const ConcreteGetter._(); 53 | const factory ConcreteGetter() = _ConcreteGetter; 54 | 55 | int get getter => 42; 56 | } 57 | 58 | @freezed 59 | abstract class CustomToString with _$CustomToString { 60 | CustomToString._(); 61 | factory CustomToString() = _CustomToString; 62 | 63 | @override 64 | String toString() { 65 | return '42'; 66 | } 67 | } 68 | 69 | @freezed 70 | abstract class MixedInToString with _$MixedInToString, ToStringMixin { 71 | MixedInToString._(); 72 | factory MixedInToString() = _MixedInToString; 73 | } 74 | 75 | mixin ToStringMixin { 76 | @override 77 | String toString() { 78 | return '42'; 79 | } 80 | } 81 | 82 | @freezed 83 | abstract class BaseToString extends AbstractToString with _$BaseToString { 84 | BaseToString._(); 85 | factory BaseToString() = _BaseToString; 86 | } 87 | 88 | abstract class AbstractToString { 89 | @override 90 | String toString() { 91 | return '42'; 92 | } 93 | } 94 | 95 | @freezed 96 | abstract class Const with _$Const { 97 | const Const._(); 98 | const factory Const(int a) = _Const; 99 | } 100 | 101 | @freezed 102 | abstract class Dollar with _$Dollar { 103 | const factory Dollar({required String $test}) = _DollarStartWithDollar; 104 | } 105 | 106 | /// Middle with $ 107 | @freezed 108 | abstract class DollarMiddle with _$DollarMiddle { 109 | const factory DollarMiddle({required String te$st}) = 110 | _DollarMiddleMiddleWithDollar; 111 | } 112 | 113 | /// End with $ 114 | @freezed 115 | abstract class DollarEnd with _$DollarEnd { 116 | const factory DollarEnd({required String test$}) = _DollarEndEndWithDollar; 117 | } 118 | 119 | /// class with $ 120 | @freezed 121 | abstract class $DollarClass with _$$DollarClass { 122 | const factory $DollarClass() = _DollarClassWithDollar; 123 | } 124 | 125 | /// class contructor with $ 126 | @freezed 127 | abstract class DollarFactory with _$DollarFactory { 128 | const factory DollarFactory.$() = _DollarFactoryWithDollar; 129 | } 130 | -------------------------------------------------------------------------------- /packages/freezed/lib/src/templates/from_json_template.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:freezed/src/freezed_generator.dart'; 3 | import 'package:source_gen/source_gen.dart'; 4 | 5 | import '../models.dart'; 6 | import 'prototypes.dart'; 7 | 8 | class FromJson { 9 | FromJson(this.clazz); 10 | 11 | final Class clazz; 12 | 13 | @override 14 | String toString() { 15 | // For manual classes, we don't handle from/toJson. This is because parts 16 | // cannot add annotations on user's behalf. 17 | if (clazz.constructors.isEmpty) return ''; 18 | 19 | final conflictCtor = clazz.constructors 20 | .where((c) => c.redirectedName.public == clazz.name.public) 21 | .firstOrNull; 22 | 23 | if (conflictCtor != null) { 24 | if (clazz.constructors.length == 1) return ''; 25 | 26 | throw InvalidGenerationSourceError(''' 27 | Could not generate _\$${clazz.name}FromJson because both ${clazz.name} and ${conflictCtor.redirectedName} would want to generate it. 28 | Rename one or the other, such that they don't conflict. 29 | '''); 30 | } 31 | 32 | String content; 33 | 34 | if (clazz.constructors.length == 1) { 35 | content = 36 | ''' 37 | return ${clazz.constructors.first.redirectedName}${clazz.genericsParameterTemplate}.fromJson( 38 | json${fromJsonArguments(clazz.genericsParameterTemplate, clazz.options.genericArgumentFactories)} 39 | );'''; 40 | } else { 41 | final cases = clazz.constructors 42 | .where((element) => !element.isFallback) 43 | .map((constructor) { 44 | final caseName = constructor.unionValue; 45 | final concreteName = constructor.redirectedName; 46 | 47 | return ''' 48 | case '$caseName': 49 | return $concreteName${clazz.genericsParameterTemplate}.fromJson( 50 | json${fromJsonArguments(clazz.genericsParameterTemplate, clazz.options.genericArgumentFactories)} 51 | ); 52 | '''; 53 | }) 54 | .join(); 55 | 56 | // TODO(rrousselGit): update logic once https://github.com/rrousselGit/freezed/pull/370 lands 57 | var defaultCase = 58 | ''' 59 | throw CheckedFromJsonException( 60 | json, 61 | \'${clazz.options.annotation.unionKey}\', 62 | \'${clazz.name}\', 63 | \'Invalid union type "\${json[\'${clazz.options.annotation.unionKey}\']}"!\' 64 | );'''; 65 | final fallbackConstructor = clazz.constructors.singleWhereOrNull( 66 | (element) => element.isFallback, 67 | ); 68 | if (fallbackConstructor != null) { 69 | defaultCase = 70 | ''' 71 | return ${fallbackConstructor.redirectedName}${clazz.genericsParameterTemplate}.fromJson( 72 | json${fromJsonArguments(clazz.genericsParameterTemplate, clazz.options.genericArgumentFactories)} 73 | );'''; 74 | } 75 | 76 | content = 77 | ''' 78 | switch (json['${clazz.options.annotation.unionKey}']) { 79 | $cases 80 | default: 81 | $defaultCase 82 | } 83 | '''; 84 | } 85 | 86 | return ''' 87 | ${clazz.name}${clazz.genericsParameterTemplate} _\$${clazz.name.public}FromJson${clazz.genericsDefinitionTemplate}( 88 | Map json${fromJsonParameters(clazz.genericsParameterTemplate, clazz.options.genericArgumentFactories)} 89 | ) { 90 | $content 91 | } 92 | '''; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /packages/freezed/lib/src/tools/type.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element2.dart'; 2 | import 'package:analyzer/dart/element/nullability_suffix.dart'; 3 | import 'package:analyzer/dart/element/type.dart'; 4 | import 'package:collection/collection.dart'; 5 | 6 | import 'imports.dart'; 7 | 8 | extension DartTypeX on DartType { 9 | bool get isDynamic2 { 10 | return this is DynamicType || this is InvalidType; 11 | } 12 | 13 | bool get isNullable { 14 | if (isDynamic2 || 15 | isDartCoreNull || 16 | nullabilitySuffix == NullabilitySuffix.question) { 17 | return true; 18 | } 19 | 20 | final that = this; 21 | if (that is TypeParameterType) return that.bound.isNullable; 22 | 23 | return false; 24 | } 25 | } 26 | 27 | /// Returns the [Element2] for a given [DartType] 28 | /// 29 | /// this is usually type.element, except if it is a typedef then it is 30 | /// type.alias.element 31 | Element2? _getElementForType(DartType type) { 32 | if (type is InterfaceType) { 33 | return type.element3; 34 | } 35 | if (type is FunctionType) { 36 | return type.alias?.element2; 37 | } 38 | return null; 39 | } 40 | 41 | /// Renders a type based on its string + potential import alias 42 | String resolveFullTypeStringFrom(LibraryElement2 originLibrary, DartType type) { 43 | final owner = originLibrary.firstFragment.prefixes.firstWhereOrNull((e) { 44 | return e.imports.any((l) { 45 | return l.importedLibrary2!.anyTransitiveExport((library) { 46 | return library.id == _getElementForType(type)?.library2?.id; 47 | }); 48 | }); 49 | }); 50 | 51 | String? displayType = type.getDisplayString(); 52 | 53 | // The parameter is a typedef in the form of 54 | // SomeTypedef typedef 55 | // 56 | // In this case the analyzer would expand that typedef using getDisplayString 57 | // For example for: 58 | // 59 | // typedef SomeTypedef = Function(String); 60 | // 61 | // it would generate: 62 | // 'dynamic Function(String)' 63 | // 64 | // Instead of 'SomeTypedef' 65 | if (type is FunctionType && type.alias?.element2 != null) { 66 | displayType = type.alias!.element2.name3!; 67 | if (type.alias!.typeArguments.isNotEmpty) { 68 | displayType += '<${type.alias!.typeArguments.join(', ')}>'; 69 | } 70 | if (type.nullabilitySuffix == NullabilitySuffix.question) { 71 | displayType += '?'; 72 | } 73 | } 74 | 75 | // The parameter is a Interface with a Type Argument that is not yet generated 76 | // In this case analyzer would set its type to InvalidType 77 | // 78 | // For example for: 79 | // List values, 80 | // 81 | // it would generate: List 82 | // instead of List 83 | // 84 | // This a regression in analyzer 5.13.0 85 | if (type is InterfaceType && 86 | type.typeArguments.any((e) => e is InvalidType)) { 87 | final dynamicType = type.element3.library2.typeProvider.dynamicType; 88 | var modified = type; 89 | modified.typeArguments..replaceWhere((t) => t is InvalidType, dynamicType); 90 | displayType = modified.getDisplayString(); 91 | } 92 | 93 | if (owner != null) { 94 | return '${owner.name3}.$displayType'; 95 | } 96 | 97 | return displayType; 98 | } 99 | 100 | extension ReplaceWhereExtension on List { 101 | void replaceWhere(bool Function(T element) test, T replacement) { 102 | for (var index = 0; index < length; index++) { 103 | if (test(this[index])) { 104 | this[index] = replacement; 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /packages/freezed/test/decorator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/results.dart'; 2 | import 'package:build_test/build_test.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | test('has no issue', () async { 7 | final main = await resolveSources( 8 | {'freezed|test/integration/decorator.dart': useAssetReader}, 9 | (r) => r.libraries.firstWhere( 10 | (element) => 11 | element.firstFragment.source.toString().contains('decorator'), 12 | ), 13 | ); 14 | 15 | var errorResult = 16 | await main.session.getErrors( 17 | '/freezed/test/integration/decorator.freezed.dart', 18 | ) 19 | as ErrorsResult; 20 | expect(errorResult.errors, isEmpty); 21 | errorResult = 22 | await main.session.getErrors('/freezed/test/integration/decorator.dart') 23 | as ErrorsResult; 24 | }); 25 | 26 | test( 27 | 'internal raw collection is not decorated when using immutable collections', 28 | () async { 29 | final main = await resolveSources( 30 | { 31 | 'freezed|test/integration/main.dart': r''' 32 | import 'decorator.dart'; 33 | ''', 34 | }, 35 | (r) => r.libraries.firstWhere( 36 | (element) => element.library2.name3 == 'decorator', 37 | ), 38 | readAllSourcesFromFilesystem: true, 39 | ); 40 | 41 | final concrete = main.classes.firstWhere( 42 | (e) => e.name3 == r'ListDecorator0', 43 | ); 44 | 45 | expect( 46 | concrete.fields2 47 | .firstWhere((element) => element.name3 == '_a') 48 | .metadata2 49 | .annotations, 50 | isEmpty, 51 | ); 52 | 53 | final unmodifiableGetter = concrete.fields2 54 | .firstWhere((element) => element.name3 == 'a') 55 | .getter2!; 56 | 57 | expect(unmodifiableGetter.metadata2.annotations.length, 2); 58 | expect( 59 | unmodifiableGetter.metadata2.annotations.last.toSource(), 60 | '@Foo()', 61 | ); 62 | }, 63 | ); 64 | 65 | test('warns if try to use deprecated property', () async { 66 | final main = await resolveSources( 67 | { 68 | 'freezed|test/integration/main.dart': r''' 69 | import 'decorator.dart'; 70 | 71 | void main() { 72 | final value = Decorator(a: 'a'); 73 | print(value.a); 74 | 75 | value.copyWith( 76 | a: 'a', 77 | ); 78 | 79 | final value2 = Decorator0(a: 'a'); 80 | 81 | print(value2.a); 82 | 83 | value2.copyWith( 84 | a: 'a', 85 | ); 86 | } 87 | ''', 88 | }, 89 | (r) => r.libraries.firstWhere( 90 | (element) => element.firstFragment.toString().contains('decorator'), 91 | ), 92 | readAllSourcesFromFilesystem: true, 93 | ); 94 | 95 | var errorResult = 96 | await main.session.getErrors('/freezed/test/integration/main.dart') 97 | as ErrorsResult; 98 | expect( 99 | errorResult.errors.map((e) => e.errorCode.name), 100 | anyOf([ 101 | [ 102 | 'UNUSED_RESULT', 103 | 'UNUSED_RESULT', 104 | 'DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE', 105 | 'DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE', 106 | 'DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE', 107 | 'DEPRECATED_MEMBER_USE_FROM_SAME_PACKAGE', 108 | ], 109 | [ 110 | 'UNUSED_RESULT', 111 | 'UNUSED_RESULT', 112 | 'DEPRECATED_MEMBER_USE', 113 | 'DEPRECATED_MEMBER_USE', 114 | 'DEPRECATED_MEMBER_USE', 115 | 'DEPRECATED_MEMBER_USE', 116 | ], 117 | ]), 118 | ); 119 | }); 120 | } 121 | -------------------------------------------------------------------------------- /packages/freezed/test/optional_maybe_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/analysis/results.dart'; 2 | import 'package:build_test/build_test.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | import 'common.dart'; 6 | import 'integration/optional_maybe.dart'; 7 | 8 | void main() { 9 | test('has no issue', () async { 10 | final main = await resolveSources( 11 | {'freezed|test/integration/optional_maybe.dart': useAssetReader}, 12 | (r) => r.libraries.firstWhere( 13 | (element) => 14 | element.firstFragment.source.toString().contains('optional_maybe'), 15 | ), 16 | ); 17 | 18 | final errorResult = 19 | await main.session.getErrors( 20 | '/freezed/test/integration/optional_maybe.freezed.dart', 21 | ) 22 | as ErrorsResult; 23 | 24 | expect(errorResult.errors, isEmpty); 25 | }); 26 | 27 | test('does not generates maybeMap', () async { 28 | await expectLater( 29 | compile(r''' 30 | import 'optional_maybe.dart'; 31 | 32 | void main() { 33 | final value = OptionalMaybeMap.first(); 34 | 35 | value.maybeMap(orElse: () => null); 36 | value.map( 37 | first: (_) {}, 38 | second: (_) {}, 39 | ); 40 | value.mapOrNull(); 41 | } 42 | '''), 43 | throwsCompileError, 44 | ); 45 | 46 | const OptionalMaybeMap.first() 47 | ..whenOrNull() 48 | ..maybeWhen(orElse: () {}) 49 | ..when(first: () {}, second: () {}); 50 | }); 51 | 52 | test('does not generates maybeWhen', () async { 53 | await expectLater( 54 | compile(r''' 55 | import 'optional_maybe.dart'; 56 | 57 | void main() { 58 | final value = OptionalMaybeWhen.first(); 59 | 60 | value.maybeWhen(orElse: () => null); 61 | value.when( 62 | first: () {}, 63 | second: () {}, 64 | ); 65 | value.whenOrNull(); 66 | } 67 | '''), 68 | throwsCompileError, 69 | ); 70 | 71 | const OptionalMaybeWhen.first() 72 | ..mapOrNull() 73 | ..maybeMap(orElse: () {}) 74 | ..map(first: (_) {}, second: (_) {}); 75 | }); 76 | 77 | test('can disable copyWith', () async { 78 | await expectLater( 79 | compile(r''' 80 | import 'optional_maybe.dart'; 81 | 82 | void main() { 83 | OptionalCopyWith().copyWith; 84 | } 85 | '''), 86 | throwsCompileError, 87 | ); 88 | }); 89 | 90 | test('can disable toString', () { 91 | expect( 92 | const OptionalToString().toString(), 93 | r"Instance of '_OptionalToString'", 94 | ); 95 | }); 96 | 97 | test('can disable ==/hash', () { 98 | expect(OptionalEqual(), isNot(OptionalEqual())); 99 | expect(OptionalEqual().hashCode, isNot(OptionalEqual().hashCode)); 100 | }); 101 | 102 | test('can force the generation of when/map', () { 103 | ForceUnionMethod2.two() 104 | ..map(two: (_) {}) 105 | ..mapOrNull() 106 | ..maybeMap(orElse: () {}) 107 | ..when(two: () {}) 108 | ..whenOrNull() 109 | ..maybeWhen(orElse: () {}); 110 | 111 | ForceUnionMethod() 112 | ..map((value) => null) 113 | ..mapOrNull((value) => null) 114 | ..maybeMap((value) => null, orElse: () {}) 115 | ..when(() => null) 116 | ..whenOrNull(() => null) 117 | ..maybeWhen(() => null, orElse: () {}); 118 | }); 119 | 120 | test('can disable toJson', () async { 121 | OptionalToJson(); 122 | OptionalToJson.fromJson({}); 123 | 124 | await expectLater( 125 | compile(r''' 126 | import 'optional_maybe.dart'; 127 | 128 | void main() { 129 | OptionalToJson().toJson; 130 | } 131 | '''), 132 | throwsCompileError, 133 | ); 134 | }); 135 | 136 | test('can force toJson', () async { 137 | expect(ForceToJson(42).toJson(), {'a': 42}); 138 | }); 139 | } 140 | -------------------------------------------------------------------------------- /packages/freezed/test/nullable_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'common.dart'; 4 | import 'integration/nullable.dart'; 5 | 6 | final throwsAssertionError = throwsA(isA()); 7 | 8 | void main() { 9 | test('RequiredPositional', () async { 10 | await expectLater( 11 | compile(r''' 12 | import 'nullable.dart'; 13 | 14 | void main() { 15 | RequiredPositional(42).copyWith(a: 42); 16 | } 17 | '''), 18 | completes, 19 | ); 20 | 21 | await expectLater( 22 | compile(r''' 23 | import 'nullable.dart'; 24 | 25 | void main() { 26 | RequiredPositional(null); 27 | } 28 | '''), 29 | throwsCompileError, 30 | ); 31 | 32 | await expectLater( 33 | compile(r''' 34 | import 'nullable.dart'; 35 | 36 | void main() { 37 | RequiredPositional(42).copyWith(a: null); 38 | } 39 | '''), 40 | throwsCompileError, 41 | ); 42 | }); 43 | 44 | test('DefaultPositional', () async { 45 | await expectLater( 46 | compile(r''' 47 | import 'nullable.dart'; 48 | 49 | void main() { 50 | DefaultPositional(42).copyWith(a: 42); 51 | DefaultPositional().copyWith(a: 42); 52 | } 53 | '''), 54 | completes, 55 | ); 56 | 57 | await expectLater( 58 | compile(r''' 59 | import 'nullable.dart'; 60 | 61 | void main() { 62 | DefaultPositional(null); 63 | } 64 | '''), 65 | throwsCompileError, 66 | ); 67 | 68 | await expectLater( 69 | compile(r''' 70 | import 'nullable.dart'; 71 | 72 | void main() { 73 | DefaultPositional().copyWith(a: null); 74 | } 75 | '''), 76 | throwsCompileError, 77 | ); 78 | }); 79 | 80 | test('DefaultNamed', () async { 81 | await expectLater( 82 | compile(r''' 83 | import 'nullable.dart'; 84 | 85 | void main() { 86 | DefaultNamed(a: 42).copyWith(a: 42); 87 | DefaultNamed().copyWith(a: 42); 88 | } 89 | '''), 90 | completes, 91 | ); 92 | 93 | await expectLater( 94 | compile(r''' 95 | import 'nullable.dart'; 96 | 97 | void main() { 98 | DefaultNamed(a: null); 99 | } 100 | '''), 101 | throwsCompileError, 102 | ); 103 | 104 | await expectLater( 105 | compile(r''' 106 | import 'nullable.dart'; 107 | 108 | void main() { 109 | DefaultNamed().copyWith(a: null); 110 | } 111 | '''), 112 | throwsCompileError, 113 | ); 114 | }); 115 | 116 | test('NullableRequiredPositional', () { 117 | // ignore: unused_result 118 | NullableRequiredPositional(42).copyWith(a: 42); 119 | // ignore: unused_result 120 | NullableRequiredPositional(null).copyWith(a: null); 121 | }); 122 | 123 | test('Positional', () { 124 | // ignore: unused_result 125 | Positional(42).copyWith(a: 42); 126 | // ignore: unused_result 127 | Positional(null).copyWith(a: null); 128 | }); 129 | 130 | test('Named', () { 131 | // ignore: unused_result 132 | Named(a: 42).copyWith(a: 42); 133 | // ignore: unused_result 134 | Named(a: null).copyWith(a: null); 135 | }); 136 | 137 | test('RequiredNamed', () async { 138 | await expectLater( 139 | compile(r''' 140 | import 'nullable.dart'; 141 | 142 | void main() { 143 | RequiredNamed(a: 42).copyWith(a: 42); 144 | } 145 | '''), 146 | completes, 147 | ); 148 | 149 | await expectLater( 150 | compile(r''' 151 | import 'nullable.dart'; 152 | 153 | void main() { 154 | RequiredNamed(a: null); 155 | } 156 | 157 | '''), 158 | throwsCompileError, 159 | ); 160 | 161 | await expectLater( 162 | compile(r''' 163 | import 'nullable.dart'; 164 | 165 | void main() { 166 | RequiredNamed(); 167 | } 168 | 169 | '''), 170 | throwsCompileError, 171 | ); 172 | 173 | await expectLater( 174 | compile(r''' 175 | import 'nullable.dart'; 176 | 177 | void main() { 178 | RequiredNamed(a: 42).copyWith(a: null); 179 | } 180 | '''), 181 | throwsCompileError, 182 | ); 183 | }); 184 | 185 | test('NullableRequiredNamed', () { 186 | // ignore: unused_result 187 | NullableRequiredNamed(a: 42).copyWith(a: 42); 188 | // ignore: unused_result 189 | NullableRequiredNamed(a: null).copyWith(a: null); 190 | }); 191 | } 192 | -------------------------------------------------------------------------------- /packages/freezed/test/generic_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: prefer_const_constructors, omit_local_variable_types, deprecated_member_use_from_same_package 2 | import 'package:analyzer/dart/analysis/results.dart'; 3 | import 'package:build_test/build_test.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | import 'common.dart'; 7 | import 'integration/generic.dart'; 8 | 9 | Future main() async { 10 | test('regression 399', () async { 11 | await expectLater( 12 | compile(r''' 13 | import 'regression399/a.dart'; 14 | import 'regression399/b.dart'; 15 | 16 | void main() { 17 | GenericRegression399A( 18 | b: GenericRegression399BImpl(), 19 | ); 20 | } 21 | '''), 22 | completes, 23 | ); 24 | 25 | await expectLater( 26 | compile(r''' 27 | import 'regression399/a.dart'; 28 | import 'regression399/b.dart'; 29 | 30 | void main() { 31 | GenericRegression399A( 32 | b: 42, 33 | ); 34 | } 35 | '''), 36 | throwsCompileError, 37 | ); 38 | }); 39 | 40 | test('has no issue', () async { 41 | final main = await resolveSources( 42 | {'freezed|test/integration/generic.dart': useAssetReader}, 43 | (r) => r.libraries.firstWhere( 44 | (element) => 45 | element.firstFragment.source.toString().contains('generic'), 46 | ), 47 | ); 48 | 49 | final errorResult = 50 | await main.session.getErrors( 51 | '/freezed/test/integration/generic.freezed.dart', 52 | ) 53 | as ErrorsResult; 54 | 55 | expect(errorResult.errors, isEmpty); 56 | }); 57 | 58 | test('is generic', () { 59 | Generic> value = Generic(Model(42)); 60 | Model model = value.model; 61 | 62 | expect(model.value, 42); 63 | }); 64 | 65 | test('map is generic too', () { 66 | var result = MultipleConstructors(false) 67 | .map>( 68 | (Default value) => value, 69 | first: (First value) => value, 70 | second: (Second value) => value, 71 | ); 72 | 73 | expect(result, MultipleConstructors(false)); 74 | 75 | MultipleConstructors( 76 | false, 77 | ).maybeMap>( 78 | (Default value) => value, 79 | first: (First value) => value, 80 | second: (Second value) => value, 81 | orElse: () => throw Error(), 82 | ); 83 | 84 | expect(result, MultipleConstructors(false)); 85 | }); 86 | 87 | test('is generic2', () { 88 | MultiGeneric> value = MultiGeneric(Model(42)); 89 | Model model = value.model; 90 | 91 | expect(model.value, 42); 92 | }); 93 | 94 | test('toString', () { 95 | expect( 96 | '${MultipleConstructors.first(42)}', 97 | 'MultipleConstructors.first(a: 42)', 98 | ); 99 | 100 | expect( 101 | '${MultipleConstructors.second('42')}', 102 | 'MultipleConstructors.second(b: 42)', 103 | ); 104 | 105 | expect( 106 | '${MultipleConstructors(false)}', 107 | 'MultipleConstructors(flag: false)', 108 | ); 109 | }); 110 | 111 | test('copy returns generic ', () { 112 | Generic> generic = Generic(Model(42)); 113 | generic = generic.copyWith(model: Model(24)); 114 | expect(generic.model.value, 24); 115 | 116 | MultiGeneric> generic2 = MultiGeneric(Model(42)); 117 | generic2 = generic2.copyWith(model: Model(24)); 118 | expect(generic2.model.value, 24); 119 | }); 120 | 121 | test('supports copyWith(value: null) vs copyWith() on nullable generics', () { 122 | expect( 123 | GenericOrNull(42).copyWith(value: null), 124 | GenericOrNull(null), 125 | ); 126 | expect(GenericOrNull(42).copyWith(), GenericOrNull(42)); 127 | 128 | expect( 129 | NullableGeneric(42).copyWith(value: null), 130 | NullableGeneric(null), 131 | ); 132 | expect(NullableGeneric(42).copyWith(), NullableGeneric(42)); 133 | }); 134 | 135 | test('did pass generic constraints', () async { 136 | await expectLater( 137 | compile(r''' 138 | import 'generic.dart'; 139 | 140 | void main() { 141 | Generic>(Model(42)); 142 | } 143 | '''), 144 | completes, 145 | ); 146 | 147 | await expectLater( 148 | compile(r''' 149 | import 'generic.dart'; 150 | 151 | void main() { 152 | Generic(42); 153 | } 154 | '''), 155 | throwsCompileError, 156 | ); 157 | }); 158 | } 159 | -------------------------------------------------------------------------------- /packages/freezed/test/source_gen_src.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_unused_constructor_parameters, unused_element 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | // ignore: import_of_legacy_library_into_null_safe 4 | import 'package:source_gen_test/annotations.dart'; 5 | 6 | @ShouldThrow(''' 7 | The class RequiredCloneable requested a copyWith implementation, yet the parameter `notCloneable` is not cloneable. 8 | 9 | To fix, either: 10 | - Disable copyWith using @Freezed(copyWith: false) 11 | - Make `notCloneable` optional 12 | - Make sure `this.notCloneable` is accessible from the copyWith method 13 | ''') 14 | @freezed 15 | class RequiredCloneable with _$RequiredCloneable { 16 | RequiredCloneable({required int notCloneable}); 17 | } 18 | 19 | mixin _$RequiredCloneable {} 20 | 21 | @ShouldThrow(r'Classes using @freezed must use `with _$NoMixin`.') 22 | @freezed 23 | class NoMixin {} 24 | 25 | class Base {} 26 | 27 | @ShouldThrow( 28 | 'Classes using extends/with must define a MyClass._() constructor.', 29 | ) 30 | @freezed 31 | abstract class ExtendsWithoutDefault extends Base with _$ExtendsWithoutDefault { 32 | factory ExtendsWithoutDefault() = _ExtendsWithoutDefault; 33 | } 34 | 35 | class _ExtendsWithoutDefault implements ExtendsWithoutDefault {} 36 | 37 | mixin _$ExtendsWithoutDefault {} 38 | 39 | @ShouldThrow('@freezed can only be applied on classes.') 40 | @freezed 41 | enum Foo { a } 42 | 43 | @ShouldThrow('Getters require a MyClass._() constructor') 44 | @freezed 45 | abstract class Properties { 46 | factory Properties() = _Properties; 47 | 48 | int get regularProperty => 42; 49 | } 50 | 51 | class _Properties implements Properties { 52 | @override 53 | final int regularProperty = 0; 54 | } 55 | 56 | @ShouldThrow( 57 | 'The parameter `a` of `RequiredNamed.foo` is non-nullable but is neither required nor marked with @Default', 58 | ) 59 | @freezed 60 | abstract class RequiredNamed { 61 | factory RequiredNamed.foo({int a}) = _RequiredNamed; 62 | } 63 | 64 | class _RequiredNamed implements RequiredNamed { 65 | _RequiredNamed({int? a}); 66 | } 67 | 68 | @ShouldThrow( 69 | 'The parameter `a` of `RequiredPositional` is non-nullable but is neither required nor marked with @Default', 70 | ) 71 | @freezed 72 | abstract class RequiredPositional { 73 | factory RequiredPositional([int a]) = _RequiredPositional; 74 | } 75 | 76 | class _RequiredPositional implements RequiredPositional { 77 | _RequiredPositional([int? a]); 78 | } 79 | 80 | @ShouldThrow( 81 | 'The parameter `a` of `RequiredNamedDefault` is non-nullable but is neither required nor marked with @Default', 82 | ) 83 | @freezed 84 | abstract class RequiredNamedDefault { 85 | factory RequiredNamedDefault({int a}) = _RequiredNamedDefault; 86 | } 87 | 88 | class _RequiredNamedDefault implements RequiredNamedDefault { 89 | _RequiredNamedDefault({int? a}); 90 | } 91 | 92 | @ShouldThrow('Getters require a MyClass._() constructor') 93 | @freezed 94 | abstract class Get { 95 | factory Get() = _Get; 96 | 97 | int get regularProperty => 42; 98 | } 99 | 100 | class _Get implements Get { 101 | @override 102 | final int regularProperty = 0; 103 | } 104 | 105 | @ShouldThrow('Final variables require a MyClass._() constructor') 106 | @freezed 107 | abstract class Final { 108 | factory Final() = _Final; 109 | 110 | final int regularProperty = 42; 111 | } 112 | 113 | class _Final implements Final { 114 | @override 115 | final int regularProperty = 0; 116 | } 117 | 118 | @ShouldThrow('@Default cannot be used on non-optional parameters') 119 | @freezed 120 | abstract class DefaultOnRequiredPositional { 121 | factory DefaultOnRequiredPositional(@Default(42) int a) = 122 | _DefaultOnRequiredPositional; 123 | } 124 | 125 | class _DefaultOnRequiredPositional implements DefaultOnRequiredPositional { 126 | _DefaultOnRequiredPositional(int a); 127 | } 128 | 129 | @ShouldThrow('Fallback union was specified but no fallback constructor exists.') 130 | @Freezed(fallbackUnion: 'fallback') 131 | class FallbackUnionMissing { 132 | factory FallbackUnionMissing.first() = _First; 133 | factory FallbackUnionMissing.second() = _Second; 134 | } 135 | 136 | class _First implements FallbackUnionMissing { 137 | _First(); 138 | } 139 | 140 | class _Second implements FallbackUnionMissing { 141 | _Second(); 142 | } 143 | 144 | @ShouldThrow('Classes decorated with @freezed cannot have mutable properties') 145 | @freezed 146 | abstract class MutableProperty { 147 | MutableProperty._(); 148 | 149 | factory MutableProperty() = _MutableProperty; 150 | 151 | int? a; 152 | } 153 | 154 | class _MutableProperty implements MutableProperty { 155 | @override 156 | int? a; 157 | } 158 | 159 | @ShouldGenerate('', contains: true, expectedLogItems: []) 160 | @freezed 161 | abstract class AbstractClass with _$AbstractClass { 162 | const factory AbstractClass() = _AbstractClass; 163 | } 164 | 165 | class _AbstractClass implements AbstractClass { 166 | const _AbstractClass(); 167 | } 168 | 169 | mixin _$AbstractClass {} 170 | -------------------------------------------------------------------------------- /packages/freezed/test/manual_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | part 'manual_test.freezed.dart'; 5 | part 'manual_test.g.dart'; 6 | 7 | // https://github.com/rrousselGit/freezed/issues/1216 8 | @freezed 9 | class Product with _$Product { 10 | @override 11 | final int price; 12 | const Product(this.price); 13 | } 14 | 15 | // https://github.com/rrousselGit/freezed/issues/1216 16 | @freezed 17 | class CartLine with _$CartLine { 18 | @override 19 | final Product product; 20 | @override 21 | final int amount; 22 | 23 | CartLine({required this.product, int? defaultAmount}) 24 | : amount = defaultAmount ?? product.price; 25 | } 26 | 27 | // Regression for https://github.com/rrousselGit/freezed/issues/1168 28 | @freezed 29 | class Person with _$Person { 30 | const Person({required this.firstName, required this.lastName}); 31 | 32 | @override 33 | final String firstName; 34 | @override 35 | final String lastName; 36 | 37 | String get fullName => '$firstName $lastName'; 38 | } 39 | 40 | @freezed 41 | @JsonSerializable() 42 | class JsonManual with _$JsonManual { 43 | JsonManual(this.a); 44 | factory JsonManual.fromJson(Map json) => 45 | _$JsonManualFromJson(json); 46 | 47 | @override 48 | final int a; 49 | 50 | Map toJson() => _$JsonManualToJson(this); 51 | } 52 | 53 | @freezed 54 | class ManualWithBothDefault with _$ManualWithBothDefault { 55 | ManualWithBothDefault._(String this.b) : a = null; 56 | ManualWithBothDefault(int this.a) : b = null; 57 | 58 | @override 59 | final int? a; 60 | @override 61 | final String? b; 62 | } 63 | 64 | @freezed 65 | class ManualWithPrivateDefault with _$ManualWithPrivateDefault { 66 | ManualWithPrivateDefault.b(String this.b) : a = null; 67 | ManualWithPrivateDefault._(int this.a) : b = null; 68 | 69 | @override 70 | final int? a; 71 | @override 72 | final String? b; 73 | } 74 | 75 | @freezed 76 | class ManualWithDefault with _$ManualWithDefault { 77 | ManualWithDefault.b(String this.b) : a = null; 78 | ManualWithDefault(int this.a) : b = null; 79 | 80 | @override 81 | final int? a; 82 | @override 83 | final String? b; 84 | } 85 | 86 | @freezed 87 | class ManualWithoutDefault with _$ManualWithoutDefault { 88 | ManualWithoutDefault.a(int this.a) : b = null; 89 | ManualWithoutDefault.b(String this.b) : a = null; 90 | 91 | @override 92 | final int? a; 93 | @override 94 | final String? b; 95 | } 96 | 97 | @freezed 98 | class ManualWithoutDefault2 with _$ManualWithoutDefault2 { 99 | ManualWithoutDefault2.b(String this.b) : a = null; 100 | ManualWithoutDefault2.a(int this.a) : b = null; 101 | 102 | @override 103 | final int? a; 104 | @override 105 | final String? b; 106 | } 107 | 108 | void main() { 109 | group('CartLine', () { 110 | test('has copyWith use default', () { 111 | expect( 112 | CartLine( 113 | product: const Product(42), 114 | ).copyWith(product: const Product(21)), 115 | CartLine(product: const Product(21)), 116 | ); 117 | }); 118 | }); 119 | 120 | group('ManualWithBothDefault', () { 121 | test('has copyWith use default', () { 122 | expect( 123 | ManualWithBothDefault(42).copyWith(a: 21), 124 | ManualWithBothDefault(21), 125 | ); 126 | }); 127 | test('overrides ==/hashCode/toString', () { 128 | final manual = ManualWithBothDefault(42); 129 | final manual2 = ManualWithBothDefault._('foo'); 130 | 131 | expect(manual, ManualWithBothDefault(42)); 132 | expect(manual, isNot(ManualWithBothDefault(21))); 133 | expect(manual.hashCode, ManualWithBothDefault(42).hashCode); 134 | expect(manual.hashCode, isNot(ManualWithBothDefault(21).hashCode)); 135 | expect(manual.toString(), 'ManualWithBothDefault(a: 42, b: null)'); 136 | 137 | expect(manual2, ManualWithBothDefault._('foo')); 138 | expect(manual2, isNot(ManualWithBothDefault._('bar'))); 139 | expect(manual2.hashCode, ManualWithBothDefault._('foo').hashCode); 140 | expect(manual2.hashCode, isNot(ManualWithBothDefault._('bar').hashCode)); 141 | expect(manual2.toString(), 'ManualWithBothDefault(a: null, b: foo)'); 142 | }); 143 | }); 144 | 145 | group('ManualWithPrivateDefault', () { 146 | test('has copyWith use _', () { 147 | expect( 148 | ManualWithPrivateDefault._(42).copyWith(a: 21), 149 | ManualWithPrivateDefault._(21), 150 | ); 151 | }); 152 | }); 153 | 154 | group('ManualWithDefault', () { 155 | test('has copyWith use default', () { 156 | expect(ManualWithDefault(42).copyWith(a: 21), ManualWithDefault(21)); 157 | }); 158 | }); 159 | 160 | group('ManualWithoutDefault', () { 161 | test('has copyWith target the first ctor', () { 162 | expect( 163 | ManualWithoutDefault.a(42).copyWith(a: 21), 164 | ManualWithoutDefault.a(21), 165 | ); 166 | }); 167 | }); 168 | group('ManualWithoutDefault2', () { 169 | test('has copyWith target the first ctor', () { 170 | expect( 171 | ManualWithoutDefault2.a(42).copyWith(b: 'foo'), 172 | ManualWithoutDefault2.b('foo'), 173 | ); 174 | }); 175 | }); 176 | } 177 | -------------------------------------------------------------------------------- /packages/freezed/lib/src/freezed_generator.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/ast/ast.dart'; 2 | import 'package:collection/collection.dart'; 3 | import 'package:freezed/src/templates/copy_with.dart'; 4 | import 'package:freezed/src/templates/pattern_template.dart'; 5 | import 'package:freezed/src/tools/type.dart'; 6 | import 'package:freezed_annotation/freezed_annotation.dart' show Freezed; 7 | import 'package:meta/meta.dart'; 8 | 9 | import 'models.dart'; 10 | import 'parse_generator.dart'; 11 | import 'templates/abstract_template.dart'; 12 | import 'templates/concrete_template.dart'; 13 | import 'templates/from_json_template.dart'; 14 | 15 | extension StringX on String { 16 | String get public { 17 | if (startsWith('_')) return substring(1); 18 | return this; 19 | } 20 | } 21 | 22 | @immutable 23 | class FreezedGenerator extends ParserGenerator { 24 | FreezedGenerator(this._buildYamlConfigs, {required this.format}); 25 | 26 | final Freezed _buildYamlConfigs; 27 | final bool format; 28 | 29 | Iterable _getCommonDeepCloneableProperties( 30 | List constructors, 31 | PropertyList commonProperties, 32 | ) sync* { 33 | for (final commonProperty in commonProperties.cloneableProperties) { 34 | final commonGetter = commonProperties.readableProperties.firstWhereOrNull( 35 | (e) => e.name == commonProperty.name, 36 | ); 37 | final deepCopyProperty = constructors.firstOrNull?.deepCloneableProperties 38 | .firstWhereOrNull((e) => e.name == commonProperty.name); 39 | 40 | if (deepCopyProperty == null || commonGetter == null) continue; 41 | 42 | yield deepCopyProperty.copyWith( 43 | nullable: 44 | deepCopyProperty.nullable || 45 | commonProperty.type.isNullable || 46 | commonGetter.type.isNullable, 47 | ); 48 | } 49 | } 50 | 51 | Iterable _generateForAll(Library globalData) sync* { 52 | yield r'T _$identity(T value) => value;'; 53 | } 54 | 55 | @override 56 | Iterable generateAll( 57 | List units, 58 | List annotations, 59 | ) sync* { 60 | if (annotations.isEmpty) return; 61 | 62 | if (!format) yield '// dart format off'; 63 | 64 | final library = Library.from(units); 65 | for (final value in _generateForAll(library)) { 66 | yield value; 67 | } 68 | 69 | final userDefinedClasses = Class.parseAll( 70 | units, 71 | annotations, 72 | library, 73 | globalConfigs: _buildYamlConfigs, 74 | ); 75 | 76 | for (final data in userDefinedClasses) { 77 | for (final value in _generateForData(library, data)) { 78 | yield value; 79 | } 80 | } 81 | 82 | if (!format) yield '// dart format on'; 83 | } 84 | 85 | Iterable _generateForData(Library globalData, Class data) sync* { 86 | if (data.options.fromJson) yield FromJson(data); 87 | 88 | final commonCopyWith = data.options.annotation.copyWith ?? true 89 | ? CopyWith( 90 | parents: data.parents, 91 | clonedClassName: data.name, 92 | readableProperties: data.properties.readableProperties, 93 | cloneableProperties: data.properties.cloneableProperties, 94 | deepCloneableProperties: _getCommonDeepCloneableProperties( 95 | data.constructors, 96 | data.properties, 97 | ).toList(), 98 | genericsDefinition: data.genericsDefinitionTemplate, 99 | genericsParameter: data.genericsParameterTemplate, 100 | data: data, 101 | ) 102 | : null; 103 | 104 | yield Abstract( 105 | data: data, 106 | copyWith: commonCopyWith, 107 | commonProperties: data.properties.readableProperties, 108 | globalData: globalData, 109 | ); 110 | 111 | yield patterns(data); 112 | 113 | for (final constructor in data.constructors) { 114 | yield Concrete( 115 | data: data, 116 | constructor: constructor, 117 | commonProperties: data.properties.readableProperties, 118 | globalData: globalData, 119 | copyWith: 120 | data.options.annotation.copyWith ?? 121 | constructor.parameters.allParameters.isNotEmpty 122 | ? CopyWith( 123 | parents: {}, 124 | clonedClassName: constructor.redirectedName, 125 | cloneableProperties: constructor.properties.toList(), 126 | readableProperties: constructor.properties 127 | .where((e) => e.isSynthetic) 128 | .toList(), 129 | deepCloneableProperties: constructor.deepCloneableProperties, 130 | genericsDefinition: data.genericsDefinitionTemplate, 131 | genericsParameter: data.genericsParameterTemplate, 132 | data: data, 133 | parent: commonCopyWith, 134 | ) 135 | : null, 136 | ); 137 | } 138 | } 139 | } 140 | 141 | String? parseLateGetterSource(String source) { 142 | var parenthesis = 0; 143 | 144 | for (var i = 0; i < source.length; i++) { 145 | final char = source[i]; 146 | switch (char) { 147 | case '(': 148 | parenthesis++; 149 | break; 150 | case ')': 151 | parenthesis--; 152 | break; 153 | case ';': 154 | if (parenthesis == 0) { 155 | final reg = RegExp(r'^[\s\t\n]*=>[\s\t\n]*(.+)', dotAll: true); 156 | return reg.firstMatch(source.substring(0, i))?.group(1); 157 | } 158 | break; 159 | default: 160 | break; 161 | } 162 | } 163 | return null; 164 | } 165 | -------------------------------------------------------------------------------- /packages/freezed/lib/src/tools/recursive_import_locator.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/element/element2.dart'; 2 | import 'package:collection/collection.dart'; 3 | 4 | extension FindAllAvailableTopLevelElements on LibraryElement2 { 5 | bool isFromPackage(String packageName) { 6 | return firstFragment.source.fullName.startsWith('/$packageName/'); 7 | } 8 | 9 | /// Recursively loops at the import/export directives to know what is available 10 | /// in the library. 11 | /// 12 | /// This function does not guarantees that the elements returned are unique. 13 | /// It is possible for the same object to be present multiple times in the list. 14 | Iterable findAllAvailableTopLevelElements() { 15 | return _findAllAvailableTopLevelElements( 16 | {}, 17 | checkExports: false, 18 | key: _LibraryKey( 19 | hideStatements: {}, 20 | showStatements: {}, 21 | librarySource: firstFragment.source.fullName, 22 | ), 23 | ); 24 | } 25 | 26 | Iterable _findAllAvailableTopLevelElements( 27 | Set<_LibraryKey> visitedLibraryPaths, { 28 | required bool checkExports, 29 | required _LibraryKey key, 30 | }) sync* { 31 | yield* children2; 32 | 33 | final librariesToCheck = checkExports 34 | ? fragments 35 | .expand((e) => e.libraryExports2) 36 | .map(_LibraryDirectives.fromExport) 37 | .nonNulls 38 | : fragments 39 | .expand((e) => e.libraryImports2) 40 | .map(_LibraryDirectives.fromImport) 41 | .nonNulls; 42 | 43 | for (final directive in librariesToCheck) { 44 | if (!visitedLibraryPaths.add(directive.key)) { 45 | continue; 46 | } 47 | 48 | yield* directive.library 49 | ._findAllAvailableTopLevelElements( 50 | visitedLibraryPaths, 51 | checkExports: true, 52 | key: directive.key, 53 | ) 54 | .where((element) { 55 | return (directive.showStatements.isEmpty && 56 | directive.hideStatements.isEmpty) || 57 | (directive.hideStatements.isNotEmpty && 58 | !directive.hideStatements.contains(element.name3)) || 59 | directive.showStatements.contains(element.name3); 60 | }); 61 | } 62 | } 63 | } 64 | 65 | class _LibraryDirectives { 66 | _LibraryDirectives({ 67 | required this.hideStatements, 68 | required this.showStatements, 69 | required this.library, 70 | }); 71 | 72 | static _LibraryDirectives? fromExport(LibraryExport export) { 73 | final library = export.exportedLibrary2; 74 | if (library == null) return null; 75 | 76 | final hideStatements = export.combinators 77 | .whereType() 78 | .expand((e) => e.hiddenNames) 79 | .toSet(); 80 | 81 | final showStatements = export.combinators 82 | .whereType() 83 | .expand((e) => e.shownNames) 84 | .toSet(); 85 | 86 | return _LibraryDirectives( 87 | hideStatements: hideStatements, 88 | showStatements: showStatements, 89 | library: library, 90 | ); 91 | } 92 | 93 | static _LibraryDirectives? fromImport(LibraryImport export) { 94 | final library = export.importedLibrary2; 95 | if (library == null) return null; 96 | 97 | final hideStatements = export.combinators 98 | .whereType() 99 | .expand((e) => e.hiddenNames) 100 | .toSet(); 101 | 102 | final showStatements = export.combinators 103 | .whereType() 104 | .expand((e) => e.shownNames) 105 | .toSet(); 106 | 107 | return _LibraryDirectives( 108 | hideStatements: hideStatements, 109 | showStatements: showStatements, 110 | library: library, 111 | ); 112 | } 113 | 114 | final Set hideStatements; 115 | final Set showStatements; 116 | final LibraryElement2 library; 117 | 118 | _LibraryKey get key { 119 | return _LibraryKey( 120 | hideStatements: hideStatements, 121 | showStatements: showStatements, 122 | librarySource: library.firstFragment.source.fullName, 123 | ); 124 | } 125 | } 126 | 127 | /// A unique key for an import/export statement, to avoid visiting a library twice 128 | /// as libraries sometimes have circular dependencies – which would cause an infinite loop. 129 | /// 130 | /// We can't simply use the library path, as it's possible for the same library 131 | /// to be exported multiple time: 132 | /// 133 | /// ```dart 134 | /// export 'library.dart' show A; 135 | /// export 'library.dart' show B; 136 | /// ``` 137 | class _LibraryKey { 138 | _LibraryKey({ 139 | required this.hideStatements, 140 | required this.showStatements, 141 | required this.librarySource, 142 | }); 143 | 144 | final Set hideStatements; 145 | final Set showStatements; 146 | final String librarySource; 147 | 148 | @override 149 | bool operator ==(Object other) { 150 | return other is _LibraryKey && 151 | librarySource == other.librarySource && 152 | const SetEquality().equals( 153 | hideStatements, 154 | other.hideStatements, 155 | ) && 156 | const SetEquality().equals( 157 | showStatements, 158 | other.showStatements, 159 | ); 160 | } 161 | 162 | @override 163 | int get hashCode => Object.hash( 164 | librarySource, 165 | const SetEquality().hash(hideStatements), 166 | const SetEquality().hash(showStatements), 167 | ); 168 | 169 | @override 170 | String toString() { 171 | return '(path: $librarySource, hide: $hideStatements, show: $showStatements)'; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /packages/freezed_annotation/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.1.0 - 2025-07-02 2 | 3 | Added `when`/`map` back 4 | 5 | ## 3.0.0 - 2025-02-25 6 | 7 | - **Breaking** removed `when`/`map` related options 8 | 9 | ## 2.4.4 - 2024-07-15 10 | 11 | - Require json_annotation ^4.8.0 12 | 13 | ## 2.4.3 - 2024-07-09 (retracted) 14 | 15 | - Stop using `JsonKey(ignore: true)` in favour of `JsonKey(includeFromJson: false, includeToJson: false)` 16 | (thanks to @lrsvmb) 17 | 18 | ## 2.4.2 - 2024-07-02 19 | 20 | - Require Dart >=3.0.0 21 | - Support latest `collection` 22 | 23 | ## 2.4.1 - 2023-07-12 24 | 25 | - The generic type of `@With`/`@Implements` now defaults to `Object?` instead of `dynamic` 26 | - Allow enabling/disabling all `when`/`map` variants at once (thanks to @gaetschwartz) 27 | 28 | ## 2.2.0 29 | 30 | - Re-introduced `@With.fromString` and `@Implements.fromString` to allow unions 31 | to implement generic types. (thanks to @rorystephenson) 32 | 33 | ## 2.1.0 34 | 35 | - Add support for de/serializing generic Freezed classes (Thanks to @TimWhiting) 36 | 37 | ## 2.0.3 38 | 39 | – fix: build.yaml decoding crash 40 | 41 | ## 2.0.1 42 | 43 | - Fixed a bug where the generated when/map methods were potentially invalid when 44 | using default values 45 | - Fixed a bug where when/map methods were generated even when not necessary 46 | 47 | ## 2.0.0 48 | 49 | - **Breaking**: freezed_annotation no-longer exports the entire package:collection 50 | - **Breaking** Removed `@Freezed(maybeMap: )` & `@Freezed(maybeWhen: )` in favor of a separate: 51 | 52 | ```Dart 53 | @Freezed(map: FreezedMap(...), when: FreezedWhenOptions(...)) 54 | ``` 55 | 56 | - Feat: Add screaming snake union case type (#617) (thanks to @zbarbuto) 57 | - Added `@unfreezed` as a variant to `@freezed`, for mutable classes 58 | 59 | ## 1.1.0 60 | 61 | Added support for disabling the generation of `maybeMap`/`maybeWhen` (thanks to @Lyokone) 62 | 63 | ## 1.0.0 64 | 65 | freezed_annotation is now stable 66 | 67 | ## 0.15.0 68 | 69 | - **Breaking** Changed the syntax for `@With` and `@Implements` to use a generic annotation. 70 | Before: 71 | 72 | ```dart 73 | @With(MyClass) 74 | @With.fromString('Generic') 75 | ``` 76 | 77 | After: 78 | 79 | ```dart 80 | @With() 81 | @With>() 82 | ``` 83 | 84 | ## 0.14.3 85 | 86 | Upgraded to support last json_annotation version 87 | 88 | ## 0.14.2 89 | 90 | - Added the ability to specify a fallback constructor when deserializing unions (thanks to @Brazol) 91 | 92 | ## 0.14.1 93 | 94 | - Added the ability to customise the JSON value of a union. See https://github.com/rrousselGit/freezed#fromjson---classes-with-multiple-constructors for more information (Thanks to @ookami-kb) 95 | 96 | ## 0.14.0 97 | 98 | - Stable null safety release 99 | - removed `@nullable`. 100 | Instead of: 101 | ```dart 102 | factory Example({@nullable int a}) = _Example; 103 | ``` 104 | Do: 105 | ```dart 106 | factory Example({int? a}) = _Example; 107 | ``` 108 | - removed `@late`. 109 | Instead of: 110 | 111 | ```dart 112 | abstract class Person with _$Person { 113 | factory Person({ 114 | required String firstName, 115 | required String lastName, 116 | }) = _Person; 117 | 118 | @late 119 | String get fullName => '$firstName $lastName'; 120 | } 121 | ``` 122 | 123 | Do: 124 | 125 | ```dart 126 | abstract class Person with _$Person { 127 | Person._(); 128 | factory Person({ 129 | required String firstName, 130 | required String lastName, 131 | }) = _Person; 132 | 133 | late final fullName = '$firstName $lastName'; 134 | } 135 | ``` 136 | 137 | ## 0.13.0-nullsafety.0 138 | 139 | - Migrated to null safety 140 | - removed `@nullable`. 141 | Instead of: 142 | ```dart 143 | factory Example({@nullable int a}) = _Example; 144 | ``` 145 | Do: 146 | ```dart 147 | factory Example({int? a}) = _Example; 148 | ``` 149 | - removed `@late`. 150 | Instead of: 151 | 152 | ```dart 153 | abstract class Person with _$Person { 154 | factory Person({ 155 | required String firstName, 156 | required String lastName, 157 | }) = _Person; 158 | 159 | @late 160 | String get fullName => '$firstName $lastName'; 161 | } 162 | ``` 163 | 164 | Do: 165 | 166 | ```dart 167 | abstract class Person with _$Person { 168 | Person._(); 169 | factory Person({ 170 | required String firstName, 171 | required String lastName, 172 | }) = _Person; 173 | 174 | late final fullName = '$firstName $lastName'; 175 | } 176 | ``` 177 | 178 | ## 0.12.0 179 | 180 | - Added `Assert` decorator to generate `assert(...)` statements on Freezed classes: 181 | 182 | ```dart 183 | abstract class Person with _$Person { 184 | @Assert('name.trim().isNotEmpty', 'name cannot be empty') 185 | @Assert('age >= 0') 186 | factory Person({ 187 | String name, 188 | int age, 189 | }) = _Person; 190 | } 191 | ``` 192 | 193 | - Added a way to customize the de/serialization of union types using the 194 | `@Freezed(unionKey: 'my-key')` decorator. 195 | 196 | See also https://github.com/rrousselGit/freezed#fromjson---classes-with-multiple-constructors 197 | 198 | ## 0.11.0 199 | 200 | - Added `@With` and `@Implements` decorators to allow only a specific constructor 201 | of a union type to implement an interface: 202 | 203 | ```dart 204 | @freezed 205 | abstract class Example with _$Example { 206 | const factory Example.person(String name, int age) = Person; 207 | 208 | @Implements(GeographicArea) 209 | const factory Example.city(String name, int population) = City; 210 | } 211 | ``` 212 | 213 | Thanks to @long1eu~ 214 | 215 | ## 0.7.1 216 | 217 | Minor change to `@Default` to fix an issue with complex default values. 218 | 219 | ## 0.7.0 220 | 221 | Add `@Default` annotation 222 | 223 | ## 0.6.0 224 | 225 | Added `@late` annotation. 226 | 227 | ## 0.4.0 228 | 229 | Added a `@nullable` annotation. 230 | 231 | ## 0.3.1 232 | 233 | Change version of `collection` to work with `flutter_test`. 234 | 235 | ## 0.3.0 236 | 237 | Initial release of the annotation package. 238 | -------------------------------------------------------------------------------- /packages/freezed/lib/src/templates/properties.dart: -------------------------------------------------------------------------------- 1 | import 'package:analyzer/dart/ast/ast.dart'; 2 | import 'package:analyzer/dart/element/element2.dart'; 3 | import 'package:analyzer/dart/element/type.dart'; 4 | import 'package:freezed/src/ast.dart'; 5 | import 'package:freezed/src/templates/parameter_template.dart'; 6 | import 'package:freezed/src/tools/type.dart'; 7 | import 'package:source_gen/source_gen.dart'; 8 | 9 | import 'concrete_template.dart'; 10 | import 'prototypes.dart'; 11 | 12 | class Property { 13 | Property({ 14 | required this.type, 15 | required this.typeDisplayString, 16 | required this.name, 17 | required this.decorators, 18 | required this.defaultValueSource, 19 | required this.hasJsonKey, 20 | required this.doc, 21 | required this.isSynthetic, 22 | required this.isFinal, 23 | }); 24 | 25 | Property.fromParameter(Parameter p, {required bool isSynthetic}) 26 | : this( 27 | decorators: p.decorators, 28 | name: p.name, 29 | isFinal: p.isFinal, 30 | doc: p.doc, 31 | type: p.type, 32 | typeDisplayString: p.typeDisplayString, 33 | defaultValueSource: p.defaultValueSource, 34 | isSynthetic: isSynthetic, 35 | hasJsonKey: false, 36 | ); 37 | 38 | static Property fromFormalParameter( 39 | FormalParameter parameter, { 40 | required bool addImplicitFinal, 41 | required bool isSynthetic, 42 | }) { 43 | final element = parameter.declaredFragment!.element; 44 | 45 | final defaultValue = element.defaultValue; 46 | if (defaultValue != null && 47 | (element.isRequired || element.isRequiredPositional)) { 48 | throw InvalidGenerationSourceError( 49 | '@Default cannot be used on non-optional parameters', 50 | element: element, 51 | ); 52 | } 53 | 54 | return Property( 55 | name: element.name3!, 56 | isFinal: addImplicitFinal || element.isFinal, 57 | isSynthetic: isSynthetic, 58 | doc: parameter.documentation ?? '', 59 | type: element.type, 60 | typeDisplayString: parseTypeSource(parameter), 61 | decorators: parseDecorators(element.metadata2.annotations), 62 | defaultValueSource: defaultValue, 63 | hasJsonKey: element.hasJsonKey, 64 | ); 65 | } 66 | 67 | final DartType type; 68 | final String typeDisplayString; 69 | final String name; 70 | final bool isFinal; 71 | final bool isSynthetic; 72 | final List decorators; 73 | final String? defaultValueSource; 74 | final bool hasJsonKey; 75 | final String doc; 76 | 77 | @override 78 | String toString() { 79 | final leading = isFinal ? 'final ' : ''; 80 | return '$doc${decorators.join()} $leading $typeDisplayString $name;'; 81 | } 82 | 83 | Getter get abstractGetter => Getter( 84 | name: name, 85 | type: typeDisplayString, 86 | decorators: decorators, 87 | doc: doc, 88 | body: ';', 89 | ); 90 | 91 | Getter asGetter(String body) => Getter( 92 | name: name, 93 | type: typeDisplayString, 94 | decorators: decorators, 95 | doc: doc, 96 | body: body, 97 | ); 98 | 99 | Setter get abstractSetter => Setter( 100 | name: name, 101 | type: typeDisplayString, 102 | decorators: decorators, 103 | doc: doc, 104 | body: ';', 105 | ); 106 | 107 | Property copyWith({ 108 | DartType? type, 109 | String? typeDisplayString, 110 | String? name, 111 | bool? isFinal, 112 | List? decorators, 113 | String? defaultValueSource, 114 | bool? hasJsonKey, 115 | String? doc, 116 | bool? isPossiblyDartCollection, 117 | TypeParameterElement2? parameterElement, 118 | }) { 119 | return Property( 120 | type: type ?? this.type, 121 | typeDisplayString: typeDisplayString ?? this.typeDisplayString, 122 | name: name ?? this.name, 123 | isSynthetic: isSynthetic, 124 | decorators: decorators ?? this.decorators, 125 | defaultValueSource: defaultValueSource ?? this.defaultValueSource, 126 | hasJsonKey: hasJsonKey ?? this.hasJsonKey, 127 | doc: doc ?? this.doc, 128 | isFinal: isFinal ?? this.isFinal, 129 | ); 130 | } 131 | } 132 | 133 | class Getter { 134 | Getter({ 135 | required String? type, 136 | required this.name, 137 | required this.decorators, 138 | required this.doc, 139 | required this.body, 140 | }) : type = type ?? 'dynamic'; 141 | 142 | final String type; 143 | final String name; 144 | final List decorators; 145 | final String doc; 146 | final String body; 147 | 148 | @override 149 | String toString({bool shouldOverride = false}) { 150 | final finalDecorators = [if (shouldOverride) '@override', ...decorators]; 151 | return '$doc${finalDecorators.join()} $type get $name$body'; 152 | } 153 | } 154 | 155 | class Setter { 156 | Setter({ 157 | required String? type, 158 | required this.name, 159 | required this.decorators, 160 | required this.doc, 161 | required this.body, 162 | }) : type = type ?? 'dynamic'; 163 | 164 | final String type; 165 | final String name; 166 | final List decorators; 167 | final String doc; 168 | final String body; 169 | 170 | @override 171 | String toString() { 172 | return '$doc${decorators.join()} set $name($type value)$body'; 173 | } 174 | } 175 | 176 | extension IsDartCollection on DartType { 177 | /// Whether this type can potentially contain a [List], [Map], [Set] or [Iterable]. 178 | /// 179 | /// This includes types such as [dynamic], [Object] and generics 180 | bool get isPossiblyDartCollection { 181 | final interface = safeCast(); 182 | 183 | return _isDartCollectionType || 184 | isDynamic2 || 185 | isDartCoreObject || 186 | this is TypeParameterType || 187 | (interface != null && 188 | interface.allSupertypes.any((e) => e._isDartCollectionType)); 189 | } 190 | 191 | /// Whether this type is a [List], [Map], [Set] or [Iterable]. 192 | bool get _isDartCollectionType { 193 | return isDartCoreMap || 194 | isDartCoreIterable || 195 | isDartCoreSet || 196 | isDartCoreList; 197 | } 198 | } 199 | 200 | extension on Object? { 201 | T? safeCast() { 202 | final that = this; 203 | if (that is T) return that; 204 | return null; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /packages/freezed/test/common_types_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: omit_local_variable_types, unused_local_variable 2 | 3 | import 'package:analyzer/dart/analysis/results.dart'; 4 | import 'package:build_test/build_test.dart'; 5 | import 'package:expect_error/expect_error.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'integration/common_types.dart'; 9 | 10 | Future main() async { 11 | final library = await Library.parseFromStacktrace(); 12 | 13 | test('has no issue', () async { 14 | final main = await resolveSources( 15 | {'freezed|test/integration/common_types.dart': useAssetReader}, 16 | (r) => r.libraries.firstWhere( 17 | (element) => 18 | element.firstFragment.source.toString().contains('common_types'), 19 | ), 20 | ); 21 | 22 | final errorResult = 23 | await main.session.getErrors( 24 | '/freezed/test/integration/common_types.freezed.dart', 25 | ) 26 | as ErrorsResult; 27 | 28 | expect(errorResult.errors, isEmpty); 29 | }); 30 | 31 | group('CommonSuperSubtype', () { 32 | test('on the shared interface, has the common properties available', () { 33 | const value = CommonSuperSubtype( 34 | nullabilityDifference: 42, 35 | typeDifference: 21, 36 | ); 37 | int? nullabilityDifference = value.nullabilityDifference; 38 | num typeDifference = value.typeDifference; 39 | 40 | expect(value.nullabilityDifference, 42); 41 | expect(value.typeDifference, 21); 42 | 43 | const value2 = CommonSuperSubtype.named( 44 | nullabilityDifference: null, 45 | typeDifference: 22, 46 | ); 47 | expect(value2.nullabilityDifference, null); 48 | expect(value2.typeDifference, 22); 49 | }); 50 | 51 | test('on the subclass, properties use explicit type', () { 52 | const value = CommonSuperSubtype0( 53 | nullabilityDifference: 42, 54 | typeDifference: 21, 55 | ); 56 | int nullabilityDifference = value.nullabilityDifference; 57 | int typeDifference = value.typeDifference; 58 | 59 | expect(value.nullabilityDifference, 42); 60 | expect(value.typeDifference, 21); 61 | 62 | const value2 = CommonSuperSubtype1( 63 | nullabilityDifference: null, 64 | typeDifference: 22, 65 | ); 66 | int? nullabilityDifference2 = value2.nullabilityDifference; 67 | double typeDifference2 = value2.typeDifference; 68 | 69 | expect(value2.nullabilityDifference, null); 70 | expect(value2.typeDifference, 22); 71 | }); 72 | 73 | test( 74 | 'should not have getters for properties that are not shared between all unions', 75 | () async { 76 | await expectLater( 77 | library.withCode(''' 78 | import 'integration/common_types.dart'; 79 | 80 | void main() { 81 | const value = CommonSuperSubtype( 82 | nullabilityDifference: 42, 83 | typeDifference: 21, 84 | ); 85 | // expect-error: UNDEFINED_GETTER 86 | value.unknown; 87 | } 88 | '''), 89 | compiles, 90 | ); 91 | }, 92 | ); 93 | 94 | test('Can clone properties with nullability difference', () { 95 | const value = CommonSuperSubtype( 96 | nullabilityDifference: 42, 97 | typeDifference: 21, 98 | ); 99 | 100 | expect( 101 | value.copyWith(nullabilityDifference: 84), 102 | const CommonSuperSubtype0( 103 | nullabilityDifference: 84, 104 | typeDifference: 21, 105 | ), 106 | ); 107 | }); 108 | 109 | test('Cannot clone with type mismatch', () async { 110 | await expectLater( 111 | library.withCode(''' 112 | import 'integration/common_types.dart'; 113 | 114 | void main() { 115 | final param = CommonSuperSubtype( 116 | nullabilityDifference: 42, 117 | typeDifference: 21, 118 | ); 119 | param.copyWith( 120 | // Since not all unions have a nullable property, we cannot assign "null" 121 | // on the shared interface. 122 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 123 | nullabilityDifference: null, 124 | // Since not all unions use the same type, we can't clone that property at all. 125 | // expect-error: UNDEFINED_NAMED_PARAMETER 126 | typeDifference: 42, 127 | ); 128 | } 129 | '''), 130 | compiles, 131 | ); 132 | }); 133 | }); 134 | 135 | group('DeepCopySharedProperties', () { 136 | test('Can clone properties with nullability difference', () { 137 | const value = DeepCopySharedProperties( 138 | CommonSuperSubtype0(nullabilityDifference: 42, typeDifference: 21), 139 | ); 140 | 141 | expect( 142 | value.copyWith( 143 | value: const CommonSuperSubtype0( 144 | nullabilityDifference: 84, 145 | typeDifference: 21, 146 | ), 147 | ), 148 | const DeepCopySharedProperties( 149 | CommonSuperSubtype0(nullabilityDifference: 84, typeDifference: 21), 150 | ), 151 | ); 152 | 153 | expect( 154 | value.copyWith.value(nullabilityDifference: 84), 155 | const DeepCopySharedProperties( 156 | CommonSuperSubtype0(nullabilityDifference: 84, typeDifference: 21), 157 | ), 158 | ); 159 | }); 160 | 161 | test('Cannot clone with type mismatch', () async { 162 | await expectLater( 163 | library.withCode(''' 164 | import 'integration/common_types.dart'; 165 | 166 | void main() { 167 | final param = DeepCopySharedProperties( 168 | CommonSuperSubtype( 169 | nullabilityDifference: 42, 170 | typeDifference: 21, 171 | ), 172 | ); 173 | param.copyWith.value( 174 | // Since not all unions have a nullable property, we cannot assign "null" 175 | // on the shared interface. 176 | // expect-error: ARGUMENT_TYPE_NOT_ASSIGNABLE 177 | nullabilityDifference: null, 178 | // Since not all unions use the same type, we can't clone that property at all. 179 | // expect-error: UNDEFINED_NAMED_PARAMETER 180 | typeDifference: 42, 181 | ); 182 | } 183 | '''), 184 | compiles, 185 | ); 186 | }); 187 | }); 188 | 189 | group('CommonUnfreezed', () { 190 | test('should have common getters available', () { 191 | final value = CommonUnfreezed.one(a: 42, b: 3.14); 192 | 193 | num a = value.a; 194 | expect(a, 42); 195 | double b = value.b; 196 | expect(b, 3.14); 197 | }); 198 | 199 | test('should not have setters for getters with different type', () async { 200 | await expectLater( 201 | library.withCode(''' 202 | import 'integration/common_types.dart'; 203 | 204 | void main() { 205 | final param = CommonUnfreezed.one(a: 42, b: 3.14); 206 | // expect-error: ASSIGNMENT_TO_FINAL_NO_SETTER 207 | param.a = 42.24; 208 | // OK since all union cases are typed the same 209 | param.b = 42; 210 | } 211 | '''), 212 | compiles, 213 | ); 214 | }); 215 | }); 216 | } 217 | -------------------------------------------------------------------------------- /packages/freezed_annotation/test/freezed_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('FreezedMapOptions', () { 6 | test('.fromJson', () { 7 | expect( 8 | FreezedMapOptions.fromJson({'map': false}).map, 9 | isFalse, 10 | ); 11 | expect( 12 | FreezedMapOptions.fromJson({'map': false}).mapOrNull, 13 | isNull, 14 | ); 15 | expect( 16 | FreezedMapOptions.fromJson({'map': false}).maybeMap, 17 | isNull, 18 | ); 19 | 20 | expect( 21 | FreezedMapOptions.fromJson({'map_or_null': false}) 22 | .map, 23 | isNull, 24 | ); 25 | expect( 26 | FreezedMapOptions.fromJson({'map_or_null': false}) 27 | .mapOrNull, 28 | isFalse, 29 | ); 30 | expect( 31 | FreezedMapOptions.fromJson({'map_or_null': false}) 32 | .maybeMap, 33 | isNull, 34 | ); 35 | 36 | expect( 37 | FreezedMapOptions.fromJson({'maybe_map': false}).map, 38 | isNull, 39 | ); 40 | expect( 41 | FreezedMapOptions.fromJson({'maybe_map': false}) 42 | .mapOrNull, 43 | isNull, 44 | ); 45 | expect( 46 | FreezedMapOptions.fromJson({'maybe_map': false}) 47 | .maybeMap, 48 | isFalse, 49 | ); 50 | }); 51 | 52 | test('.all', () { 53 | expect(FreezedMapOptions.all.map, isTrue); 54 | expect(FreezedMapOptions.all.maybeMap, isTrue); 55 | expect(FreezedMapOptions.all.mapOrNull, isTrue); 56 | }); 57 | 58 | test('.none', () { 59 | expect(FreezedMapOptions.none.map, isFalse); 60 | expect(FreezedMapOptions.none.maybeMap, isFalse); 61 | expect(FreezedMapOptions.none.mapOrNull, isFalse); 62 | }); 63 | 64 | test('()', () { 65 | expect(const FreezedMapOptions().map, isNull); 66 | expect(const FreezedMapOptions().maybeMap, isNull); 67 | expect(const FreezedMapOptions().mapOrNull, isNull); 68 | }); 69 | }); 70 | 71 | group('FreezedWhenOptions', () { 72 | test('.fromJson', () { 73 | expect( 74 | FreezedWhenOptions.fromJson({'when': false}).when, 75 | isFalse, 76 | ); 77 | expect( 78 | FreezedWhenOptions.fromJson({'when': false}) 79 | .whenOrNull, 80 | isNull, 81 | ); 82 | expect( 83 | FreezedWhenOptions.fromJson({'when': false}) 84 | .maybeWhen, 85 | isNull, 86 | ); 87 | 88 | expect( 89 | FreezedWhenOptions.fromJson({'when_or_null': false}) 90 | .when, 91 | isNull, 92 | ); 93 | expect( 94 | FreezedWhenOptions.fromJson({'when_or_null': false}) 95 | .whenOrNull, 96 | isFalse, 97 | ); 98 | expect( 99 | FreezedWhenOptions.fromJson({'when_or_null': false}) 100 | .maybeWhen, 101 | isNull, 102 | ); 103 | 104 | expect( 105 | FreezedWhenOptions.fromJson({'maybe_when': false}) 106 | .when, 107 | isNull); 108 | expect( 109 | FreezedWhenOptions.fromJson({'maybe_when': false}) 110 | .whenOrNull, 111 | isNull, 112 | ); 113 | expect( 114 | FreezedWhenOptions.fromJson({'maybe_when': false}) 115 | .maybeWhen, 116 | isFalse, 117 | ); 118 | }); 119 | 120 | test('.all', () { 121 | expect(FreezedWhenOptions.all.when, isTrue); 122 | expect(FreezedWhenOptions.all.maybeWhen, isTrue); 123 | expect(FreezedWhenOptions.all.whenOrNull, isTrue); 124 | }); 125 | 126 | test('.none', () { 127 | expect(FreezedWhenOptions.none.when, isFalse); 128 | expect(FreezedWhenOptions.none.maybeWhen, isFalse); 129 | expect(FreezedWhenOptions.none.whenOrNull, isFalse); 130 | }); 131 | 132 | test('()', () { 133 | expect(const FreezedWhenOptions().when, isNull); 134 | expect(const FreezedWhenOptions().maybeWhen, isNull); 135 | expect(const FreezedWhenOptions().whenOrNull, isNull); 136 | }); 137 | }); 138 | 139 | group('Freezed', () { 140 | test('.fromJson', () { 141 | final defaultValue = Freezed.fromJson({}); 142 | 143 | expect(defaultValue.copyWith, isNull); 144 | expect(defaultValue.equal, isNull); 145 | expect(defaultValue.fallbackUnion, null); 146 | expect(defaultValue.fromJson, isNull); 147 | expect(defaultValue.map, isNull); 148 | expect(defaultValue.toJson, isNull); 149 | expect(defaultValue.toStringOverride, isNull); 150 | expect(defaultValue.unionKey, 'runtimeType'); 151 | expect(defaultValue.unionValueCase, isNull); 152 | expect(defaultValue.when, isNull); 153 | expect(defaultValue.makeCollectionsUnmodifiable, isTrue); 154 | }); 155 | 156 | test('.fromJson({map: x})', () { 157 | expect(Freezed.fromJson({'map': false}).map, FreezedMapOptions.none); 158 | expect(Freezed.fromJson({'map': true}).map, FreezedMapOptions.all); 159 | 160 | expect( 161 | Freezed.fromJson({ 162 | 'map': { 163 | 'map': true, 164 | 'maybe_map': false, 165 | 'map_or_null': true, 166 | } 167 | }).map, 168 | isA() 169 | .having( 170 | (e) => e.map, 171 | 'map', 172 | isTrue, 173 | ) 174 | .having( 175 | (e) => e.maybeMap, 176 | 'maybeMap', 177 | isFalse, 178 | ) 179 | .having( 180 | (e) => e.mapOrNull, 181 | 'mapOrNull', 182 | isTrue, 183 | ), 184 | ); 185 | 186 | expect( 187 | () => Freezed.fromJson({'map': 42}), 188 | throwsA(isA()), 189 | ); 190 | }); 191 | 192 | test('.fromJson({when: x})', () { 193 | expect(Freezed.fromJson({'when': false}).when, FreezedWhenOptions.none); 194 | expect(Freezed.fromJson({'when': true}).when, FreezedWhenOptions.all); 195 | 196 | expect( 197 | Freezed.fromJson({ 198 | 'when': { 199 | 'when': true, 200 | 'maybe_when': false, 201 | 'when_or_null': true, 202 | } 203 | }).when, 204 | isA() 205 | .having( 206 | (e) => e.when, 207 | 'when', 208 | isTrue, 209 | ) 210 | .having( 211 | (e) => e.maybeWhen, 212 | 'maybeWhen', 213 | isFalse, 214 | ) 215 | .having( 216 | (e) => e.whenOrNull, 217 | 'whenOrNull', 218 | isTrue, 219 | ), 220 | ); 221 | 222 | expect( 223 | () => Freezed.fromJson({'when': 42}), 224 | throwsA(isA()), 225 | ); 226 | }); 227 | }); 228 | 229 | test('unfreezed', () { 230 | expect(unfreezed.makeCollectionsUnmodifiable, false); 231 | expect(unfreezed.equal, false); 232 | expect(unfreezed.addImplicitFinal, false); 233 | }); 234 | } 235 | -------------------------------------------------------------------------------- /packages/freezed/test/integration/multiple_constructors.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'multiple_constructors.freezed.dart'; 4 | part 'multiple_part.dart'; 5 | 6 | @freezed 7 | sealed class EjectCaseToPart with _$EjectCaseToPart { 8 | factory EjectCaseToPart.a() = EjectedToPart; 9 | factory EjectCaseToPart.b() = EjectedToLocal; 10 | } 11 | 12 | class EjectedToLocal implements EjectCaseToPart {} 13 | 14 | @freezed 15 | sealed class PrivateUnion with _$PrivateUnion { 16 | // ignore: unused_element 17 | const factory PrivateUnion._(int value) = _PrivateUnion; 18 | // ignore: unused_element 19 | const factory PrivateUnion._b(int value) = _PrivateUnionB; 20 | } 21 | 22 | @freezed 23 | sealed class Response with _$Response { 24 | Response._({DateTime? time}) : time = time ?? DateTime(0, 0, 0); 25 | // Constructors may enable passing parameters to ._(); 26 | factory Response.data(T value, {DateTime? time}) = ResponseData; 27 | // If those parameters are named optionals, they are not required to be passed. 28 | factory Response.error(Object error) = ResponseError; 29 | 30 | @override 31 | final DateTime time; 32 | } 33 | 34 | @freezed 35 | abstract class ManualPositionInAnyOrder with _$ManualPositionInAnyOrder { 36 | const ManualPositionInAnyOrder._(this.a, this.b); 37 | const factory ManualPositionInAnyOrder(String a, int b) = 38 | _ManualPositionInAnyOrder; 39 | const factory ManualPositionInAnyOrder.other(int b, String a) = 40 | _ManualPositionInAnyOrder2; 41 | 42 | @override 43 | final String a; 44 | @override 45 | final int b; 46 | } 47 | 48 | @freezed 49 | abstract class ManualNamedOptionalProperty with _$ManualNamedOptionalProperty { 50 | const ManualNamedOptionalProperty._({this.value = 0}); 51 | const factory ManualNamedOptionalProperty(int value) = _ManualNamedProperty; 52 | const factory ManualNamedOptionalProperty.a() = _ManualNamedPropertyA; 53 | 54 | @override 55 | final int value; 56 | } 57 | 58 | @freezed 59 | abstract class Subclass with _$Subclass { 60 | const Subclass._({required this.value}); 61 | const factory Subclass.a(int value) = _SubclassA; 62 | const factory Subclass.b(int value) = _SubclassB; 63 | 64 | // Check that no @override is nu 65 | @override 66 | final int value; 67 | } 68 | 69 | @unfreezed 70 | abstract class UnfreezedImmutableUnion with _$UnfreezedImmutableUnion { 71 | factory UnfreezedImmutableUnion(final String a) = 72 | DirectUnfreezedImmutableUnion; 73 | factory UnfreezedImmutableUnion.named(String a) = 74 | DirectUnfreezedImmutableUnionNamed; 75 | } 76 | 77 | @unfreezed 78 | abstract class UnfreezedImmutableUnion2 with _$UnfreezedImmutableUnion2 { 79 | factory UnfreezedImmutableUnion2(String a) = DirectUnfreezedImmutableUnion2; 80 | factory UnfreezedImmutableUnion2.named(final String a) = 81 | DirectUnfreezedImmutableUnionNamed2; 82 | } 83 | 84 | @unfreezed 85 | abstract class MutableUnion with _$MutableUnion { 86 | factory MutableUnion(String a, int b) = MutableUnion0; 87 | factory MutableUnion.named(String a, int c) = MutableUnion1; 88 | } 89 | 90 | @freezed 91 | abstract class DefaultValueNamedConstructor 92 | with _$DefaultValueNamedConstructor { 93 | factory DefaultValueNamedConstructor.a([@Default(42) int value]) = 94 | _ADefaultValueNamedConstructor; 95 | factory DefaultValueNamedConstructor.b([@Default(42) int value]) = 96 | _BDefaultValueNamedConstructor; 97 | } 98 | 99 | @freezed 100 | abstract class NamedDefaultValueNamedConstructor 101 | with _$NamedDefaultValueNamedConstructor { 102 | factory NamedDefaultValueNamedConstructor.a({@Default(42) int value}) = 103 | _BNamedDefaultValueNamedConstructor; 104 | 105 | factory NamedDefaultValueNamedConstructor.b({@Default(42) int value}) = 106 | _ANamedDefaultValueNamedConstructor; 107 | } 108 | 109 | @freezed 110 | abstract class NoCommonParam with _$NoCommonParam { 111 | const factory NoCommonParam(String a, {int? b}) = NoCommonParam0; 112 | const factory NoCommonParam.named(double c, [Object? d]) = NoCommonParam1; 113 | } 114 | 115 | // Regression test for https://github.com/rrousselGit/freezed/issues/358 116 | @freezed 117 | abstract class Regression358 with _$Regression358 { 118 | const factory Regression358({required int number}) = _Regression358; 119 | 120 | factory Regression358.withSpecificColor({int count = 2}) => 121 | Regression358(number: count); 122 | } 123 | 124 | @freezed 125 | abstract class SharedParam with _$SharedParam { 126 | const factory SharedParam(String a, int b) = SharedParam0; 127 | const factory SharedParam.named(String a, int c) = SharedParam1; 128 | } 129 | 130 | @freezed 131 | abstract class Complex with _$Complex { 132 | @Assert("a != ''", '"Hello"') 133 | const factory Complex( 134 | /// Hello 135 | String a, 136 | ) = Complex0; 137 | 138 | @Assert("a == ''") 139 | @Assert('b == true', 'b must be true') 140 | const factory Complex.first( 141 | /// World 142 | String a, { 143 | 144 | /// B 145 | bool? b, 146 | double? d, 147 | }) = Complex1; 148 | 149 | const factory Complex.second( 150 | String a, [ 151 | 152 | /// C 153 | int? c, 154 | double? d, 155 | ]) = Complex2; 156 | } 157 | 158 | @freezed 159 | abstract class SwitchTest with _$SwitchTest { 160 | const factory SwitchTest(String a) = SwitchTest0; 161 | const factory SwitchTest.first(String a, {bool? b, double? d}) = SwitchTest1; 162 | const factory SwitchTest.second(String a, [int? c, double? d]) = SwitchTest2; 163 | } 164 | 165 | @freezed 166 | abstract class NoDefault with _$NoDefault { 167 | const factory NoDefault.first(String a) = NoDefault1; 168 | const factory NoDefault.second(String a) = NoDefault2; 169 | } 170 | 171 | @freezed 172 | sealed class NameConflict with _$NameConflict { 173 | factory NameConflict.something(Error error) = Something; 174 | factory NameConflict.error(Error error) = SomeError; 175 | } 176 | 177 | @freezed 178 | abstract class Recursive with _$Recursive { 179 | factory Recursive() = RecursiveImpl; 180 | factory Recursive.next(RecursiveImpl value) = _RecursiveNext; 181 | } 182 | 183 | @freezed 184 | abstract class RecursiveWith$Dollar with _$RecursiveWith$Dollar { 185 | factory RecursiveWith$Dollar() = RecursiveWith$DollarImpl; 186 | factory RecursiveWith$Dollar.next(RecursiveWith$DollarImpl value) = 187 | _RecursiveWith$DollarNext; 188 | } 189 | 190 | @freezed 191 | abstract class RequiredParams with _$RequiredParams { 192 | const factory RequiredParams({required String a}) = RequiredParams0; 193 | const factory RequiredParams.second({required String a}) = RequiredParams1; 194 | } 195 | 196 | @Freezed( 197 | // We're relying twice on the generated freezed types, which isn't supported. 198 | // This breaks copyWith as it thinks both union cases have `children` as type `List` 199 | copyWith: false, 200 | ) 201 | abstract class NestedList with _$NestedList { 202 | factory NestedList.shallow(List children) = 203 | ShallowNestedList; 204 | factory NestedList.deep(List children) = DeepNestedList; 205 | } 206 | 207 | @freezed 208 | abstract class NestedListItem with _$NestedListItem { 209 | factory NestedListItem.leaf() = LeafNestedListItem; 210 | factory NestedListItem.inner(List children) = 211 | InnerNestedListItem; 212 | } 213 | 214 | @Freezed( 215 | // We're relying twice on the generated freezed types, which isn't supported. 216 | // This breaks copyWith as it thinks both union cases have `children` as type `List` 217 | copyWith: false, 218 | ) 219 | @freezed 220 | abstract class NestedMap with _$NestedMap { 221 | factory NestedMap.shallow(Map children) = 222 | ShallowNestedMap; 223 | factory NestedMap.deep(Map children) = 224 | DeepNestedMap; 225 | } 226 | 227 | @freezed 228 | abstract class NestedMapItem with _$NestedMapItem { 229 | factory NestedMapItem.leaf() = LeafNestedMapItem; 230 | factory NestedMapItem.inner(Map children) = 231 | InnerNestedMapItem; 232 | } 233 | 234 | @freezed 235 | abstract class ToBeGenerated with _$ToBeGenerated { 236 | const factory ToBeGenerated.generated() = CodeGenerated; 237 | } 238 | 239 | @freezed 240 | abstract class UsesGenerated with _$UsesGenerated { 241 | factory UsesGenerated( 242 | CodeGenerated value, 243 | List list, 244 | List> nestedList, 245 | Map map, 246 | ) = _UsesGenerated; 247 | } 248 | --------------------------------------------------------------------------------