From b7728ac5afbf9fd829585cd2765c9f0b14afb1e0 Mon Sep 17 00:00:00 2001 From: Alfonso Garcia-Caro Date: Mon, 30 May 2022 15:20:45 +0900 Subject: [PATCH] Generate bindings for Material widgets --- README.md | 16 +++++ lib/fsgen.dart | 163 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 122 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index a876577..2392246 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ A sample command-line application with an entrypoint in `bin/`, library code in `lib/`, and example unit test in `test/`. # DartToFableBindings + + +## Example + +From the package dir you want to generate your bindings for: + +``` +dart run ../../../DartToFableBindings/bin/fsgen.dart \ + --exclude 'dart:async,dart:collection,dart:convert,dart:core,dart:developer,dart:io,dart:isolate,dart:math,dart:typed_data,dart,dart:ffi,dart:html,dart:js,dart:ui,dart:js_util' \ + --show-progress \ + --no-auto-include-dependencies \ + --no-validate-links \ + --no-verbose-warnings \ + --no-allow-non-local-warnings \ + --no-allow-tools +``` \ No newline at end of file diff --git a/lib/fsgen.dart b/lib/fsgen.dart index 6e0fcb5..74bb0e7 100644 --- a/lib/fsgen.dart +++ b/lib/fsgen.dart @@ -1,6 +1,6 @@ import 'package:dartdoc/dartdoc.dart'; import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/src/dart/element/element.dart'; +// import 'package:analyzer/src/dart/element/element.dart'; class Dependency { final Uri libraryUri; @@ -25,6 +25,12 @@ class Dependency { } } +class RenderParamsResult { + final String rendered; + final int namedIndex; + RenderParamsResult(this.rendered, [this.namedIndex = -1]); +} + class FsGenerator implements Generator { Dependency? dependency( LibraryElement? elementType, PackageGraph packageGraph) { @@ -34,18 +40,35 @@ class FsGenerator implements Generator { return Dependency(elementType.source.uri, metadata!.name, metadata.version); } - String extractNamedParams(Iterable parameters) { - var positionalUntil = -1; - for (final p in parameters) { - positionalUntil += 1; - if (p.isNamed) { - break; - } - } - return positionalUntil != -1 && positionalUntil != parameters.length - 1? '[] ' : ''; + String isConst(bool isConst) { + return isConst ? '[] ' : ''; } - String renderParams(Iterable parameters) { - return '(${parameters.isEmpty ? '' : parameters.map((e) => '${e.isRequiredNamed || e.isRequiredPositional ? '' : '?'}${e.name} : ${e is TypeParameterElementType ? "'":''}${e.type.element?.name ?? e.type.alias?.element.name ?? e.type.getDisplayString(withNullability: true)}').reduce((value, element) => '$value, $element')})'; + + String paramAttributes(bool isConst, int namedIndex) { + final a = isConst ? "[] " : ""; + final b = namedIndex >= 0 ? '[] ' : ''; + return a + b; + } + + RenderParamsResult renderParams(List parameters) { + String renderParam(ParameterElement e) { + return '${e.isOptional ? "?" : ""}${e.name} : ${e is TypeParameterElementType ? "'":''}${e.type.element?.name ?? e.type.alias?.element.name ?? e.type.getDisplayString(withNullability: true)}'; + } + + final parameterEls = parameters.map((e) => e.element!).toList(); + // If there are optional positional params there should be no named params + if (parameterEls.any((element) => element.isOptionalPositional)) { + final required = parameterEls.where((e) => e.isRequiredPositional).map(renderParam); + final optional = parameterEls.where((e) => e.isOptionalPositional).map(renderParam); + return RenderParamsResult(required.followedBy(optional).join(", ")); + } + else { + final positional = parameterEls.where((e) => e.isPositional).map(renderParam).toList(); + final namedRequired = parameterEls.where((e) => e.isRequiredNamed).map(renderParam).toList(); + final namedOptional = parameterEls.where((e) => e.isOptionalNamed).map(renderParam).toList(); + final namedIndex = namedRequired.isNotEmpty || namedOptional.isNotEmpty ? positional.length : -1; + return RenderParamsResult(positional.followedBy(namedRequired).followedBy(namedOptional).join(", "), namedIndex); + } } String renderGenericArgs(Iterable genericParams) => genericParams @@ -55,75 +78,101 @@ class FsGenerator implements Generator { @override Future generate(PackageGraph packageGraph, FileWriter writer) async { - for (final package in packageGraph.packages) { + // print("Default: ${packageGraph.defaultPackageName}"); + + final packages = [packageGraph.defaultPackage]; //packageGraph.packages + final moduleName = "Flutter.Material"; + final modulePath = "package:flutter/material.dart"; + final fileFilter = "src/material"; + final superClassFilter = "src/material"; + final printAtEnd = " interface Widget"; + + for (final package in packages) { + var buffer = StringBuffer(); + buffer.writeln('module rec $moduleName'); + buffer.writeln(); + buffer.writeln('let [] private PATH = "$modulePath"'); + buffer.writeln(); for (final lib in package.libraries) { - if (!lib.isPublic) continue; - buffer.writeln('module rec ``${lib.name}``='); - final currentLibUri = lib.element.source.uri; - final dependencies = Set.identity(); - for (final libEl in lib.element.importedLibraries) { - if (libEl.isPrivate) continue; - final dep = dependency(libEl, packageGraph); - if (dep != null && dep.libraryUri != currentLibUri) { - dependencies.add(dep); - } - } + if (!lib.isPublic || !lib.sourceFileName.contains(fileFilter) ) continue; + + // final currentLibUri = lib.element.source.uri; + // final dependencies = Set.identity(); + // for (final libEl in lib.element.importedLibraries) { + // if (libEl.isPrivate) continue; + // final dep = dependency(libEl, packageGraph); + // if (dep != null && dep.libraryUri != currentLibUri) { + // dependencies.add(dep); + // } + // } + for (final clazz in lib.classes) { if (!clazz.isCanonical) continue; + + final superClazz = (clazz.supertype?.isPublic ?? false) ? clazz.supertype : null; + if (superClazz == null || superClazz.name != superClassFilter) continue; + final genericParams = clazz.typeParameters; - final renderedClassGenerics = - renderGenericArgs(genericParams.map((e) => e.element!)); + final renderedClassGenerics = renderGenericArgs(genericParams.map((e) => e.element!)); var headerPrinted = false; var moreThanHeader = false; - var defaultConstructor = clazz.unnamedConstructor; - final superClazz = (clazz.supertype?.isPublic ?? false) ? clazz.supertype : null; - final superGenerics = renderGenericArgs( - superClazz?.typeArguments.where((e) => e.type.element != null) - .map((e) => e.type.element!) ?? []); - buffer.writeln(' ['); - if (defaultConstructor != null) { + + buffer.writeln('/// https://api.flutter.dev/flutter/material/${clazz.name}-class.html'); + buffer.writeln('['); + + var defCons = clazz.unnamedConstructor; + if (defCons != null) { headerPrinted = true; - final renderedParams = renderParams(defaultConstructor.parameters.map((e) => e.element!)); + final renderedParams = renderParams(defCons.parameters); buffer.writeln( - ' type ${clazz.name}$renderedClassGenerics ${extractNamedParams(defaultConstructor.parameters.map((e) => e.element!))} $renderedParams ='); - } - if (superClazz != null) { - moreThanHeader = true; - final args = ((defaultConstructor?.element as ConstructorElementImpl?)?.superConstructor?.parameters.length ?? 0); - buffer.writeln(' inherit ${superClazz.name}$superGenerics(${args == 0 ? '' : List.generate(args, (_) => 'jsNative') - .reduce((value, element) => '$value, $element')})'); + 'type ${clazz.name}$renderedClassGenerics ${paramAttributes(defCons.isConst, renderedParams.namedIndex)}(${renderedParams.rendered}) ='); } + moreThanHeader = true; + // if (superClazz != null) { + // moreThanHeader = true; + // final superGenerics = renderGenericArgs( + // superClazz?.typeArguments.where((e) => e.type.element != null) + // .map((e) => e.type.element!) ?? []); + // final args = ((defaultConstructor?.element as ConstructorElementImpl?)?.superConstructor?.parameters.length ?? 0); + // buffer.writeln(' inherit ${superClazz.name}$superGenerics(${args == 0 ? '' : List.generate(args, (_) => 'nativeOnly') + // .reduce((value, element) => '$value, $element')})'); + // } + if (!headerPrinted) { headerPrinted = true; - buffer.writeln(' type ${clazz.name}$renderedClassGenerics ='); + buffer.writeln('type ${clazz.name}$renderedClassGenerics ='); } - for (final constructor in clazz.constructors - .where((element) => !element.isUnnamedConstructor)) { - if (!constructor.isCanonical) continue; - final renderedParams = renderParams(constructor.parameters.map((e) => e.element!)); + + final namedConstructors = clazz.constructors.where((element) => !element.isUnnamedConstructor && element.isCanonical); + for (final cons in namedConstructors) { + final renderedParams = renderParams(cons.parameters); moreThanHeader = true; - final treated = constructor.name.substring(clazz.name.length + 1); + final treated = cons.name.substring(clazz.name.length + 1); buffer.writeln( - ' ${extractNamedParams(constructor.parameters.map((e) => e.element!))}${treated != 'new' ? 'static member' : ''} $treated$renderedParams : ${clazz.name}$renderedClassGenerics = jsNative'); - //print('SDK? ${lib.isInSdk} - $package - ${package.packageMeta.version} - ${lib.element.source.uri} - $lib - $clazz - ${clazz.supertype ?? 'No super'} - $constructor'); + ' ${paramAttributes(cons.isConst, renderedParams.namedIndex)}static member $treated(${renderedParams.rendered}): ${clazz.name}$renderedClassGenerics = nativeOnly' + ); } - if (!moreThanHeader) { - buffer.writeln(' class end'); + if (printAtEnd != null) { + buffer.writeln(' interface Widget'); + + } else if (!moreThanHeader) { + buffer.writeln(' class end'); } buffer.writeln(); } - writer.write('${lib.hashCode}.fs', buffer.toString()); + // print('$lib dependencies:'); + // for (final d in dependencies) { + // print(d); + // } - print('$lib dependencies:'); - for (final d in dependencies) { - print(d); - } - } + } // for (final lib in package.libraries) { + + writer.write('$moduleName.fs', buffer.toString()); } } }