toClass function

Spec toClass(
  1. String className,
  2. ObjectType<RuleContext> obj,
  3. GenOption option
)

Implementation

Spec toClass(
  String className,
  ts.ObjectType obj,
  GenOption option,
) {
  final isVariant = obj.isVariant;
  final constructorParameters = <Parameter>[];
  final fromJson = StringBuffer();
  final fromDeserializable = StringBuffer();
  final toJson = StringBuffer();
  final toJsonFields = StringBuffer();
  final toSerializable = StringBuffer();
  final toSerializableFields = StringBuffer();
  final hashes = <String>[];
  final equals = <String>[];
  final fields = <Field>[];
  final copyWithParameters = <Parameter>[];
  final copyWithAssign = StringBuffer();
  for (final e in obj.children) {
    final child = e.child;
    final idlName = child.id!;
    var fieldName = idlName.camelCase;
    if (kDartKeywordsAndInternalTypes.contains(fieldName)) {
      fieldName += '_';
    }
    final isNumberKey = RegExp(r'^\d+$').hasMatch(fieldName);
    if (isNumberKey) {
      fieldName = '\$$fieldName';
    }
    final isIdType = child is ts.IdType;
    String dartType = child.dartType();
    final useBool = (isIdType && isVariant) || dartType == 'null';
    final isOpt = isIdType ||
        (child as ts.PairType).value.child is ts.OptType ||
        isVariant;
    if (isOpt && !dartType.endsWith('?')) {
      dartType += '?';
    }
    final isList = dartType.startsWith('List<');
    if (isList) {
      hashes.add('const DeepCollectionEquality().hash($fieldName)');
      equals.add(
        'const DeepCollectionEquality().equals(other.$fieldName, $fieldName)',
      );
    } else {
      hashes.add(fieldName);
      equals.add(
        '(identical(other.$fieldName, $fieldName) || other.$fieldName == $fieldName)',
      );
    }
    final type = Reference(useBool ? 'bool' : dartType);
    constructorParameters.add(
      Parameter(
        (b) => b
          ..named = true
          ..required = !isOpt
          ..defaultTo = useBool ? const Code('false') : null
          ..name = fieldName
          ..toThis = true,
      ),
    );
    copyWithParameters.add(
      Parameter(
        (b) => b
          ..name = fieldName
          ..named = true
          ..type = Reference(
            useBool
                ? 'bool?'
                : dartType.endsWith('?')
                    ? dartType
                    : '$dartType?',
          ),
      ),
    );
    copyWithAssign.writeln('$fieldName: $fieldName ?? this.$fieldName,');
    fields.add(
      Field(
        (b) => b
          ..name = fieldName
          ..type = type
          ..modifier = FieldModifier.final$
          ..docs = ListBuilder(
            ['/// [$fieldName] defined in Candid: `${child.did}`'],
          ),
      ),
    );
    if (useBool) {
      fromDeserializable.writeln("$fieldName: obj.containsKey('$idlName'),");
      toSerializable.writeln("if ($fieldName) '$idlName': null,");
      if (option.explicitSerializationMethods) {
        fromJson.writeln("$fieldName: json['$idlName'],");
        toJson.writeln("'$idlName': $fieldName,");
      } else {
        fromJson.writeln("$fieldName: json.containsKey('$idlName'),");
        toJson.writeln("if ($fieldName) '$idlName': null,");
      }
    } else {
      final deserJson = child.deserializeAndReplace(
        replace: isNumberKey ? 'json[$idlName]' : "json['$idlName']",
        fromIDL: false,
        nullable: isOpt,
      );
      fromJson.writeln('$fieldName: $deserJson,');
      final deserIDL = child.deserializeAndReplace(
        replace: isNumberKey ? 'obj[$idlName]' : "obj['$idlName']",
        fromIDL: true,
        nullable: isOpt,
      );
      fromDeserializable.writeln('$fieldName: $deserIDL,');

      final ser = child.serialize(
        fromIDL: !option.explicitSerializationMethods,
      );
      final arg =
          ser == null ? fieldName : ser.replaceAll(ts.IDLType.ph, fieldName);
      var isOptChild = false;
      if (child is ts.PairType) {
        final value = child.value.child;
        isOptChild = value is ts.OptType ||
            (value is ts.IdType &&
                value.child is ts.Id &&
                (value.child as ts.Id).isOpt);
      }
      if ((!isVariant && isOptChild) || !isOpt) {
        if (isNumberKey) {
          toSerializable.writeln('$idlName: $arg,');
          toJson.writeln('$idlName: $arg,');
        } else {
          toSerializable.writeln("'$idlName': $arg,");
          toJson.writeln("'$idlName': $arg,");
        }
      } else {
        if (isNumberKey) {
          toSerializable.writeln('if ($fieldName != null) $idlName: $arg,');
          toJson.writeln('if ($fieldName != null) $idlName: $arg,');
        } else {
          toSerializable.writeln("if ($fieldName != null) '$idlName': $arg,");
          toJson.writeln("if ($fieldName != null) '$idlName': $arg,");
        }
      }
    }
    toSerializableFields.writeln('final $fieldName = this.$fieldName;');
    final toJsonField = _typeToJsonField(option, obj, e, fieldName);
    toJsonFields.writeln('final $fieldName = this.$toJsonField;');
  }
  return Class(
    (b) => b
      ..name = className
      ..annotations = ListBuilder([const CodeExpression(Code('immutable'))])
      ..constructors = ListBuilder([
        Constructor(
          (b) => b
            ..optionalParameters = ListBuilder(constructorParameters)
            ..constant = true,
        ),
        if (option.explicitSerializationMethods)
          Constructor(
            (b) => b
              ..docs = ListBuilder([
                '/// An extra method for the deserialization with `packages:agent_dart`.',
              ])
              ..name = 'fromIDLDeserializable'
              ..factory = true
              ..body =
                  Code('return ${obj.ctx.getClassName()}($fromDeserializable);')
              ..requiredParameters = ListBuilder([
                Parameter(
                  (b) => b
                    ..type = const Reference('Map')
                    ..name = 'obj',
                ),
              ]),
          ),
        Constructor(
          (b) => b
            ..name = 'fromJson'
            ..factory = true
            ..body = Code('return ${obj.ctx.getClassName()}($fromJson);')
            ..requiredParameters = ListBuilder([
              Parameter(
                (b) => b
                  ..type = const Reference('Map')
                  ..name = 'json',
              ),
            ]),
        ),
      ])
      ..fields = ListBuilder(fields)
      ..methods = ListBuilder([
        if (option.explicitSerializationMethods)
          Method(
            (b) => b
              ..docs = ListBuilder([
                '/// An extra method for the serialization with `packages:agent_dart`.',
              ])
              ..name = 'toIDLSerializable'
              ..body =
                  Code('${toSerializableFields}return { $toSerializable };')
              ..returns = const Reference('Map<String, dynamic>'),
          ),
        Method(
          (b) => b
            ..name = 'toJson'
            ..body = Code('${toJsonFields}return { $toJson };')
            ..returns = Reference(
              '${option.explicitSerializationMethods ? '' : '\n'}'
              'Map<String, dynamic>',
            ),
        ),
        if (option.copyWith)
          Method(
            (b) => b
              ..name = 'copyWith'
              ..optionalParameters = ListBuilder(copyWithParameters)
              ..body = Code('return $className($copyWithAssign);')
              ..returns = Reference(className),
          ),
        if (option.equal) ...[
          Method(
            (b) => b
              ..name = 'operator =='
              ..returns = const Reference('bool')
              ..requiredParameters = ListBuilder([
                Parameter(
                  (b) => b
                    ..name = 'other'
                    ..type = const Reference('Object'),
                ),
              ])
              ..annotations =
                  ListBuilder([const CodeExpression(Code('override'))])
              ..body = Code(
                'return identical(this, other) || (other.runtimeType == runtimeType && other is $className ${fields.isEmpty ? '' : '&&'} ${equals.join("&&")});',
              ),
          ),
          Method(
            (b) => b
              ..type = MethodType.getter
              ..name = 'hashCode'
              ..returns = const Reference('int')
              ..lambda = true
              ..annotations =
                  ListBuilder([const CodeExpression(Code('override'))])
              ..body =
                  Code('Object.hashAll([runtimeType,${hashes.join(",")}])'),
          ),
        ],
        toStringMethod,
      ])
      ..docs = ListBuilder(['/// [$className] defined in Candid', obj.doc]),
  );
}