did2dart function

String did2dart(
  1. String filename,
  2. String contents, [
  3. GenOption option = const GenOption()
])

Implementation

String did2dart(
  String filename,
  String contents, [
  GenOption option = const GenOption(),
]) {
  stdout.writeln(
    '[candid_dart] Generate for $filename with options: \n'
    '${option.toJson().entries.map((entry) => '  ${entry.key}: ${entry.value}').join('\n')}',
  );
  filename = filename.replaceAllMapped(RegExp(r'[^\da-zA-Z_.]'), (_) => '_');
  final cdVisitor = PreVisitor();
  cdVisitor.visit(newParser(contents).prog());
  final deps = cdVisitor.deps;
  final idlVisitor = _idlVisitor = IDLVisitor();
  final prog = idlVisitor.visit(newParser(contents).prog()) as ts.Prog;
  final defs = prog.defs;
  final cdSb = StringBuffer();
  final idls = StringBuffer();
  for (final def in defs) {
    final id = '_${def.key.did.noDoubleQuotes}';
    final cd = deps[def.key.did]?.cd ?? false;
    final type = cd ? 'RecClass' : def.type;
    final idlType = cd ? 'IDL.Rec()' : def.idlType;
    idls.writeln('/// [$id] defined in Candid\n${def.doc}');
    if (def.body.child is ts.IdType) {
      idls.writeln('static final $id = $idlType;');
    } else {
      idls.writeln('static final $type $id = $idlType;');
    }
    if (cd) {
      idls.writeln('static final ${def.type} _$id = ${def.idlType};');
      cdSb.writeln('$id.fill(_$id);');
    }
  }
  final actors = StringBuffer();
  final actor = prog.actor;
  String? idlName;
  if (actor != null) {
    final ref = actor.isRef();
    final key = actor.key?.did.noDoubleQuotes;
    idlName = key == null ? 'idl' : '${key}Idl';
    if (ref) {
      actors.writeln(
        'static final $idlName = _${actor.body.did.noDoubleQuotes};',
      );
    } else {
      final cds = cdSb.toString();
      if (cds.isEmpty) {
        actors.writeln(
          'static final ${actor.type} $idlName = ${actor.body.idlType};',
        );
      } else {
        actors.writeln(
          'static final ${actor.type} $idlName = '
          '(){${cds}return ${actor.body.idlType};}();',
        );
      }
    }
    if (key != null) {
      actors.writeln('static final idl = $idlName;');
    }

    final init = actor.init;
    if (init != null && init.children.isNotEmpty) {
      actors.writeln(
        'static final List<CType> \$initIdl = <CType>[${init.idlType}];',
      );
    }
  }
  final split = filename.split('.');
  split.removeLast();
  final clazz = split.join('_').pascalCase;
  final actorMethods = StringBuffer();
  final serviceMethods = StringBuffer();
  final entries = idlVisitor.methods.entries;
  for (final entry in entries) {
    final name = entry.value.name;
    final body = entry.value.body;
    if (body is ts.FuncType) {
      if (!body.args.serializable || !body.ret.serializable) {
        continue;
      }
      final argsType = body.args.dartType();
      final argsSer = body.args.serialize(
        fromIDL: option.explicitSerializationMethods,
      )!;
      final retType = body.ret.dartType();
      final noRet = body.ret.children.isEmpty;
      final noArgs = body.args.children.isEmpty;
      final retDeser = noRet
          ? ''
          : 'return ${body.ret.deserialize(fromIDL: option.explicitSerializationMethods)};';
      final arg = noArgs
          ? ''
          : argsType.endsWith('?')
              ? '[$argsType arg,]'
              : '$argsType arg,';
      String methodName = name.did.noDoubleQuotes.camelCase;
      if (kDartKeywordsAndInternalTypes.contains(methodName)) {
        methodName = '${methodName}_';
      }
      final actorMethod = """
${entry.value.doc}
static Future<$retType> $methodName(CanisterActor actor, $arg) async {
  ${noArgs ? 'const' : 'final'} request = ${argsSer.replaceAll(ts.IDLType.ph, "arg")};
  const method = '${name.did.noDoubleQuotes}';${option.preActorCall?.trim() ?? ''}
  ${noRet && (option.postActorCall == null || option.postActorCall!.isEmpty) ? '' : 'final response ='} await actor.getFunc(method)!(request);${option.postActorCall?.trim() ?? ''}
  ${retDeser.replaceAll(ts.IDLType.ph, "response")}
}
      """;
      actorMethods.writeln(actorMethod);
      final serviceMethod = '''
${entry.value.doc}
Future<$retType> $methodName($arg) async {
  final actor = await getActor();
  return ${clazz}IDLActor.$methodName(actor, ${noArgs ? '' : 'arg,'});
}
      ''';
      serviceMethods.writeln(serviceMethod);
    }
  }
  final sameObjs = idlVisitor.sameObjs;
  final hasObj = idlVisitor.objs.isNotEmpty || idlVisitor.tuples.isNotEmpty;
  final imports = [
    Directive.import('dart:async'),
    Directive.import('package:agent_dart_base/agent_dart_base.dart'),
    ...idlVisitor.pkgs.map(Directive.import),
    if (hasObj) Directive.import('package:meta/meta.dart'),
    if (option.freezed && hasObj)
      Directive.import('package:freezed_annotation/freezed_annotation.dart'),
    ...?option.injectPackages?.map(Directive.import),
  ];
  imports.sort((a, b) => a.url.compareTo(b.url));
  if (option.freezed && hasObj) {
    imports.add(
      Directive.part(
        filename.replaceAll(RegExp(r'.did$'), '.idl.freezed.dart'),
      ),
    );
  }
  final emitter = DartEmitter.scoped();
  final ignoredLintRules = [
    'type=lint',
    'depend_on_referenced_packages',
    'unnecessary_null_comparison',
    'unnecessary_non_null_assertion',
    'unused_field',
    'unused_import',
  ];
  final code = Library(
    (b) => b
      ..comments = ListBuilder([
        'coverage:ignore-file',
        'ignore_for_file: ${ignoredLintRules.join(', ')}',
        '======================================',
        'GENERATED CODE - DO NOT MODIFY BY HAND',
        '======================================',
      ])
      ..directives = ListBuilder(imports)
      ..body.addAll([
        Code(newActor(clazz, actorMethods.toString())),
        if (option.service) Code(newService(clazz, serviceMethods.toString())),
        Code('class ${clazz}IDL{\nconst ${clazz}IDL._();\n$idls\n$actors}'),
        ...idlVisitor.objs.entries.map((e) {
          final className = e.key;
          final type = e.value;
          final isTuple = type is ts.RecordType && type.isTupleValue;
          final Spec clazz;
          if (type.isEnum) {
            clazz = toEnum(className, type, option);
          } else if (option.freezed) {
            clazz = isTuple
                ? toFreezedTupleClass(className, type, option)
                : toFreezedClass(className, type, option);
          } else {
            clazz = isTuple
                ? toTupleClass(className, type, option)
                : toClass(className, type, option);
          }
          if (sameObjs.containsKey(type.did)) {
            final set = sameObjs[type.did]!;
            if (set.length > 1) {
              final ds = <Spec>[clazz];
              for (final value in set) {
                if (value != className) {
                  ds.add(
                    TypeDef(
                      (b) => b
                        ..name = value
                        ..definition = CodeExpression(Code(className))
                        ..docs = ListBuilder(
                          ['/// [$value] defined in Candid', type.doc],
                        ),
                    ),
                  );
                }
              }
              return ds;
            }
          }
          return [clazz];
        }).expand((e) => e),
        ...idlVisitor.tuples.entries.map(
          (e) => option.freezed
              ? toFreezedTupleClass(e.key, e.value, option)
              : toTupleClass(e.key, e.value, option),
        ),
        ...idlVisitor.typedefs.values.map(toTypeDef).whereType<TypeDef>(),
      ]),
  ).accept(emitter).toString();
  return DartFormatter(
    languageVersion: Version.parse(Platform.version.split(' ').first),
    fixes: StyleFix.all,
  ).format(code);
}