Index: pkg/analysis_server/tool/spec/codegen_dart_protocol.dart |
diff --git a/pkg/analysis_server/tool/spec/codegen_dart_protocol.dart b/pkg/analysis_server/tool/spec/codegen_dart_protocol.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ba54b62e679680f9de3a222f4a0ed91ea4970486 |
--- /dev/null |
+++ b/pkg/analysis_server/tool/spec/codegen_dart_protocol.dart |
@@ -0,0 +1,821 @@ |
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library codegen.protocol; |
+ |
+import 'dart:convert'; |
+ |
+import 'api.dart'; |
+import 'codegen_tools.dart'; |
+import 'from_html.dart'; |
+import 'implied_types.dart'; |
+import 'to_html.dart'; |
+ |
+import 'package:html5lib/dom.dart' as dom; |
+ |
+/** |
+ * Container for code that can be used to translate a data type from JSON. |
+ */ |
+abstract class FromJsonCode { |
+ /** |
+ * True if the data type is already in JSON form, so the translation is the |
+ * identity function. |
+ */ |
+ bool get isIdentity; |
+ |
+ /** |
+ * Get the translation code in the form of a closure. |
+ */ |
+ String get asClosure; |
+ |
+ /** |
+ * Get the translation code in the form of a code snippet, where [jsonPath] |
+ * is the variable holding the JSON path, and [json] is the variable holding |
+ * the raw JSON. |
+ */ |
+ String asSnippet(String jsonPath, String json); |
+} |
+ |
+/** |
+ * Representation of FromJsonCode for a function defined elsewhere. |
+ */ |
+class FromJsonFunction extends FromJsonCode { |
+ final String asClosure; |
+ |
+ FromJsonFunction(this.asClosure); |
+ |
+ @override |
+ bool get isIdentity => false; |
+ |
+ @override |
+ String asSnippet(String jsonPath, String json) => |
+ '$asClosure($jsonPath, $json)'; |
+} |
+ |
+typedef String FromJsonSnippetCallback(String jsonPath, String json); |
+ |
+/** |
+ * Representation of FromJsonCode for a snippet of inline code. |
+ */ |
+class FromJsonSnippet extends FromJsonCode { |
+ /** |
+ * Callback that can be used to generate the code snippet, once the names |
+ * of the [jsonPath] and [json] variables are known. |
+ */ |
+ final FromJsonSnippetCallback callback; |
+ |
+ FromJsonSnippet(this.callback); |
+ |
+ @override |
+ bool get isIdentity => false; |
+ |
+ @override |
+ String get asClosure => |
+ '(String jsonPath, Object json) => ${callback('jsonPath', 'json')}'; |
+ |
+ @override |
+ String asSnippet(String jsonPath, String json) => callback(jsonPath, json); |
+} |
+ |
+/** |
+ * Representation of FromJsonCode for the identity transformation. |
+ */ |
+class FromJsonIdentity extends FromJsonSnippet { |
+ FromJsonIdentity() : super((String jsonPath, String json) => json); |
+ |
+ @override |
+ bool get isIdentity => true; |
+} |
+ |
+/** |
+ * Container for code that can be used to translate a data type to JSON. |
+ */ |
+abstract class ToJsonCode { |
+ /** |
+ * True if the data type is already in JSON form, so the translation is the |
+ * identity function. |
+ */ |
+ bool get isIdentity; |
+ |
+ /** |
+ * Get the translation code in the form of a closure. |
+ */ |
+ String get asClosure; |
+ |
+ /** |
+ * Get the translation code in the form of a code snippet, where [value] |
+ * is the variable holding the object to be translated. |
+ */ |
+ String asSnippet(String value); |
+} |
+ |
+/** |
+ * Representation of ToJsonCode for a function defined elsewhere. |
+ */ |
+class ToJsonFunction extends ToJsonCode { |
+ final String asClosure; |
+ |
+ ToJsonFunction(this.asClosure); |
+ |
+ @override |
+ bool get isIdentity => false; |
+ |
+ @override |
+ String asSnippet(String value) => '$asClosure($value)'; |
+} |
+ |
+typedef String ToJsonSnippetCallback(String value); |
+ |
+/** |
+ * Representation of ToJsonCode for a snippet of inline code. |
+ */ |
+class ToJsonSnippet extends ToJsonCode { |
+ /** |
+ * Callback that can be used to generate the code snippet, once the name |
+ * of the [value] variable is known. |
+ */ |
+ final ToJsonSnippetCallback callback; |
+ |
+ /** |
+ * Dart type of the [value] variable. |
+ */ |
+ final String type; |
+ |
+ ToJsonSnippet(this.type, this.callback); |
+ |
+ @override |
+ bool get isIdentity => false; |
+ |
+ @override |
+ String get asClosure => '($type value) => ${callback('value')}'; |
+ |
+ @override |
+ String asSnippet(String value) => callback(value); |
+} |
+ |
+/** |
+ * Representation of FromJsonCode for the identity transformation. |
+ */ |
+class ToJsonIdentity extends ToJsonSnippet { |
+ ToJsonIdentity(String type) : super(type, (String value) => value); |
+ |
+ @override |
+ bool get isIdentity => true; |
+} |
+ |
+/** |
+ * Visitor which produces Dart code representing the API. |
+ */ |
+class CodegenProtocolVisitor extends HierarchicalApiVisitor with CodeGenerator { |
+ /** |
+ * Type references in the spec that are named something else in Dart. |
+ */ |
+ static const Map<String, String> _typeRenames = const { |
+ 'object': 'Object', |
+ }; |
+ |
+ /** |
+ * Visitor used to produce doc comments. |
+ */ |
+ final ToHtmlVisitor toHtmlVisitor; |
+ |
+ /** |
+ * Types implied by the API. This includes types explicitly named in the |
+ * API as well as those implied by the definitions of requests, responses, |
+ * notifications, etc. |
+ */ |
+ final Map<String, ImpliedType> impliedTypes; |
+ |
+ CodegenProtocolVisitor(Api api) |
+ : super(api), |
+ toHtmlVisitor = new ToHtmlVisitor(api), |
+ impliedTypes = computeImpliedTypes(api); |
+ |
+ @override |
+ visitApi() { |
+ outputHeader(); |
+ writeln(); |
+ writeln('part of protocol2;'); |
+ emitClasses(); |
+ writeln(); |
+ emitJsonDecoder(); |
+ emitConvenienceFunctions(); |
+ } |
+ |
+ /** |
+ * Translate each type implied by the API to a class. |
+ */ |
+ void emitClasses() { |
+ for (ImpliedType impliedType in impliedTypes.values) { |
+ TypeDecl type = impliedType.type; |
+ if (type != null) { |
+ String dartTypeName = capitalize(impliedType.camelName); |
+ if (type is TypeObject) { |
+ writeln(); |
+ emitObjectClass(dartTypeName, type, impliedType); |
+ } else if (type is TypeEnum) { |
+ writeln(); |
+ emitEnumClass(dartTypeName, type, impliedType); |
+ } |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Emit the class to encapsulate an object type. |
+ */ |
+ void emitObjectClass(String className, TypeObject type, ImpliedType |
+ impliedType) { |
+ docComment(toHtmlVisitor.collectHtml(() { |
+ toHtmlVisitor.p(() { |
+ toHtmlVisitor.write(impliedType.humanReadableName); |
+ }); |
+ if (impliedType.type != null) { |
+ toHtmlVisitor.showType(null, impliedType.type); |
+ } |
+ })); |
+ writeln('class $className {'); |
+ indent(() { |
+ for (TypeObjectField field in type.fields) { |
+ if (field.value != null) { |
+ continue; |
+ } |
+ docComment(toHtmlVisitor.collectHtml(() { |
+ toHtmlVisitor.translateHtml(field.html); |
+ })); |
+ writeln('final ${dartType(field.type)} ${field.name};'); |
+ writeln(); |
+ } |
+ emitObjectConstructor(type, className); |
+ writeln(); |
+ emitToJsonMember(type); |
+ writeln(); |
+ writeln('String toString() => JSON.encode(toJson());'); |
+ writeln(); |
+ emitObjectEqualsMember(type, className); |
+ writeln(); |
+ emitObjectHashCode(type); |
+ }); |
+ writeln('}'); |
+ } |
+ |
+ /** |
+ * Emit the constructor for an object class. |
+ */ |
+ void emitObjectConstructor(TypeObject type, String className) { |
+ List<String> args = <String>[]; |
+ List<String> optionalArgs = <String>[]; |
+ for (TypeObjectField field in type.fields) { |
+ if (field.value != null) { |
+ continue; |
+ } |
+ String arg = 'this.${field.name}'; |
+ if (field.optional) { |
+ optionalArgs.add(arg); |
+ } else { |
+ args.add(arg); |
+ } |
+ } |
+ if (optionalArgs.isNotEmpty) { |
+ args.add('{${optionalArgs.join(', ')}}'); |
+ } |
+ writeln('$className(${args.join(', ')});'); |
+ } |
+ |
+ /** |
+ * Emit the toJson() code for an object class. |
+ */ |
+ void emitToJsonMember(TypeObject type) { |
+ writeln('Map<String, dynamic> toJson() {'); |
+ indent(() { |
+ writeln('Map<String, dynamic> result = {};'); |
+ for (TypeObjectField field in type.fields) { |
+ String fieldNameString = literalString(field.name); |
+ if (field.value != null) { |
+ writeln('result[$fieldNameString] = ${literalString(field.value)};'); |
+ continue; |
+ } |
+ String fieldToJson = toJsonCode(field.type).asSnippet(field.name); |
+ String populateField = 'result[$fieldNameString] = $fieldToJson;'; |
+ if (field.optional) { |
+ writeln('if (${field.name} != null) {'); |
+ indent(() { |
+ writeln(populateField); |
+ }); |
+ writeln('}'); |
+ } else { |
+ writeln(populateField); |
+ } |
+ } |
+ writeln('return result;'); |
+ }); |
+ writeln('}'); |
+ } |
+ |
+ /** |
+ * Emit the operator== code for an object class. |
+ */ |
+ void emitObjectEqualsMember(TypeObject type, String className) { |
+ writeln('bool operator==(other) {'); |
+ indent(() { |
+ writeln('if (other is $className) {'); |
+ indent(() { |
+ var comparisons = <String>[]; |
+ for (TypeObjectField field in type.fields) { |
+ if (field.value != null) { |
+ continue; |
+ } |
+ comparisons.add('${field.name} == other.${field.name}'); |
+ } |
+ if (comparisons.isEmpty) { |
+ writeln('return true;'); |
+ } else { |
+ String concatenated = comparisons.join(' &&\n '); |
+ writeln('return $concatenated;'); |
+ } |
+ }); |
+ writeln('}'); |
+ writeln('return false;'); |
+ }); |
+ writeln('}'); |
+ } |
+ |
+ /** |
+ * Emit the hashCode getter for an object class. |
+ */ |
+ void emitObjectHashCode(TypeObject type) { |
+ writeln('int get hashCode {'); |
+ indent(() { |
+ writeln('int hash = 0;'); |
+ for (TypeObjectField field in type.fields) { |
+ String valueToCombine; |
+ if (field.value != null) { |
+ valueToCombine = field.value.hashCode.toString(); |
+ } else { |
+ valueToCombine = '${field.name}.hashCode'; |
+ } |
+ writeln('hash = _JenkinsSmiHash.combine(hash, $valueToCombine);'); |
+ } |
+ writeln('return _JenkinsSmiHash.finish(hash);'); |
+ }); |
+ writeln('}'); |
+ } |
+ |
+ /** |
+ * Emit a class to encapsulate an enum. |
+ */ |
+ void emitEnumClass(String className, TypeEnum type, ImpliedType impliedType) { |
+ docComment(toHtmlVisitor.collectHtml(() { |
+ toHtmlVisitor.p(() { |
+ toHtmlVisitor.write(impliedType.humanReadableName); |
+ }); |
+ if (impliedType.type != null) { |
+ toHtmlVisitor.showType(null, impliedType.type); |
+ } |
+ })); |
+ writeln('class $className {'); |
+ indent(() { |
+ for (TypeEnumValue value in type.values) { |
+ docComment(toHtmlVisitor.collectHtml(() { |
+ toHtmlVisitor.translateHtml(value.html); |
+ })); |
+ String valueString = literalString(value.value); |
+ writeln( |
+ 'static const ${value.value} = const $className._($valueString);'); |
+ writeln(); |
+ } |
+ writeln('final String name;'); |
+ writeln(); |
+ writeln('const $className._(this.name);'); |
+ writeln(); |
+ emitEnumClassConstructor(className, type); |
+ writeln(); |
+ writeln('String toString() => "$className.\$name";'); |
+ writeln(); |
+ writeln('String toJson() => name;'); |
+ }); |
+ writeln('}'); |
+ } |
+ |
+ /** |
+ * Emit the constructor for an enum class. |
+ */ |
+ void emitEnumClassConstructor(String className, TypeEnum type) { |
+ writeln('factory $className(String name) {'); |
+ indent(() { |
+ writeln('switch (name) {'); |
+ indent(() { |
+ for (TypeEnumValue value in type.values) { |
+ String valueString = literalString(value.value); |
+ writeln('case $valueString:'); |
+ indent(() { |
+ writeln('return ${value.value};'); |
+ }); |
+ } |
+ }); |
+ writeln('}'); |
+ writeln(r"throw new Exception('Illegal enum value: $name');"); |
+ }); |
+ writeln('}'); |
+ } |
+ |
+ /** |
+ * Compute the code necessary to convert [type] to JSON. |
+ */ |
+ ToJsonCode toJsonCode(TypeDecl type) { |
+ TypeDecl resolvedType = resolveTypeReferenceChain(type); |
+ if (resolvedType is TypeReference) { |
+ return new ToJsonIdentity(dartType(type)); |
+ } else if (resolvedType is TypeList) { |
+ ToJsonCode itemCode = toJsonCode(resolvedType.itemType); |
+ if (itemCode.isIdentity) { |
+ return new ToJsonIdentity(dartType(type)); |
+ } else { |
+ return new ToJsonSnippet(dartType(type), (String value) => |
+ '$value.map(${itemCode.asClosure})'); |
+ } |
+ } else if (resolvedType is TypeMap) { |
+ ToJsonCode keyCode; |
+ if (dartType(resolvedType.keyType) != 'String') { |
+ keyCode = toJsonCode(resolvedType.keyType); |
+ } else { |
+ keyCode = new ToJsonIdentity(dartType(resolvedType.keyType)); |
+ } |
+ ToJsonCode valueCode = toJsonCode(resolvedType.valueType); |
+ if (keyCode.isIdentity && valueCode.isIdentity) { |
+ return new ToJsonIdentity(dartType(resolvedType)); |
+ } else { |
+ return new ToJsonSnippet(dartType(type), (String value) { |
+ StringBuffer result = new StringBuffer(); |
+ result.write('_mapMap($value'); |
+ if (!keyCode.isIdentity) { |
+ result.write(', keyCallback: ${keyCode.asClosure}'); |
+ } |
+ if (!valueCode.isIdentity) { |
+ result.write(', valueCallback: ${valueCode.asClosure}'); |
+ } |
+ result.write(')'); |
+ return result.toString(); |
+ }); |
+ } |
+ } else if (resolvedType is TypeUnion) { |
+ for (TypeDecl choice in resolvedType.choices) { |
+ if (resolveTypeReferenceChain(choice) is! TypeObject) { |
+ throw new Exception('Union types must be unions of objects'); |
+ } |
+ } |
+ return new ToJsonSnippet(dartType(type), (String value) => |
+ '$value.toJson()'); |
+ } else if (resolvedType is TypeObject || resolvedType is TypeEnum) { |
+ return new ToJsonSnippet(dartType(type), (String value) => |
+ '$value.toJson()'); |
+ } else { |
+ throw new Exception("Can't convert $resolvedType from JSON"); |
+ } |
+ } |
+ |
+ /** |
+ * Emit the JsonDecoder class, which contains code necessary to translate |
+ * objects from JSON format. |
+ */ |
+ void emitJsonDecoder() { |
+ writeln('abstract class JsonDecoder extends JsonDecoderBase {'); |
+ indent(() { |
+ for (ImpliedType impliedType in impliedTypes.values) { |
+ TypeDecl type = impliedType.type; |
+ if (type != null) { |
+ String dartTypeName = capitalize(impliedType.camelName); |
+ if (type is TypeObject) { |
+ writeln(); |
+ emitObjectFromJson(dartTypeName, type, impliedType); |
+ } else if (type is TypeEnum) { |
+ writeln(); |
+ emitEnumFromJson(dartTypeName, type, impliedType); |
+ } |
+ } |
+ } |
+ }); |
+ writeln('}'); |
+ } |
+ |
+ /** |
+ * Emit the method for decoding an object from JSON. |
+ */ |
+ void emitObjectFromJson(String className, TypeObject type, ImpliedType |
+ impliedType) { |
+ String humanReadableNameString = literalString(impliedType.humanReadableName |
+ ); |
+ String methodName = camelJoin(['decode', className]); |
+ writeln('$className $methodName(String jsonPath, Object json) {'); |
+ indent(() { |
+ writeln('if (json is Map) {'); |
+ indent(() { |
+ List<String> args = <String>[]; |
+ List<String> optionalArgs = <String>[]; |
+ for (TypeObjectField field in type.fields) { |
+ String fieldNameString = literalString(field.name); |
+ String fieldAccessor = 'json[$fieldNameString]'; |
+ String jsonPath = 'jsonPath + ${literalString('.${field.name}')}'; |
+ if (field.value != null) { |
+ String valueString = literalString(field.value); |
+ writeln('if ($fieldAccessor != $valueString) {'); |
+ indent(() { |
+ writeln('throw mismatch(jsonPath, "equal " + $valueString);'); |
+ }); |
+ writeln('}'); |
+ continue; |
+ } |
+ if (field.optional) { |
+ optionalArgs.add('${field.name}: ${field.name}'); |
+ } else { |
+ args.add(field.name); |
+ } |
+ String fieldDartType = dartType(field.type); |
+ writeln('$fieldDartType ${field.name};'); |
+ writeln('if (json.containsKey($fieldNameString)) {'); |
+ indent(() { |
+ String toJson = fromJsonCode(field.type).asSnippet(jsonPath, |
+ fieldAccessor); |
+ writeln('${field.name} = $toJson;'); |
+ }); |
+ write('}'); |
+ if (!field.optional) { |
+ writeln(' else {'); |
+ indent(() { |
+ writeln("throw missingKey(jsonPath, $fieldNameString);"); |
+ }); |
+ writeln('}'); |
+ } else { |
+ writeln(); |
+ } |
+ } |
+ args.addAll(optionalArgs); |
+ writeln('return new $className(${args.join(', ')});'); |
+ }); |
+ writeln('} else {'); |
+ indent(() { |
+ writeln('throw mismatch(jsonPath, $humanReadableNameString);'); |
+ }); |
+ writeln('}'); |
+ }); |
+ writeln('}'); |
+ } |
+ |
+ /** |
+ * Emit the method for decoding an enum from JSON. |
+ */ |
+ void emitEnumFromJson(String className, TypeEnum type, ImpliedType |
+ impliedType) { |
+ String methodName = camelJoin(['decode', className]); |
+ writeln('$className $methodName(String jsonPath, Object json) {'); |
+ indent(() { |
+ writeln('if (json is String) {'); |
+ indent(() { |
+ writeln('try {'); |
+ indent(() { |
+ writeln('return new $className(json);'); |
+ }); |
+ writeln('} catch(_) {'); |
+ indent(() { |
+ writeln('// Fall through'); |
+ }); |
+ writeln('}'); |
+ }); |
+ writeln('}'); |
+ String humanReadableNameString = literalString( |
+ impliedType.humanReadableName); |
+ writeln('throw mismatch(jsonPath, $humanReadableNameString);'); |
+ }); |
+ writeln('}'); |
+ } |
+ |
+ /** |
+ * Compute the code necessary to translate [type] from JSON. |
+ */ |
+ FromJsonCode fromJsonCode(TypeDecl type) { |
+ if (type is TypeReference) { |
+ TypeDefinition referencedDefinition = api.types[type.typeName]; |
+ if (referencedDefinition != null) { |
+ TypeDecl referencedType = referencedDefinition.type; |
+ if (referencedType is TypeObject || referencedType is TypeEnum) { |
+ String methodName = camelJoin(['decode', type.typeName]); |
+ return new FromJsonFunction(methodName); |
+ } else { |
+ return fromJsonCode(referencedType); |
+ } |
+ } else { |
+ switch (type.typeName) { |
+ case 'String': |
+ return new FromJsonFunction('_decodeString'); |
+ case 'bool': |
+ return new FromJsonFunction('_decodeBool'); |
+ case 'int': |
+ return new FromJsonFunction('_decodeInt'); |
+ case 'object': |
+ return new FromJsonIdentity(); |
+ default: |
+ throw new Exception('Unexpected type name ${type.typeName}'); |
+ } |
+ } |
+ } else if (type is TypeMap) { |
+ FromJsonCode keyCode; |
+ if (dartType(type.keyType) != 'String') { |
+ keyCode = fromJsonCode(type.keyType); |
+ } else { |
+ keyCode = new FromJsonIdentity(); |
+ } |
+ FromJsonCode valueCode = fromJsonCode(type.valueType); |
+ if (keyCode.isIdentity && valueCode.isIdentity) { |
+ return new FromJsonFunction('_decodeMap'); |
+ } else { |
+ return new FromJsonSnippet((String jsonPath, String json) { |
+ StringBuffer result = new StringBuffer(); |
+ result.write('_decodeMap($jsonPath, $json'); |
+ if (!keyCode.isIdentity) { |
+ result.write(', keyDecoder: ${keyCode.asClosure}'); |
+ } |
+ if (!valueCode.isIdentity) { |
+ result.write(', valueDecoder: ${valueCode.asClosure}'); |
+ } |
+ result.write(')'); |
+ return result.toString(); |
+ }); |
+ } |
+ } else if (type is TypeList) { |
+ FromJsonCode itemCode = fromJsonCode(type.itemType); |
+ if (itemCode.isIdentity) { |
+ return new FromJsonFunction('_decodeList'); |
+ } else { |
+ return new FromJsonSnippet((String jsonPath, String json) => |
+ '_decodeList($jsonPath, $json, ${itemCode.asClosure})'); |
+ } |
+ } else if (type is TypeUnion) { |
+ List<String> decoders = <String>[]; |
+ for (TypeDecl choice in type.choices) { |
+ TypeDecl resolvedChoice = resolveTypeReferenceChain(choice); |
+ if (resolvedChoice is TypeObject) { |
+ TypeObjectField field = resolvedChoice.getField(type.field); |
+ if (field == null) { |
+ throw new Exception( |
+ 'Each choice in the union needs a field named ${type.field}'); |
+ } |
+ if (field.value == null) { |
+ throw new Exception( |
+ 'Each choice in the union needs a constant value for the field ${type.field}'); |
+ } |
+ String closure = fromJsonCode(choice).asClosure; |
+ decoders.add('${literalString(field.value)}: $closure'); |
+ } else { |
+ throw new Exception('Union types must be unions of objects.'); |
+ } |
+ } |
+ return new FromJsonSnippet((String jsonPath, String json) => |
+ '_decodeUnion($jsonPath, $json, ${literalString(type.field)}, {${decoders.join(', ')}})' |
+ ); |
+ } else { |
+ throw new Exception("Can't convert $type from JSON"); |
+ } |
+ } |
+ |
+ /** |
+ * Emit convenience functions for decoding parts of the protocol (requests, |
+ * responses, and notifications). |
+ */ |
+ void emitConvenienceFunctions() { |
+ for (Domain domain in api.domains) { |
+ String domainName = domain.name; |
+ for (Request request in domain.requests) { |
+ String requestNameCamel = camelJoin([domainName, request.method]); |
+ String requestLongName = request.longMethod; |
+ dom.Element html = request.html; |
+ if (request.params != null) { |
+ emitConvenienceDecoder(requestNameCamel, requestLongName, html, |
+ 'request', 'params', 'Request', 'request', 'new RequestDecoder(request)', |
+ 'a request'); |
+ } |
+ if (request.result != null) { |
+ emitConvenienceDecoder(requestNameCamel, requestLongName, html, |
+ 'response', 'result', 'Response', 'response', 'new ResponseDecoder()', |
+ 'the response to a request'); |
+ } |
+ } |
+ for (Notification notification in domain.notifications) { |
+ String notificationNameCamel = camelJoin([domainName, |
+ notification.event]); |
+ String notificationLongName = notification.longEvent; |
+ dom.Element html = notification.html; |
+ if (notification.params != null) { |
+ emitConvenienceDecoder(notificationNameCamel, notificationLongName, |
+ html, 'notification', 'params', 'Notification', 'notification', |
+ 'new ResponseDecoder()', 'a notification'); |
+ } |
+ } |
+ } |
+ for (Refactoring refactoring in api.refactorings) { |
+ String refactoringNameCamel = camelJoin(refactoring.kind.toLowerCase( |
+ ).split('_')); |
+ String refactoringLongName = refactoring.kind; |
+ dom.Element html = refactoring.html; |
+ if (refactoring.feedback != null) { |
+ emitConvenienceDecoder(refactoringNameCamel, refactoringLongName, html, |
+ 'feedback', 'feedback', 'EditGetRefactoringResult', 'refactoringResult', |
+ 'new ResponseDecoder()', 'refactoring feedback'); |
+ } |
+ if (refactoring.options != null) { |
+ emitConvenienceDecoder(refactoringNameCamel, refactoringLongName, html, |
+ 'options', 'options', 'EditGetRefactoringParams', 'refactoringParams', |
+ 'new RequestDecoder(request)', 'refactoring options', extraArgs: |
+ ['Request request']); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Emit a convenience function for decoding a piece of protocol. [camelName] |
+ * is the name of the protocol piece (e.g. "analysisGetErrors"). [longName] |
+ * is the long name used in the spec (e.g. "analysis.getErrors"). [html] is |
+ * the HTML documentation of the protocol piece. [fieldName] is the field |
+ * within the protocol piece to be decoded (e.g. 'params'). [inputType] is |
+ * the type of the object to be input to the convenience function. |
+ * [inputName] is the name of the parameter to the convenience function. |
+ * [makeDecoder] is a code snippet for creating the JsonDecoder-derived class |
+ * to do the decoding. [description] is a short description of the kind of |
+ * protocol being decoded (e.g. "a notification"). |
+ */ |
+ void emitConvenienceDecoder(String camelName, String longName, dom.Element |
+ html, String methodSuffix, String fieldName, String inputType, String |
+ inputName, String makeDecoder, String description, {List<String> extraArgs: |
+ const []}) { |
+ String impliedTypeName = camelJoin([camelName, fieldName], doCapitalize: |
+ true); |
+ String methodName = camelJoin(['decode', camelName, methodSuffix]); |
+ String paramsDecoder = camelJoin(['decode', impliedTypeName]); |
+ writeln(); |
+ docComment(toHtmlVisitor.collectHtml(() { |
+ toHtmlVisitor.p(() { |
+ toHtmlVisitor.writeln('Decode $description of type $longName'); |
+ }); |
+ toHtmlVisitor.translateHtml(html); |
+ })); |
+ List<String> args = ['$inputType $inputName']; |
+ args.addAll(extraArgs); |
+ writeln('$impliedTypeName $methodName(${args.join(', ')}) {'); |
+ indent(() { |
+ writeln('var decoder = $makeDecoder;'); |
+ String fieldNameString = literalString(fieldName); |
+ writeln( |
+ "return decoder.$paramsDecoder($fieldNameString, $inputName.$fieldName);"); |
+ }); |
+ writeln('}'); |
+ } |
+ |
+ /** |
+ * Create a string literal that evaluates to [s]. |
+ */ |
+ String literalString(String s) { |
+ return JSON.encode(s); |
+ } |
+ |
+ /** |
+ * Convert the given [TypeDecl] to a Dart type. |
+ */ |
+ String dartType(TypeDecl type) { |
+ if (type is TypeReference) { |
+ String typeName = type.typeName; |
+ TypeDefinition referencedDefinition = api.types[typeName]; |
+ if (_typeRenames.containsKey(typeName)) { |
+ return _typeRenames[typeName]; |
+ } |
+ if (referencedDefinition == null) { |
+ return typeName; |
+ } |
+ TypeDecl referencedType = referencedDefinition.type; |
+ if (referencedType is TypeObject || referencedType is TypeEnum) { |
+ return typeName; |
+ } |
+ return dartType(referencedType); |
+ } else if (type is TypeList) { |
+ return 'List<${dartType(type.itemType)}>'; |
+ } else if (type is TypeMap) { |
+ return 'Map<${dartType(type.keyType)}, ${dartType(type.valueType)}>'; |
+ } else if (type is TypeUnion) { |
+ return 'dynamic'; |
+ } else { |
+ throw new Exception("Can't convert to a dart type"); |
+ } |
+ } |
+} |
+ |
+final GeneratedFile target = new GeneratedFile( |
+ '../../lib/src/generated_protocol.dart', () { |
+ CodegenProtocolVisitor visitor = new CodegenProtocolVisitor(readApi()); |
+ return visitor.collectCode(visitor.visitApi); |
+}); |
+ |
+/** |
+ * Translate spec_input.html into protocol_matchers.dart. |
+ */ |
+main() { |
+ target.generate(); |
+} |