Skip to content

Instantly share code, notes, and snippets.

@devoncarew
Created July 22, 2019 16:17
Show Gist options
  • Select an option

  • Save devoncarew/27e3cc37c90023708ce23d9be4218b01 to your computer and use it in GitHub Desktop.

Select an option

Save devoncarew/27e3cc37c90023708ce23d9be4218b01 to your computer and use it in GitHub Desktop.
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:cli_util/cli_logging.dart';
import 'package:path/path.dart' as path;
Logger logger;
void main(List<String> args) async {
if (args.length != 1) {
fail('usage: dart bin/widgets.dart <path-to-flutter-sdk>');
}
final String flutterSdkPath = args.first;
final String flutterPackagePath =
path.join(flutterSdkPath, 'packages/flutter/lib');
logger = Logger.standard();
Progress progress = logger.progress('Setting up analysis context');
List<String> includedPaths = <String>[flutterPackagePath];
AnalysisContextCollection collection =
new AnalysisContextCollection(includedPaths: includedPaths);
if (collection.contexts.length != 1) {
progress.finish();
fail('expected one analysis context, found ${collection.contexts.length}');
}
AnalysisContext context = collection.contexts.first;
AnalysisSession session = context.currentSession;
// Future<ErrorsResult> getErrors(String path);
List<String> files = context.contextRoot.analyzedFiles().toList();
progress.finish(showTiming: true);
progress = logger.progress("Resolving class 'Widget'");
final LibraryElement widgetsLibrary = await session
.getLibraryByUri('package:flutter/src/widgets/framework.dart');
//print(widgetsLibrary.source.uri);
final ClassElement widgetClass = widgetsLibrary.getType('Widget');
//print('$widgetClass {}\n');
progress.finish(showTiming: true);
progress = logger.progress('Scanning Dart files');
List<String> libraryFiles = [];
for (String file in files) {
SourceKind kind = await session.getSourceKind(file);
if (kind == SourceKind.LIBRARY) {
libraryFiles.add(file);
}
}
progress.finish(message: '${libraryFiles.length} dart files');
progress = logger.progress('Resolving widget classes');
List<ClassElement> classes = [];
for (String file in libraryFiles) {
ResolvedLibraryResult resolvedLibraryResult =
await session.getResolvedLibrary(file);
LibraryElement lib = resolvedLibraryResult.element;
for (Element element in lib.topLevelElements) {
if (element is! ClassElement) {
continue;
}
final ClassElement clazz = element;
if (clazz.allSupertypes.contains(widgetClass.type)) {
// hide private classes
final String name = clazz.name;
if (!name.startsWith('_')) {
classes.add(clazz);
}
}
}
}
progress.finish(message: '${classes.length} widgets');
// Normalize the output json.
classes.sort((ClassElement a, ClassElement b) => a.name.compareTo(b.name));
progress = logger.progress('Generating json');
List json = [];
for (ClassElement c in classes) {
json.add(_convertToJson(c, widgetClass));
}
JsonEncoder encoder = new JsonEncoder.withIndent(' ');
String output = encoder.convert(json);
File file = new File('widgets.json');
file.writeAsStringSync(output);
final int kb = (file.lengthSync() + 1023) ~/ 1024;
progress.finish(message: '${kb}kb');
logger.stdout('\nwrote ${file.path}');
}
Map _convertToJson(ClassElement c, ClassElement widgetClass) {
//{
// "name": "Container",
// "categories": ["Basics"],
// "subcategories": ["Single-child layout widgets"],
// "description": "A convenience widget that combines common painting, positioning, and sizing widgets.",
// "link": "https://api.flutter.dev/flutter/widgets/Container-class.html",
// "image": "..."
//}
// todo: properties
// todo: children?
// todo: stateful? stateless?
// todo: show info about the class's parameterization?
// flutter/src/material/about.dart
final String filePath = c.library.librarySource.uri.path;
final String libraryName = filePath.split('/')[2];
String summary;
ElementAnnotation summaryAnnotation =
_getAnnotations(c, 'Summary').firstWhere((a) => true, orElse: () => null);
if (summaryAnnotation != null) {
DartObject o = summaryAnnotation.computeConstantValue();
DartObject text = o.getField('text');
summary = text.toStringValue().trim();
}
List<String> categories = [];
for (ElementAnnotation a in _getAnnotations(c, 'Category')) {
DartObject o = a.computeConstantValue();
DartObject value = o.getField('value');
categories.add(value.toStringValue());
}
categories.sort();
List<String> subcategories = [];
for (ElementAnnotation a in _getAnnotations(c, 'Subcategory')) {
DartObject o = a.computeConstantValue();
DartObject value = o.getField('value');
subcategories.add(value.toStringValue());
}
subcategories.sort();
// use annotations from
// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/foundation/annotations.dart
// ?
final Map m = LinkedHashMap();
m['name'] = c.name;
m['description'] = summary ?? _singleLine(c.documentationComment);
m['categories'] = categories;
if (subcategories.isNotEmpty) {
m['subcategories'] = subcategories;
}
m['library'] = libraryName;
if (c.isAbstract) {
m['abstract'] = true;
}
if (c != widgetClass) {
m['parent'] = c.supertype.name;
}
// todo: likely not necessary - his can be computed from library and class name.
//m['link'] =
// 'https://api.flutter.dev/flutter/$libraryName/${c.name}-class.html';
// todo: this info likely shouldn't be associated with the source code, but in
// a lookup table in the website repo
//m['image'] = c.name;
return m;
}
List<ElementAnnotation> _getAnnotations(ClassElement c, String name) {
return c.metadata.where((ElementAnnotation a) {
if (a.element is ConstructorElement) {
return a.element.enclosingElement.name == name;
} else {
return false;
}
}).toList();
}
String _singleLine(String docs) {
if (docs == null) {
return '';
}
return docs
.split('\n')
.map((String line) {
return line.startsWith('/// ')
? line.substring(4)
: line == '///' ? '' : line;
})
.map((line) => line.trimRight())
.takeWhile((line) => line.isNotEmpty)
.join(' ');
}
void fail(String message) {
print(message);
exit(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment