generateModel<T extends Object?> method

void generateModel<T extends Object?>(
  1. StringBuffer buffer,
  2. ClassModel model
)

Generate models for the data classes file.

Implementation

void generateModel<T extends Object?>(
  final StringBuffer buffer,
  final ClassModel model,
) {
  final String comparable = model.fields.any((final _) => _.compare)
      ? ' implements Comparable<${model.name}>'
      : '';
  final String doc =
      model.doc == null ? 'The model of a `${model.key}`.' : model.doc!;
  final bool isConst = model.fields.every(
    (final _) =>
        (_.type != FieldType.$datetime && _.type != FieldType.$$datetime) ||
        _.$default == null,
  );
  buffer
    ..writeDoc(doc, indent: 2)
    ..writeln('@sealed')
    ..writeln('@immutable')
    ..writeln('class ${model.name}$comparable {')

    /// `constructor`
    ..writeDoc(doc, indent: 4)
    ..writeFunction(
      '${isConst ? 'const ' : ''}${model.name}',
      bracketFields: <String>[
        for (final FieldModel field in model.fields)
          (final FieldModel field) {
            final String $default = _renderDefault(model, field);
            final String $field = field.required ? 'required ' : '';
            return $field +
                ($default.isNotEmpty
                    ? 'final '
                        '${_renderType(model, field, nullable: true)} '
                        '${field.name}'
                    : 'this.${field.name}');
          }(field)
      ]..sort(
          (final String a, final String b) =>
              (b.startsWith('required') ? 1 : -1)
                  .compareTo(a.startsWith('required') ? 1 : -1),
        ),
      outerFields: <String?>[
        for (final FieldModel field in model.fields)
          (final String $default) {
            if ($default.isNotEmpty) {
              return '${field.name} = ${field.name} ?? ${$default}';
            } else {
              return null;
            }
          }(_renderDefault(model, field))
      ].whereType(),
    )
    ..writeln();

  /// `keys`
  if (model.staticKeys) {
    buffer
      ..writeDoc('The key of this [${model.name}].', indent: 2)
      ..writeln("static const String \$ = '${model.key}';");
    for (final FieldModel field in model.fields) {
      buffer
        ..writeDoc(
          'The key of `[${field.name}]` property of this [${model.name}].',
          indent: 2,
        )
        ..writeln("static const String \$${field.name} = '${field.key}';");
    }
  }

  /// `fields`
  for (final FieldModel field in model.fields) {
    buffer
      ..writeDoc(
        field.doc == null
            ? 'The `${field.key}` property of this [${model.name}].'
            : field.doc!,
        indent: 2,
      )
      ..writeln('final ${_renderType(model, field)} ${field.name};');
  }

  /// `copyWith`
  if (model.fields.any((final _) => _.copy)) {
    buffer
      ..writeDoc('Return the copy of this model.', indent: 2)
      ..writeFunction(
        '${model.name} copyWith',
        bracketFields: <String>[
          for (final FieldModel field in model.fields)
            if (field.copy)
              '''final ${_renderType(model, field, nullable: true)} ${field.name}'''
        ],
        bodyConstructor: model.name,
        bodyFields: <String>[
          for (final FieldModel field in model.fields)
            if (field.copy)
              '${field.name}: ${field.name} ?? this.${field.name}'
            else if (field.required)
              '${field.name}: ${field.name}'
        ],
      );
  }

  /// `copyWithNull`
  if (model.fields.any((final _) => _.nullable && _.copy)) {
    buffer
      ..writeDoc(
        'Return the copy of this model with nullable fields.',
        indent: 2,
      )
      ..writeFunction(
        '${model.name} copyWithNull',
        bracketFields: <String>[
          for (final FieldModel field in model.fields)
            if (field.nullable && field.copy)
              'final bool ${field.name} = false'
        ],
        bodyConstructor: model.name,
        bodyFields: <String>[
          for (final FieldModel field in model.fields)
            if (field.nullable && field.copy)
              '${field.name}: ${field.name} ? null : this.${field.name}'
            else
              '${field.name}: ${field.name}'
        ],
      );
  }

  buffer

    /// `toMap`
    ..writeDoc(
      'Convert this model to map with string keys.',
      indent: 2,
    )
    ..writeFunction(
      'Map<String, Object?> toMap',
      bodyConstructor: '<String, Object?>{',
      bodyFields: <String>[
        for (final FieldModel field in model.fields)
          if (field.serialize != FieldSerialization.none)
            (final FieldModel field) {
              final String key =
                  model.staticKeys ? field.staticKey : "'${field.key}'";
              final String $field =
                  '$key: ${renderSerialization(model, field)}';
              return field.serialize == FieldSerialization.nonNull &&
                      field.nullable
                  ? 'if (${field.name} != null) ${$field}'
                  : $field;
            }(field)
      ],
    )

    /// `fromMap`
    ..writeDoc('Convert the map with string keys to this model.', indent: 2)
    ..writeFunction(
      'factory ${model.name}.fromMap',
      fields: <String>['final Map<String, Object?> map'],
      bodyConstructor: (model.fields.isEmpty ? 'const ' : '') + model.name,
      bodyFields: <String>[
        for (final FieldModel field in model.fields)
          if (field.deserialize || field.required)
            '${field.name}: ${renderDeserialization(model, field)}'
      ],
    );

  if (model.toJson) {
    buffer

      /// `toJson`
      ..writeDoc('Convert this model to a json string.', indent: 2)
      ..writeFunction(
        'String toJson',
        bodyConstructor: 'json.encode',
        bodyFields: <String>['toMap()'],
      )

      /// `fromJson`
      ..writeDoc('Convert the json string to this model.', indent: 2)
      ..writeFunction(
        'factory ${model.name}.fromJson',
        fields: <String>['final String source'],
        bodyConstructor: '${model.name}.fromMap',
        bodyFields: <String>['json.decode(source)! as Map<String, Object?>'],
      );
  }

  /// `compareTo`
  final List<String> compareFields = <String>[
    for (final FieldModel field in model.fields)
      if (field.compare)
        if (field.nullable)
          '${field.name} != null && other.${field.name} != null ? '
              '${field.name}!.compareTo(other.${field.name}!) : 0'
        else
          '${field.name}.compareTo(other.${field.name})'
  ];
  if (compareFields.isEmpty) {
  } else if (compareFields.length == 1) {
    buffer
      ..writeln()
      ..writeln('@override')
      ..writeFunction(
        'int compareTo',
        fields: <String>['final ${model.name} other'],
        bodyFields: compareFields,
        separator: '',
      );
  } else {
    buffer
      ..writeln()
      ..writeln('@override')
      ..writeln('int compareTo(final ${model.name} other) {')
      ..writeln('int value;');
    for (int index = 0; index < compareFields.length; index++) {
      if (index > 0) {
        buffer.write(' else ');
      }
      buffer.writeln(
        'if ((value = ${compareFields.elementAt(index)}) != 0) {}',
      );
    }
    buffer
      ..writeln('return value;')
      ..writeln('}');
  }

  /// `equality`
  if (model.fields.any((final _) => _.equality != FieldEquality.none)) {
    buffer
      ..writeln()

      /// `==` operator
      ..writeln('@override')
      ..writeFunction(
        'bool operator ==',
        fields: <String>['final Object? other'],
        bodyFields: <String>[
          'identical(this, other) ||other is ${model.name}',
          for (final FieldModel field in model.fields)
            if (field.equality != FieldEquality.none)
              if (field.type.name.startsWith(r'$$'))
                (final FieldModel field) {
                  final String equality =
                      field.equality == FieldEquality.ordered
                          ? 'IterableEquality'
                          : 'UnorderedIterableEquality';
                  return 'const $equality'
                      '<${_renderType(model, field, iterable: false)}>()'
                      '.equals(other.${field.name}, ${field.name},)';
                }(field)
              else
                'other.${field.name} == ${field.name}'
        ],
        separator: ' && ',
      )
      ..writeln()

      /// `hashCode`
      ..writeln('@override')
      ..writeFunction(
        'int get hashCode',
        bodyFields: <String>[
          for (final FieldModel field in model.fields)
            if (field.equality != FieldEquality.none)
              (final FieldModel field) {
                if (!field.type.isIterable) {
                  return '${field.name}.hashCode';
                }
                final bool needsParanthesis = field.nullable &&
                    model.fields
                            .where(
                              (final FieldModel field) =>
                                  field.equality != FieldEquality.none,
                            )
                            .length >
                        1;
                String hash = field.equality == FieldEquality.unordered
                    ? 'Object.hashAllUnordered'
                    : 'Object.hashAll';
                hash = field.nullable
                    ? '${field.name} == null ? ${field.name}.hashCode : '
                        '$hash(${field.name}!)'
                    : '$hash(${field.name})';
                return needsParanthesis ? '($hash)' : hash;
              }(field)
        ],
        separator: ' ^ ',
      );
  }

  /// `toString`
  buffer
    ..writeln()
    ..writeln('@override')
    ..writeFunction(
      'String toString',
      bodyConstructor:
          "'${model.name.startsWith(r'$') ? r'\' : ''}${model.name}(",
      bodyFields: <String>[
        for (final FieldModel field in model.fields)
          if (field.$toString) '${field.name}: \$${field.name}'
      ],
    )
    ..writeln('}');
}