| 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
|
| index 7bacc2322fe7cbe25ece19528116eea6c5750dab..2fc8d73ebf31a74fc38a56080f83f28201d5f3db 100644
|
| --- a/pkg/analysis_server/tool/spec/codegen_dart_protocol.dart
|
| +++ b/pkg/analysis_server/tool/spec/codegen_dart_protocol.dart
|
| @@ -6,6 +6,8 @@ library codegen.protocol;
|
|
|
| import 'dart:convert';
|
|
|
| +import 'package:html5lib/dom.dart' as dom;
|
| +
|
| import 'api.dart';
|
| import 'codegen_dart.dart';
|
| import 'codegen_tools.dart';
|
| @@ -13,158 +15,6 @@ 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;
|
| -}
|
| -
|
| /**
|
| * Special flags that need to be inserted into the declaration of the Element
|
| * class.
|
| @@ -178,11 +28,28 @@ const Map<String, String> specialElementFlags = const {
|
| 'deprecated': '0x20'
|
| };
|
|
|
| +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();
|
| +}
|
| +
|
| /**
|
| * Callback type used to represent arbitrary code generation.
|
| */
|
| typedef void CodegenCallback();
|
|
|
| +typedef String FromJsonSnippetCallback(String jsonPath, String json);
|
| +
|
| +typedef String ToJsonSnippetCallback(String value);
|
| +
|
| /**
|
| * Visitor which produces Dart code representing the API.
|
| */
|
| @@ -216,12 +83,30 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
|
| toHtmlVisitor = new ToHtmlVisitor(api),
|
| impliedTypes = computeImpliedTypes(api);
|
|
|
| - @override
|
| - visitApi() {
|
| - outputHeader();
|
| - writeln();
|
| - writeln('part of protocol;');
|
| - emitClasses();
|
| + /**
|
| + * Compute the code necessary to compare two objects for equality.
|
| + */
|
| + String compareEqualsCode(TypeDecl type, String thisVar, String otherVar) {
|
| + TypeDecl resolvedType = resolveTypeReferenceChain(type);
|
| + if (resolvedType is TypeReference ||
|
| + resolvedType is TypeEnum ||
|
| + resolvedType is TypeObject ||
|
| + resolvedType is TypeUnion) {
|
| + return '$thisVar == $otherVar';
|
| + } else if (resolvedType is TypeList) {
|
| + String itemTypeName = dartType(resolvedType.itemType);
|
| + String subComparison = compareEqualsCode(resolvedType.itemType, 'a', 'b');
|
| + String closure = '($itemTypeName a, $itemTypeName b) => $subComparison';
|
| + return '_listEqual($thisVar, $otherVar, $closure)';
|
| + } else if (resolvedType is TypeMap) {
|
| + String valueTypeName = dartType(resolvedType.valueType);
|
| + String subComparison =
|
| + compareEqualsCode(resolvedType.valueType, 'a', 'b');
|
| + String closure = '($valueTypeName a, $valueTypeName b) => $subComparison';
|
| + return '_mapEqual($thisVar, $otherVar, $closure)';
|
| + }
|
| + throw new Exception(
|
| + "Don't know how to compare for equality: $resolvedType");
|
| }
|
|
|
| /**
|
| @@ -244,60 +129,233 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
|
| }
|
|
|
| /**
|
| - * Emit a class representing an data structure that doesn't exist in the
|
| - * protocol because it is empty (e.g. the "params" object for a request that
|
| - * doesn't have any parameters).
|
| - */
|
| - void emitEmptyObjectClass(String className, ImpliedType impliedType) {
|
| - docComment(toHtmlVisitor.collectHtml(() {
|
| - toHtmlVisitor.p(() {
|
| - toHtmlVisitor.write(impliedType.humanReadableName);
|
| - });
|
| - }));
|
| - writeln('class $className {');
|
| - indent(() {
|
| - if (emitToRequestMember(impliedType)) {
|
| - writeln();
|
| - }
|
| - if (emitToResponseMember(impliedType)) {
|
| - writeln();
|
| - }
|
| - if (emitToNotificationMember(impliedType)) {
|
| - writeln();
|
| - }
|
| - emitObjectEqualsMember(null, className);
|
| - writeln();
|
| - emitObjectHashCode(null, className);
|
| - });
|
| - writeln('}');
|
| - }
|
| -
|
| - /**
|
| - * Emit the class to encapsulate an object type.
|
| + * Emit a convenience constructor for decoding a piece of protocol, if
|
| + * appropriate. Return true if a constructor was emitted.
|
| */
|
| - 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);
|
| - }
|
| - }));
|
| - write('class $className');
|
| - if (impliedType.kind == 'refactoringFeedback') {
|
| - write(' extends RefactoringFeedback');
|
| - }
|
| - if (impliedType.kind == 'refactoringOptions') {
|
| - write(' extends RefactoringOptions');
|
| - }
|
| - writeln(' implements HasToJson {');
|
| - indent(() {
|
| - if (emitSpecialStaticMembers(className)) {
|
| - writeln();
|
| - }
|
| - for (TypeObjectField field in type.fields) {
|
| + bool emitConvenienceConstructor(String className, ImpliedType impliedType) {
|
| + // The type of object from which this piece of protocol should be decoded.
|
| + String inputType;
|
| + // The name of the input object.
|
| + String inputName;
|
| + // The field within the input object to decode.
|
| + String fieldName;
|
| + // Constructor call to create the JsonDecoder object.
|
| + String makeDecoder;
|
| + // Name of the constructor to create.
|
| + String constructorName;
|
| + // Extra arguments for the constructor.
|
| + List<String> extraArgs = <String>[];
|
| + switch (impliedType.kind) {
|
| + case 'requestParams':
|
| + inputType = 'Request';
|
| + inputName = 'request';
|
| + fieldName = '_params';
|
| + makeDecoder = 'new RequestDecoder(request)';
|
| + constructorName = 'fromRequest';
|
| + break;
|
| + case 'requestResult':
|
| + inputType = 'Response';
|
| + inputName = 'response';
|
| + fieldName = '_result';
|
| + makeDecoder =
|
| + 'new ResponseDecoder(REQUEST_ID_REFACTORING_KINDS.remove(response.id))';
|
| + constructorName = 'fromResponse';
|
| + break;
|
| + case 'notificationParams':
|
| + inputType = 'Notification';
|
| + inputName = 'notification';
|
| + fieldName = '_params';
|
| + makeDecoder = 'new ResponseDecoder(null)';
|
| + constructorName = 'fromNotification';
|
| + break;
|
| + case 'refactoringOptions':
|
| + inputType = 'EditGetRefactoringParams';
|
| + inputName = 'refactoringParams';
|
| + fieldName = 'options';
|
| + makeDecoder = 'new RequestDecoder(request)';
|
| + constructorName = 'fromRefactoringParams';
|
| + extraArgs.add('Request request');
|
| + break;
|
| + default:
|
| + return false;
|
| + }
|
| + List<String> args = ['$inputType $inputName'];
|
| + args.addAll(extraArgs);
|
| + writeln('factory $className.$constructorName(${args.join(', ')}) {');
|
| + indent(() {
|
| + String fieldNameString =
|
| + literalString(fieldName.replaceFirst(new RegExp('^_'), ''));
|
| + if (className == 'EditGetRefactoringParams') {
|
| + writeln('var params = new $className.fromJson(');
|
| + writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);');
|
| + writeln('REQUEST_ID_REFACTORING_KINDS[request.id] = params.kind;');
|
| + writeln('return params;');
|
| + } else {
|
| + writeln('return new $className.fromJson(');
|
| + writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);');
|
| + }
|
| + });
|
| + writeln('}');
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * Emit a class representing an data structure that doesn't exist in the
|
| + * protocol because it is empty (e.g. the "params" object for a request that
|
| + * doesn't have any parameters).
|
| + */
|
| + void emitEmptyObjectClass(String className, ImpliedType impliedType) {
|
| + docComment(toHtmlVisitor.collectHtml(() {
|
| + toHtmlVisitor.p(() {
|
| + toHtmlVisitor.write(impliedType.humanReadableName);
|
| + });
|
| + }));
|
| + writeln('class $className {');
|
| + indent(() {
|
| + if (emitToRequestMember(impliedType)) {
|
| + writeln();
|
| + }
|
| + if (emitToResponseMember(impliedType)) {
|
| + writeln();
|
| + }
|
| + if (emitToNotificationMember(impliedType)) {
|
| + writeln();
|
| + }
|
| + emitObjectEqualsMember(null, className);
|
| + writeln();
|
| + emitObjectHashCode(null, className);
|
| + });
|
| + 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 implements Enum {');
|
| + indent(() {
|
| + if (emitSpecialStaticMembers(className)) {
|
| + writeln();
|
| + }
|
| + 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();
|
| + emitEnumFromJsonConstructor(className, type, impliedType);
|
| + writeln();
|
| + if (emitSpecialConstructors(className)) {
|
| + writeln();
|
| + }
|
| + if (emitSpecialGetters(className)) {
|
| + writeln();
|
| + }
|
| + if (emitSpecialMethods(className)) {
|
| + writeln();
|
| + }
|
| + writeln('@override');
|
| + 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('}');
|
| + }
|
| +
|
| + /**
|
| + * Emit the method for decoding an enum from JSON.
|
| + */
|
| + void emitEnumFromJsonConstructor(String className, TypeEnum type,
|
| + ImpliedType impliedType) {
|
| + writeln(
|
| + 'factory $className.fromJson(JsonDecoder jsonDecoder, 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 jsonDecoder.mismatch(jsonPath, $humanReadableNameString);');
|
| + });
|
| + writeln('}');
|
| + }
|
| +
|
| + /**
|
| + * 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);
|
| + }
|
| + }));
|
| + write('class $className');
|
| + if (impliedType.kind == 'refactoringFeedback') {
|
| + write(' extends RefactoringFeedback');
|
| + }
|
| + if (impliedType.kind == 'refactoringOptions') {
|
| + write(' extends RefactoringOptions');
|
| + }
|
| + writeln(' implements HasToJson {');
|
| + indent(() {
|
| + if (emitSpecialStaticMembers(className)) {
|
| + writeln();
|
| + }
|
| + for (TypeObjectField field in type.fields) {
|
| if (field.value != null) {
|
| continue;
|
| }
|
| @@ -345,46 +403,208 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
|
| }
|
|
|
| /**
|
| - * If the class named [className] requires special static members, emit them
|
| - * and return true.
|
| + * Emit the constructor for an object class.
|
| */
|
| - bool emitSpecialStaticMembers(String className) {
|
| - switch (className) {
|
| - case 'Element':
|
| - List<String> makeFlagsArgs = <String>[];
|
| - List<String> makeFlagsStatements = <String>[];
|
| - specialElementFlags.forEach((String name, String value) {
|
| - String flag = 'FLAG_${name.toUpperCase()}';
|
| - String camelName = camelJoin(['is', name]);
|
| - writeln('static const int $flag = $value;');
|
| - makeFlagsArgs.add('$camelName: false');
|
| - makeFlagsStatements.add('if ($camelName) flags |= $flag;');
|
| - });
|
| - writeln();
|
| - writeln('static int makeFlags({${makeFlagsArgs.join(', ')}}) {');
|
| - indent(() {
|
| - writeln('int flags = 0;');
|
| - for (String statement in makeFlagsStatements) {
|
| - writeln(statement);
|
| - }
|
| - writeln('return flags;');
|
| - });
|
| - writeln('}');
|
| - return true;
|
| - case 'SourceEdit':
|
| - docComment(
|
| - [
|
| - new dom.Text(
|
| - 'Get the result of applying a set of ' +
|
| - '[edits] to the given [code]. Edits are applied in the order ' +
|
| - 'they appear in [edits].')]);
|
| + void emitObjectConstructor(TypeObject type, String className) {
|
| + List<String> args = <String>[];
|
| + List<String> optionalArgs = <String>[];
|
| + List<CodegenCallback> extraInitCode = <CodegenCallback>[];
|
| + for (TypeObjectField field in type.fields) {
|
| + if (field.value != null) {
|
| + continue;
|
| + }
|
| + String arg = 'this.${field.name}';
|
| + if (isOptionalConstructorArg(className, field)) {
|
| + optionalArgs.add(arg);
|
| + if (!field.optional) {
|
| + // Optional constructor arg, but non-optional field. If no arg is
|
| + // given, the constructor should populate with the empty list.
|
| + TypeDecl fieldType = field.type;
|
| + if (fieldType is TypeList) {
|
| + extraInitCode.add(() {
|
| + writeln('if (${field.name} == null) {');
|
| + indent(() {
|
| + writeln('${field.name} = <${dartType(fieldType.itemType)}>[];');
|
| + });
|
| + writeln('}');
|
| + });
|
| + } else {
|
| + throw new Exception(
|
| + "Don't know how to create default field value.");
|
| + }
|
| + }
|
| + } else {
|
| + args.add(arg);
|
| + }
|
| + }
|
| + if (optionalArgs.isNotEmpty) {
|
| + args.add('{${optionalArgs.join(', ')}}');
|
| + }
|
| + write('$className(${args.join(', ')})');
|
| + if (extraInitCode.isEmpty) {
|
| + writeln(';');
|
| + } else {
|
| + writeln(' {');
|
| + indent(() {
|
| + for (CodegenCallback callback in extraInitCode) {
|
| + callback();
|
| + }
|
| + });
|
| + writeln('}');
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Emit the operator== code for an object class.
|
| + */
|
| + void emitObjectEqualsMember(TypeObject type, String className) {
|
| + writeln('@override');
|
| + writeln('bool operator==(other) {');
|
| + indent(() {
|
| + writeln('if (other is $className) {');
|
| + indent(() {
|
| + var comparisons = <String>[];
|
| + if (type != null) {
|
| + for (TypeObjectField field in type.fields) {
|
| + if (field.value != null) {
|
| + continue;
|
| + }
|
| + comparisons.add(
|
| + compareEqualsCode(field.type, 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 method for decoding an object from JSON.
|
| + */
|
| + void emitObjectFromJsonConstructor(String className, TypeObject type,
|
| + ImpliedType impliedType) {
|
| + String humanReadableNameString =
|
| + literalString(impliedType.humanReadableName);
|
| + if (className == 'RefactoringFeedback') {
|
| + writeln(
|
| + 'factory RefactoringFeedback.fromJson(JsonDecoder jsonDecoder, '
|
| + 'String jsonPath, Object json, Map responseJson) {');
|
| + indent(() {
|
| writeln(
|
| - 'static String applySequence(String code, Iterable<SourceEdit> edits) =>');
|
| - writeln(' _applySequence(code, edits);');
|
| - return true;
|
| - default:
|
| - return false;
|
| + 'return _refactoringFeedbackFromJson(jsonDecoder, jsonPath, '
|
| + 'json, responseJson);');
|
| + });
|
| + writeln('}');
|
| + return;
|
| + }
|
| + if (className == 'RefactoringOptions') {
|
| + writeln(
|
| + 'factory RefactoringOptions.fromJson(JsonDecoder jsonDecoder, '
|
| + 'String jsonPath, Object json, RefactoringKind kind) {');
|
| + indent(() {
|
| + writeln(
|
| + 'return _refactoringOptionsFromJson(jsonDecoder, jsonPath, ' 'json, kind);');
|
| + });
|
| + writeln('}');
|
| + return;
|
| }
|
| + writeln(
|
| + 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, Object json) {');
|
| + indent(() {
|
| + writeln('if (json == null) {');
|
| + indent(() {
|
| + writeln('json = {};');
|
| + });
|
| + writeln('}');
|
| + 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 jsonDecoder.mismatch(jsonPath, "equal " + $valueString);');
|
| + });
|
| + writeln('}');
|
| + continue;
|
| + }
|
| + if (isOptionalConstructorArg(className, field)) {
|
| + optionalArgs.add('${field.name}: ${field.name}');
|
| + } else {
|
| + args.add(field.name);
|
| + }
|
| + TypeDecl fieldType = field.type;
|
| + String fieldDartType = dartType(fieldType);
|
| + writeln('$fieldDartType ${field.name};');
|
| + writeln('if (json.containsKey($fieldNameString)) {');
|
| + indent(() {
|
| + String fromJson =
|
| + fromJsonCode(fieldType).asSnippet(jsonPath, fieldAccessor);
|
| + writeln('${field.name} = $fromJson;');
|
| + });
|
| + write('}');
|
| + if (!field.optional) {
|
| + writeln(' else {');
|
| + indent(() {
|
| + writeln(
|
| + "throw jsonDecoder.missingKey(jsonPath, $fieldNameString);");
|
| + });
|
| + writeln('}');
|
| + } else {
|
| + writeln();
|
| + }
|
| + }
|
| + args.addAll(optionalArgs);
|
| + writeln('return new $className(${args.join(', ')});');
|
| + });
|
| + writeln('} else {');
|
| + indent(() {
|
| + writeln(
|
| + 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);');
|
| + });
|
| + writeln('}');
|
| + });
|
| + writeln('}');
|
| + }
|
| +
|
| + /**
|
| + * Emit the hashCode getter for an object class.
|
| + */
|
| + void emitObjectHashCode(TypeObject type, String className) {
|
| + writeln('@override');
|
| + writeln('int get hashCode {');
|
| + indent(() {
|
| + if (type == null) {
|
| + writeln('return ${className.hashCode};');
|
| + } else {
|
| + 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('}');
|
| }
|
|
|
| /**
|
| @@ -500,69 +720,46 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
|
| }
|
|
|
| /**
|
| - * Emit the constructor for an object class.
|
| + * If the class named [className] requires special static members, emit them
|
| + * and return true.
|
| */
|
| - void emitObjectConstructor(TypeObject type, String className) {
|
| - List<String> args = <String>[];
|
| - List<String> optionalArgs = <String>[];
|
| - List<CodegenCallback> extraInitCode = <CodegenCallback>[];
|
| - for (TypeObjectField field in type.fields) {
|
| - if (field.value != null) {
|
| - continue;
|
| - }
|
| - String arg = 'this.${field.name}';
|
| - if (isOptionalConstructorArg(className, field)) {
|
| - optionalArgs.add(arg);
|
| - if (!field.optional) {
|
| - // Optional constructor arg, but non-optional field. If no arg is
|
| - // given, the constructor should populate with the empty list.
|
| - TypeDecl fieldType = field.type;
|
| - if (fieldType is TypeList) {
|
| - extraInitCode.add(() {
|
| - writeln('if (${field.name} == null) {');
|
| - indent(() {
|
| - writeln('${field.name} = <${dartType(fieldType.itemType)}>[];');
|
| - });
|
| - writeln('}');
|
| - });
|
| - } else {
|
| - throw new Exception(
|
| - "Don't know how to create default field value.");
|
| + bool emitSpecialStaticMembers(String className) {
|
| + switch (className) {
|
| + case 'Element':
|
| + List<String> makeFlagsArgs = <String>[];
|
| + List<String> makeFlagsStatements = <String>[];
|
| + specialElementFlags.forEach((String name, String value) {
|
| + String flag = 'FLAG_${name.toUpperCase()}';
|
| + String camelName = camelJoin(['is', name]);
|
| + writeln('static const int $flag = $value;');
|
| + makeFlagsArgs.add('$camelName: false');
|
| + makeFlagsStatements.add('if ($camelName) flags |= $flag;');
|
| + });
|
| + writeln();
|
| + writeln('static int makeFlags({${makeFlagsArgs.join(', ')}}) {');
|
| + indent(() {
|
| + writeln('int flags = 0;');
|
| + for (String statement in makeFlagsStatements) {
|
| + writeln(statement);
|
| }
|
| - }
|
| - } else {
|
| - args.add(arg);
|
| - }
|
| - }
|
| - if (optionalArgs.isNotEmpty) {
|
| - args.add('{${optionalArgs.join(', ')}}');
|
| - }
|
| - write('$className(${args.join(', ')})');
|
| - if (extraInitCode.isEmpty) {
|
| - writeln(';');
|
| - } else {
|
| - writeln(' {');
|
| - indent(() {
|
| - for (CodegenCallback callback in extraInitCode) {
|
| - callback();
|
| - }
|
| - });
|
| - writeln('}');
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * True if the constructor argument for the given field should be optional.
|
| - */
|
| - bool isOptionalConstructorArg(String className, TypeObjectField field) {
|
| - if (field.optional) {
|
| - return true;
|
| - }
|
| - List<String> forceOptional = _optionalConstructorArguments[className];
|
| - if (forceOptional != null && forceOptional.contains(field.name)) {
|
| - return true;
|
| + writeln('return flags;');
|
| + });
|
| + writeln('}');
|
| + return true;
|
| + case 'SourceEdit':
|
| + docComment(
|
| + [
|
| + new dom.Text(
|
| + 'Get the result of applying a set of ' +
|
| + '[edits] to the given [code]. Edits are applied in the order ' +
|
| + 'they appear in [edits].')]);
|
| + writeln(
|
| + 'static String applySequence(String code, Iterable<SourceEdit> edits) =>');
|
| + writeln(' _applySequence(code, edits);');
|
| + return true;
|
| + default:
|
| + return false;
|
| }
|
| - return false;
|
| }
|
|
|
| /**
|
| @@ -596,6 +793,25 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
|
| }
|
|
|
| /**
|
| + * Emit the toNotification() code for a class, if appropriate. Returns true
|
| + * if code was emitted.
|
| + */
|
| + bool emitToNotificationMember(ImpliedType impliedType) {
|
| + if (impliedType.kind == 'notificationParams') {
|
| + writeln('Notification toNotification() {');
|
| + indent(() {
|
| + String eventString =
|
| + literalString((impliedType.apiNode as Notification).longEvent);
|
| + String jsonPart = impliedType.type != null ? 'toJson()' : 'null';
|
| + writeln('return new Notification($eventString, $jsonPart);');
|
| + });
|
| + writeln('}');
|
| + return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| * Emit the toRequest() code for a class, if appropriate. Returns true if
|
| * code was emitted.
|
| */
|
| @@ -632,157 +848,128 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
|
| }
|
|
|
| /**
|
| - * Emit the toNotification() code for a class, if appropriate. Returns true
|
| - * if code was emitted.
|
| + * Compute the code necessary to translate [type] from JSON.
|
| */
|
| - bool emitToNotificationMember(ImpliedType impliedType) {
|
| - if (impliedType.kind == 'notificationParams') {
|
| - writeln('Notification toNotification() {');
|
| - indent(() {
|
| - String eventString =
|
| - literalString((impliedType.apiNode as Notification).longEvent);
|
| - String jsonPart = impliedType.type != null ? 'toJson()' : 'null';
|
| - writeln('return new Notification($eventString, $jsonPart);');
|
| - });
|
| - writeln('}');
|
| - return true;
|
| + 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) {
|
| + return new FromJsonSnippet((String jsonPath, String json) {
|
| + String typeName = dartType(type);
|
| + if (typeName == 'RefactoringFeedback') {
|
| + return
|
| + 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, json)';
|
| + } else if (typeName == 'RefactoringOptions') {
|
| + return
|
| + 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, kind)';
|
| + } else {
|
| + return 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json)';
|
| + }
|
| + });
|
| + } else {
|
| + return fromJsonCode(referencedType);
|
| + }
|
| + } else {
|
| + switch (type.typeName) {
|
| + case 'String':
|
| + return new FromJsonFunction('jsonDecoder._decodeString');
|
| + case 'bool':
|
| + return new FromJsonFunction('jsonDecoder._decodeBool');
|
| + case 'int':
|
| + case 'long':
|
| + return new FromJsonFunction('jsonDecoder._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('jsonDecoder._decodeMap');
|
| + } else {
|
| + return new FromJsonSnippet((String jsonPath, String json) {
|
| + StringBuffer result = new StringBuffer();
|
| + result.write('jsonDecoder._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('jsonDecoder._decodeList');
|
| + } else {
|
| + return new FromJsonSnippet(
|
| + (String jsonPath, String json) =>
|
| + 'jsonDecoder._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) =>
|
| + 'jsonDecoder._decodeUnion($jsonPath, $json, ${literalString(type.field)}, {${decoders.join(', ')}})');
|
| + } else {
|
| + throw new Exception("Can't convert $type from JSON");
|
| }
|
| - return false;
|
| }
|
|
|
| /**
|
| - * Emit the operator== code for an object class.
|
| + * True if the constructor argument for the given field should be optional.
|
| */
|
| - void emitObjectEqualsMember(TypeObject type, String className) {
|
| - writeln('@override');
|
| - writeln('bool operator==(other) {');
|
| - indent(() {
|
| - writeln('if (other is $className) {');
|
| - indent(() {
|
| - var comparisons = <String>[];
|
| - if (type != null) {
|
| - for (TypeObjectField field in type.fields) {
|
| - if (field.value != null) {
|
| - continue;
|
| - }
|
| - comparisons.add(
|
| - compareEqualsCode(field.type, 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('}');
|
| + bool isOptionalConstructorArg(String className, TypeObjectField field) {
|
| + if (field.optional) {
|
| + return true;
|
| + }
|
| + List<String> forceOptional = _optionalConstructorArguments[className];
|
| + if (forceOptional != null && forceOptional.contains(field.name)) {
|
| + return true;
|
| + }
|
| + return false;
|
| }
|
|
|
| /**
|
| - * Emit the hashCode getter for an object class.
|
| + * Create a string literal that evaluates to [s].
|
| */
|
| - void emitObjectHashCode(TypeObject type, String className) {
|
| - writeln('@override');
|
| - writeln('int get hashCode {');
|
| - indent(() {
|
| - if (type == null) {
|
| - writeln('return ${className.hashCode};');
|
| - } else {
|
| - 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('}');
|
| + String literalString(String s) {
|
| + return JSON.encode(s);
|
| }
|
|
|
| /**
|
| - * 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 implements Enum {');
|
| - indent(() {
|
| - if (emitSpecialStaticMembers(className)) {
|
| - writeln();
|
| - }
|
| - 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();
|
| - emitEnumFromJsonConstructor(className, type, impliedType);
|
| - writeln();
|
| - if (emitSpecialConstructors(className)) {
|
| - writeln();
|
| - }
|
| - if (emitSpecialGetters(className)) {
|
| - writeln();
|
| - }
|
| - if (emitSpecialMethods(className)) {
|
| - writeln();
|
| - }
|
| - writeln('@override');
|
| - 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.
|
| + * Compute the code necessary to convert [type] to JSON.
|
| */
|
| ToJsonCode toJsonCode(TypeDecl type) {
|
| TypeDecl resolvedType = resolveTypeReferenceChain(type);
|
| @@ -839,344 +1026,157 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
|
| }
|
| }
|
|
|
| + @override
|
| + visitApi() {
|
| + outputHeader();
|
| + writeln();
|
| + writeln('part of protocol;');
|
| + emitClasses();
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Container for code that can be used to translate a data type from JSON.
|
| + */
|
| +abstract class FromJsonCode {
|
| /**
|
| - * Compute the code necessary to compare two objects for equality.
|
| + * Get the translation code in the form of a closure.
|
| */
|
| - String compareEqualsCode(TypeDecl type, String thisVar, String otherVar) {
|
| - TypeDecl resolvedType = resolveTypeReferenceChain(type);
|
| - if (resolvedType is TypeReference ||
|
| - resolvedType is TypeEnum ||
|
| - resolvedType is TypeObject ||
|
| - resolvedType is TypeUnion) {
|
| - return '$thisVar == $otherVar';
|
| - } else if (resolvedType is TypeList) {
|
| - String itemTypeName = dartType(resolvedType.itemType);
|
| - String subComparison = compareEqualsCode(resolvedType.itemType, 'a', 'b');
|
| - String closure = '($itemTypeName a, $itemTypeName b) => $subComparison';
|
| - return '_listEqual($thisVar, $otherVar, $closure)';
|
| - } else if (resolvedType is TypeMap) {
|
| - String valueTypeName = dartType(resolvedType.valueType);
|
| - String subComparison =
|
| - compareEqualsCode(resolvedType.valueType, 'a', 'b');
|
| - String closure = '($valueTypeName a, $valueTypeName b) => $subComparison';
|
| - return '_mapEqual($thisVar, $otherVar, $closure)';
|
| - }
|
| - throw new Exception(
|
| - "Don't know how to compare for equality: $resolvedType");
|
| - }
|
| + String get asClosure;
|
|
|
| /**
|
| - * Emit the method for decoding an object from JSON.
|
| + * True if the data type is already in JSON form, so the translation is the
|
| + * identity function.
|
| */
|
| - void emitObjectFromJsonConstructor(String className, TypeObject type,
|
| - ImpliedType impliedType) {
|
| - String humanReadableNameString =
|
| - literalString(impliedType.humanReadableName);
|
| - if (className == 'RefactoringFeedback') {
|
| - writeln(
|
| - 'factory RefactoringFeedback.fromJson(JsonDecoder jsonDecoder, '
|
| - 'String jsonPath, Object json, Map responseJson) {');
|
| - indent(() {
|
| - writeln(
|
| - 'return _refactoringFeedbackFromJson(jsonDecoder, jsonPath, '
|
| - 'json, responseJson);');
|
| - });
|
| - writeln('}');
|
| - return;
|
| - }
|
| - if (className == 'RefactoringOptions') {
|
| - writeln(
|
| - 'factory RefactoringOptions.fromJson(JsonDecoder jsonDecoder, '
|
| - 'String jsonPath, Object json, RefactoringKind kind) {');
|
| - indent(() {
|
| - writeln(
|
| - 'return _refactoringOptionsFromJson(jsonDecoder, jsonPath, ' 'json, kind);');
|
| - });
|
| - writeln('}');
|
| - return;
|
| - }
|
| - writeln(
|
| - 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, Object json) {');
|
| - indent(() {
|
| - writeln('if (json == null) {');
|
| - indent(() {
|
| - writeln('json = {};');
|
| - });
|
| - writeln('}');
|
| - 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 jsonDecoder.mismatch(jsonPath, "equal " + $valueString);');
|
| - });
|
| - writeln('}');
|
| - continue;
|
| - }
|
| - if (isOptionalConstructorArg(className, field)) {
|
| - optionalArgs.add('${field.name}: ${field.name}');
|
| - } else {
|
| - args.add(field.name);
|
| - }
|
| - TypeDecl fieldType = field.type;
|
| - String fieldDartType = dartType(fieldType);
|
| - writeln('$fieldDartType ${field.name};');
|
| - writeln('if (json.containsKey($fieldNameString)) {');
|
| - indent(() {
|
| - String fromJson =
|
| - fromJsonCode(fieldType).asSnippet(jsonPath, fieldAccessor);
|
| - writeln('${field.name} = $fromJson;');
|
| - });
|
| - write('}');
|
| - if (!field.optional) {
|
| - writeln(' else {');
|
| - indent(() {
|
| - writeln(
|
| - "throw jsonDecoder.missingKey(jsonPath, $fieldNameString);");
|
| - });
|
| - writeln('}');
|
| - } else {
|
| - writeln();
|
| - }
|
| - }
|
| - args.addAll(optionalArgs);
|
| - writeln('return new $className(${args.join(', ')});');
|
| - });
|
| - writeln('} else {');
|
| - indent(() {
|
| - writeln(
|
| - 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);');
|
| - });
|
| - writeln('}');
|
| - });
|
| - writeln('}');
|
| - }
|
| + bool get isIdentity;
|
|
|
| /**
|
| - * Emit the method for decoding an enum from JSON.
|
| + * 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.
|
| */
|
| - void emitEnumFromJsonConstructor(String className, TypeEnum type,
|
| - ImpliedType impliedType) {
|
| - writeln(
|
| - 'factory $className.fromJson(JsonDecoder jsonDecoder, 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 jsonDecoder.mismatch(jsonPath, $humanReadableNameString);');
|
| - });
|
| - writeln('}');
|
| - }
|
| + 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)';
|
| +}
|
| +
|
| +/**
|
| + * Representation of FromJsonCode for the identity transformation.
|
| + */
|
| +class FromJsonIdentity extends FromJsonSnippet {
|
| + FromJsonIdentity() : super((String jsonPath, String json) => json);
|
| +
|
| + @override
|
| + bool get isIdentity => true;
|
| +}
|
| +
|
| +/**
|
| + * Representation of FromJsonCode for a snippet of inline code.
|
| + */
|
| +class FromJsonSnippet extends FromJsonCode {
|
| /**
|
| - * Compute the code necessary to translate [type] from JSON.
|
| + * Callback that can be used to generate the code snippet, once the names
|
| + * of the [jsonPath] and [json] variables are known.
|
| */
|
| - 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) {
|
| - return new FromJsonSnippet((String jsonPath, String json) {
|
| - String typeName = dartType(type);
|
| - if (typeName == 'RefactoringFeedback') {
|
| - return
|
| - 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, json)';
|
| - } else if (typeName == 'RefactoringOptions') {
|
| - return
|
| - 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, kind)';
|
| - } else {
|
| - return 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json)';
|
| - }
|
| - });
|
| - } else {
|
| - return fromJsonCode(referencedType);
|
| - }
|
| - } else {
|
| - switch (type.typeName) {
|
| - case 'String':
|
| - return new FromJsonFunction('jsonDecoder._decodeString');
|
| - case 'bool':
|
| - return new FromJsonFunction('jsonDecoder._decodeBool');
|
| - case 'int':
|
| - case 'long':
|
| - return new FromJsonFunction('jsonDecoder._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('jsonDecoder._decodeMap');
|
| - } else {
|
| - return new FromJsonSnippet((String jsonPath, String json) {
|
| - StringBuffer result = new StringBuffer();
|
| - result.write('jsonDecoder._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('jsonDecoder._decodeList');
|
| - } else {
|
| - return new FromJsonSnippet(
|
| - (String jsonPath, String json) =>
|
| - 'jsonDecoder._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) =>
|
| - 'jsonDecoder._decodeUnion($jsonPath, $json, ${literalString(type.field)}, {${decoders.join(', ')}})');
|
| - } else {
|
| - throw new Exception("Can't convert $type from JSON");
|
| - }
|
| - }
|
| + final FromJsonSnippetCallback callback;
|
| +
|
| + FromJsonSnippet(this.callback);
|
| +
|
| + @override
|
| + String get asClosure =>
|
| + '(String jsonPath, Object json) => ${callback('jsonPath', 'json')}';
|
|
|
| + @override
|
| + bool get isIdentity => false;
|
| +
|
| + @override
|
| + String asSnippet(String jsonPath, String json) => callback(jsonPath, json);
|
| +}
|
| +
|
| +/**
|
| + * Container for code that can be used to translate a data type to JSON.
|
| + */
|
| +abstract class ToJsonCode {
|
| /**
|
| - * Emit a convenience constructor for decoding a piece of protocol, if
|
| - * appropriate. Return true if a constructor was emitted.
|
| + * Get the translation code in the form of a closure.
|
| */
|
| - bool emitConvenienceConstructor(String className, ImpliedType impliedType) {
|
| - // The type of object from which this piece of protocol should be decoded.
|
| - String inputType;
|
| - // The name of the input object.
|
| - String inputName;
|
| - // The field within the input object to decode.
|
| - String fieldName;
|
| - // Constructor call to create the JsonDecoder object.
|
| - String makeDecoder;
|
| - // Name of the constructor to create.
|
| - String constructorName;
|
| - // Extra arguments for the constructor.
|
| - List<String> extraArgs = <String>[];
|
| - switch (impliedType.kind) {
|
| - case 'requestParams':
|
| - inputType = 'Request';
|
| - inputName = 'request';
|
| - fieldName = '_params';
|
| - makeDecoder = 'new RequestDecoder(request)';
|
| - constructorName = 'fromRequest';
|
| - break;
|
| - case 'requestResult':
|
| - inputType = 'Response';
|
| - inputName = 'response';
|
| - fieldName = '_result';
|
| - makeDecoder =
|
| - 'new ResponseDecoder(REQUEST_ID_REFACTORING_KINDS.remove(response.id))';
|
| - constructorName = 'fromResponse';
|
| - break;
|
| - case 'notificationParams':
|
| - inputType = 'Notification';
|
| - inputName = 'notification';
|
| - fieldName = '_params';
|
| - makeDecoder = 'new ResponseDecoder(null)';
|
| - constructorName = 'fromNotification';
|
| - break;
|
| - case 'refactoringOptions':
|
| - inputType = 'EditGetRefactoringParams';
|
| - inputName = 'refactoringParams';
|
| - fieldName = 'options';
|
| - makeDecoder = 'new RequestDecoder(request)';
|
| - constructorName = 'fromRefactoringParams';
|
| - extraArgs.add('Request request');
|
| - break;
|
| - default:
|
| - return false;
|
| - }
|
| - List<String> args = ['$inputType $inputName'];
|
| - args.addAll(extraArgs);
|
| - writeln('factory $className.$constructorName(${args.join(', ')}) {');
|
| - indent(() {
|
| - String fieldNameString =
|
| - literalString(fieldName.replaceFirst(new RegExp('^_'), ''));
|
| - if (className == 'EditGetRefactoringParams') {
|
| - writeln('var params = new $className.fromJson(');
|
| - writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);');
|
| - writeln('REQUEST_ID_REFACTORING_KINDS[request.id] = params.kind;');
|
| - writeln('return params;');
|
| - } else {
|
| - writeln('return new $className.fromJson(');
|
| - writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);');
|
| - }
|
| - });
|
| - writeln('}');
|
| - return true;
|
| - }
|
| + String get asClosure;
|
|
|
| /**
|
| - * Create a string literal that evaluates to [s].
|
| + * True if the data type is already in JSON form, so the translation is the
|
| + * identity function.
|
| */
|
| - String literalString(String s) {
|
| - return JSON.encode(s);
|
| - }
|
| + bool get isIdentity;
|
| +
|
| + /**
|
| + * 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);
|
| }
|
|
|
| -final GeneratedFile target =
|
| - new GeneratedFile('../../lib/src/generated_protocol.dart', () {
|
| - CodegenProtocolVisitor visitor = new CodegenProtocolVisitor(readApi());
|
| - return visitor.collectCode(visitor.visitApi);
|
| -});
|
| +/**
|
| + * 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)';
|
| +}
|
|
|
| /**
|
| - * Translate spec_input.html into protocol_matchers.dart.
|
| + * Representation of FromJsonCode for the identity transformation.
|
| */
|
| -main() {
|
| - target.generate();
|
| +class ToJsonIdentity extends ToJsonSnippet {
|
| + ToJsonIdentity(String type) : super(type, (String value) => value);
|
| +
|
| + @override
|
| + bool get isIdentity => true;
|
| +}
|
| +
|
| +/**
|
| + * 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
|
| + String get asClosure => '($type value) => ${callback('value')}';
|
| +
|
| + @override
|
| + bool get isIdentity => false;
|
| +
|
| + @override
|
| + String asSnippet(String value) => callback(value);
|
| }
|
|
|