Created
July 22, 2019 16:17
-
-
Save devoncarew/27e3cc37c90023708ce23d9be4218b01 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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