did2dart function
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);
}