From 7a93ebc833abbce9fb65600fe1ea0c02404dd3df Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 29 Mar 2018 12:14:22 -0400 Subject: [PATCH] TypeScript def builder --- angel_serialize_generator/build.yaml | 16 ++ .../lib/angel_serialize_generator.dart | 8 +- angel_serialize_generator/lib/typescript.dart | 148 ++++++++++++++++++ angel_serialize_generator/pubspec.yaml | 1 + 4 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 angel_serialize_generator/lib/typescript.dart diff --git a/angel_serialize_generator/build.yaml b/angel_serialize_generator/build.yaml index 52e83344..440f73cc 100644 --- a/angel_serialize_generator/build.yaml +++ b/angel_serialize_generator/build.yaml @@ -17,6 +17,22 @@ builders: generate_for: - "lib/src/models/**.dart" - "test/**.dart" + typescript_definition: + target: "angel_serialize_generator" + import: "package:angel_serialize_generator/angel_serialize_generator.dart" + builder_factories: + - typescriptDefinitionBuilder + auto_apply: root_package + build_to: source + build_extensions: + .dart: + - ".d.ts" + required_inputs: + - .dart + defaults: + generate_for: + - "lib/src/models/**.dart" + - "test/**.dart" targets: _book: sources: diff --git a/angel_serialize_generator/lib/angel_serialize_generator.dart b/angel_serialize_generator/lib/angel_serialize_generator.dart index 66b99994..35021637 100644 --- a/angel_serialize_generator/lib/angel_serialize_generator.dart +++ b/angel_serialize_generator/lib/angel_serialize_generator.dart @@ -5,6 +5,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:angel_serialize/angel_serialize.dart'; import 'package:build/build.dart'; +import 'package:code_buffer/code_buffer.dart'; import 'package:code_builder/code_builder.dart'; import 'package:recase/recase.dart'; import 'package:source_gen/source_gen.dart' hide LibraryBuilder; @@ -15,6 +16,8 @@ part 'model.dart'; part 'serialize.dart'; +part 'typescript.dart'; + Builder jsonModelBuilder(_) { return new PartBuilder( const [const JsonModelGenerator()], @@ -29,6 +32,10 @@ Builder serializerBuilder(_) { ); } +Builder typescriptDefinitionBuilder(_) { + return const TypeScriptDefinitionBuilder(); +} + /// Converts a [DartType] to a [TypeReference]. TypeReference convertTypeReference(DartType t) { return new TypeReference((b) { @@ -60,7 +67,6 @@ bool isListModelType(InterfaceType t) { isModelClass(t.typeArguments[0]); } - /// Determines if a [DartType] is a `Map` with the second type argument being a `Model`. bool isMapToModelType(InterfaceType t) { return t.name == 'Map' && diff --git a/angel_serialize_generator/lib/typescript.dart b/angel_serialize_generator/lib/typescript.dart new file mode 100644 index 00000000..ca4a3b76 --- /dev/null +++ b/angel_serialize_generator/lib/typescript.dart @@ -0,0 +1,148 @@ +part of angel_serialize_generator; + +class TypeScriptDefinitionBuilder implements Builder { + final bool autoSnakeCaseNames; + + const TypeScriptDefinitionBuilder({this.autoSnakeCaseNames: true}); + + @override + Map> get buildExtensions { + return { + '.dart': ['.d.ts'] + }; + } + + Future compileToTypeScriptType(String fieldName, InterfaceType type, + List ext, BuildStep buildStep) async { + String typeScriptType = 'any'; + + var types = const { + num: 'number', + bool: 'boolean', + String: 'string', + Symbol: 'Symbol', + }; + + types.forEach((t, tsType) { + if (new TypeChecker.fromRuntime(t).isAssignableFromType(type)) + typeScriptType = tsType; + }); + + if (isListModelType(type)) { + var arg = await compileToTypeScriptType( + fieldName, type.typeArguments[0], ext, buildStep); + typeScriptType = '$arg[]'; + } else if (isMapToModelType(type)) { + var key = await compileToTypeScriptType( + fieldName, type.typeArguments[0], ext, buildStep); + var value = await compileToTypeScriptType( + fieldName, type.typeArguments[1], ext, buildStep); + var modelType = type.typeArguments[1]; + var ctx = await buildContext( + modelType.element, + new ConstantReader( + serializableTypeChecker.firstAnnotationOf(modelType.element)), + buildStep, + buildStep.resolver, + autoSnakeCaseNames, + true, + ); + + typeScriptType = ctx.modelClassNameRecase.pascalCase + + new ReCase(fieldName).pascalCase; + + ext.add(new CodeBuffer() + ..writeln('interface $typeScriptType {') + ..indent() + ..writeln('[key: $key]: $value;') + ..outdent() + ..writeln('}')); + } else if (const TypeChecker.fromRuntime(List).isAssignableFromType(type)) { + typeScriptType = 'any[]'; + } else if (isModelClass(type)) { + var ctx = await buildContext( + type.element, + new ConstantReader( + serializableTypeChecker.firstAnnotationOf(type.element)), + buildStep, + buildStep.resolver, + autoSnakeCaseNames, + true, + ); + typeScriptType = ctx.modelClassNameRecase.pascalCase; + } + + return typeScriptType; + } + + @override + Future build(BuildStep buildStep) async { + var contexts = []; + var lib = new LibraryReader(await buildStep.inputLibrary); + var elements = + lib.annotatedWith(const TypeChecker.fromRuntime(Serializable)); + + for (var element in elements) { + if (element.element.kind != ElementKind.CLASS) + throw 'Only classes can be annotated with a @Serializable() annotation.'; + + contexts.add(await buildContext( + element.element, + element.annotation, + buildStep, + await buildStep.resolver, + true, + autoSnakeCaseNames != false)); + } + + if (contexts.isEmpty) return; + + var buf = new CodeBuffer( + trailingNewline: true, + sourceUrl: buildStep.inputId.uri, + ); + + // declare module `foo` { + //buf + // ..writeln("declare module '${buildStep.inputId.package}' {") + // ..indent(); + + for (var ctx in contexts) { + // interface Bar { ... } + buf + ..writeln('interface ${ctx.modelClassNameRecase.pascalCase} {') + ..indent(); + + var ext = []; + + for (var field in ctx.fields) { + // Skip excluded fields + if (ctx.excluded[field.name]?.canSerialize == false) continue; + + var alias = ctx.resolveFieldName(field.name); + var typeScriptType = await compileToTypeScriptType( + field.name, field.type, ext, buildStep); + + // foo: string; + buf.writeln('$alias: $typeScriptType;'); + } + + buf + ..outdent() + ..writeln('}'); + + for (var b in ext) { + b.copyInto(buf); + } + } + + //buf + // ..outdent() + // ..writeln('}'); + + buildStep.writeAsString( + buildStep.inputId.changeExtension('.d.ts'), + buf.toString(), + ); + } +} diff --git a/angel_serialize_generator/pubspec.yaml b/angel_serialize_generator/pubspec.yaml index d597606a..b4da0290 100644 --- a/angel_serialize_generator/pubspec.yaml +++ b/angel_serialize_generator/pubspec.yaml @@ -8,6 +8,7 @@ environment: dependencies: angel_serialize: ^2.0.0-alpha build_config: ">=0.2.4 <2.0.0" + code_buffer: ^1.0.0 code_builder: ^3.0.0 id: ^1.0.0 recase: ^1.0.0