Chromium Code Reviews| Index: pkg/analysis_server/spec/api.dart |
| diff --git a/pkg/analysis_server/spec/api.dart b/pkg/analysis_server/spec/api.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..0724e6d3859e5747818dc85718df22a44d9ec90a |
| --- /dev/null |
| +++ b/pkg/analysis_server/spec/api.dart |
| @@ -0,0 +1,449 @@ |
| +// 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. |
| + |
| +/** |
| + * Data structures representing an API definition, and visitor base classes |
| + * for visiting those data structures. |
| + */ |
| +library api; |
| + |
| +import 'dart:collection'; |
| + |
| +import 'package:html5lib/dom.dart' as dom; |
| + |
| +/** |
| + * Base class for visiting the API definition. |
| + */ |
| +abstract class ApiVisitor<T> { |
| + T visitTypeReference(TypeReference typeReference); |
| + T visitTypeObject(TypeObject typeObject); |
| + T visitTypeList(TypeList typeList); |
| + T visitTypeMap(TypeMap typeMap); |
| + T visitTypeEnum(TypeEnum typeEnum); |
| + |
| + /** |
| + * Dispatch the given [node] to the visitor. |
|
scheglov
2014/08/06 18:20:39
[node] -> [type]
Paul Berry
2014/08/06 18:32:43
Done.
|
| + */ |
| + T visitTypeDecl(TypeDecl type) => type.accept(this); |
| +} |
| + |
| +/** |
| + * API visitor that visits the entire API hierarchically by default. |
| + */ |
| +class HierarchicalApiVisitor extends ApiVisitor { |
| + /** |
| + * The API to visit. |
| + */ |
| + final Api api; |
| + |
| + HierarchicalApiVisitor(this.api); |
| + |
| + void visitApi() { |
| + api.domains.forEach(visitDomain); |
| + visitTypes(api.types); |
| + visitRefactorings(api.refactorings); |
| + } |
| + |
| + void visitRefactorings(Refactorings refactorings) { |
| + refactorings.forEach(visitRefactoring); |
| + } |
| + |
| + void visitRefactoring(Refactoring refactoring) { |
| + if (refactoring.feedback != null) { |
| + visitTypeDecl(refactoring.feedback); |
| + } |
| + if (refactoring.options != null) { |
| + visitTypeDecl(refactoring.options); |
| + } |
| + } |
| + |
| + void visitTypes(Types types) { |
| + types.forEach(visitTypeDefinition); |
| + } |
| + |
| + void visitDomain(Domain domain) { |
| + domain.requests.forEach(visitRequest); |
| + domain.notifications.forEach(visitNotification); |
| + } |
| + |
| + void visitNotification(Notification notification) { |
| + if (notification.params != null) { |
| + visitTypeDecl(notification.params); |
| + } |
| + } |
| + |
| + void visitRequest(Request request) { |
| + if (request.params != null) { |
| + visitTypeDecl(request.params); |
| + } |
| + if (request.result != null) { |
| + visitTypeDecl(request.result); |
| + } |
| + } |
| + |
| + void visitTypeDefinition(TypeDefinition typeDefinition) { |
| + visitTypeDecl(typeDefinition.type); |
| + } |
| + |
| + @override |
| + void visitTypeEnum(TypeEnum typeEnum) { |
| + typeEnum.values.forEach(visitTypeEnumValue); |
| + } |
| + |
| + void visitTypeEnumValue(TypeEnumValue typeEnumValue) { |
| + } |
| + |
| + @override |
| + void visitTypeList(TypeList typeList) { |
| + visitTypeDecl(typeList.itemType); |
| + } |
| + |
| + @override |
| + void visitTypeMap(TypeMap typeMap) { |
| + visitTypeDecl(typeMap.keyType); |
| + visitTypeDecl(typeMap.valueType); |
| + } |
| + |
| + @override |
| + void visitTypeObject(TypeObject typeObject) { |
| + typeObject.fields.forEach(visitTypeObjectField); |
| + } |
| + |
| + void visitTypeObjectField(TypeObjectField typeObjectField) { |
| + visitTypeDecl(typeObjectField.type); |
| + } |
| + |
| + @override |
| + void visitTypeReference(TypeReference typeReference) { |
| + } |
| + |
| + /** |
| + * If [type] is a [TypeReference] which points to another [TypeReference], |
| + * follow the chain until the last [TypeReference] is reached. |
| + */ |
| + TypeReference resolveTypeReferenceChain(TypeReference type) { |
| + while (api.types.containsKey(type.typeName)) { |
| + TypeDecl referredType = api.types[type.typeName].type; |
| + if (referredType is TypeReference) { |
| + type = referredType; |
| + continue; |
| + } |
| + break; |
| + } |
| + return type; |
| + } |
| +} |
| + |
| +/** |
| + * Base class for objects in the API model |
|
scheglov
2014/08/06 18:20:39
.
Paul Berry
2014/08/06 18:32:43
Done.
|
| + */ |
| +class ApiNode { |
| + /** |
| + * Html element representing this part of the API. |
| + */ |
| + final dom.Element html; |
| + |
| + ApiNode(this.html); |
| +} |
| + |
| +/** |
| + * Toplevel container for the API. |
| + */ |
| +class Api extends ApiNode { |
| + final String version; |
| + final List<Domain> domains; |
| + final Types types; |
| + final Refactorings refactorings; |
| + |
| + Api(this.version, this.domains, this.types, this.refactorings, dom.Element |
| + html) : super(html); |
| +} |
| + |
| +/** |
| + * A collection of refactoring definitions. |
| + */ |
| +class Refactorings extends ApiNode with IterableMixin<Refactoring> { |
| + final List<Refactoring> refactorings; |
| + |
| + Refactorings(this.refactorings, dom.Element html) : super(html); |
| + |
| + @override |
| + Iterator<Refactoring> get iterator => refactorings.iterator; |
| +} |
| + |
| +/** |
| + * Description of a single refactoring. |
| + */ |
| +class Refactoring extends ApiNode { |
| + /** |
| + * Name of the refactoring. This should match one of the values allowed for |
| + * RefactoringKind. |
| + */ |
| + final String kind; |
| + |
| + /** |
| + * Type of the refactoring feedback, or null if the refactoring has no |
| + * feedback. |
| + */ |
| + final TypeObject feedback; |
| + |
| + /** |
| + * Type of the refactoring options, or null if the refactoring has no options. |
| + */ |
| + final TypeObject options; |
| + |
| + Refactoring(this.kind, this.feedback, this.options, dom.Element html) : super( |
| + html); |
| +} |
| + |
| +/** |
| + * A collection of type definitions. |
| + */ |
| +class Types extends ApiNode with IterableMixin<TypeDefinition> { |
| + final Map<String, TypeDefinition> types; |
| + |
| + Types(this.types, dom.Element html) : super(html); |
| + |
| + bool containsKey(String typeName) => types.containsKey(typeName); |
| + |
| + @override |
| + Iterator<TypeDefinition> get iterator => types.values.iterator; |
| + |
| + TypeDefinition operator [](String typeName) => types[typeName]; |
| + |
| + Iterable<String> get keys => types.keys; |
| +} |
| + |
| +/** |
| + * Definition of a single domain. |
| + */ |
| +class Domain extends ApiNode { |
| + final String name; |
| + final List<Request> requests; |
| + final List<Notification> notifications; |
| + |
| + Domain(this.name, this.requests, this.notifications, dom.Element html) : |
| + super(html); |
| +} |
| + |
| +/** |
| + * Description of a request method. |
| + */ |
| +class Request extends ApiNode { |
| + /** |
| + * Name of the domain enclosing this request. |
| + */ |
| + final String domainName; |
| + |
| + /** |
| + * Name of the request, without the domain prefix. |
| + */ |
| + final String method; |
| + |
| + /** |
| + * Type of the object associated with the "params" key in the request object, |
| + * or null if the request has no parameters. |
| + */ |
| + final TypeObject params; |
| + |
| + /** |
| + * Type of the object associated with the "result" key in the response object, |
| + * or null if the response has no results. |
| + */ |
| + final TypeObject result; |
| + |
| + Request(this.domainName, this.method, this.params, this.result, dom.Element |
| + html) : super(html); |
| + |
| + /** |
| + * Get the name of the request, including the domain prefix. |
| + */ |
| + String get longMethod => '$domainName.$method'; |
| + |
| + /** |
| + * Get the full type of the request object, including the common "id" and |
| + * "method" fields. |
| + */ |
| + TypeDecl get requestType { |
| + List<TypeObjectField> fields = [new TypeObjectField('id', new TypeReference( |
| + 'String', null), null), new TypeObjectField('method', new TypeReference( |
| + 'String', null), null, value: '$domainName.$method')]; |
| + if (params != null) { |
| + fields.add(new TypeObjectField('params', params, null)); |
| + } |
| + return new TypeObject(fields, null); |
| + } |
| + |
| + /** |
| + * Get the full type of the response object, including the common "id" and |
| + * "error" fields. |
| + */ |
| + TypeDecl get responseType { |
| + List<TypeObjectField> fields = [new TypeObjectField('id', new TypeReference( |
| + 'String', null), null), new TypeObjectField('error', new TypeReference('Error', |
| + null), null, optional: true)]; |
| + if (result != null) { |
| + fields.add(new TypeObjectField('result', result, null)); |
| + } |
| + return new TypeObject(fields, null); |
| + } |
| +} |
| + |
| +/** |
| + * Description of a request method. |
| + */ |
| +class Notification extends ApiNode { |
| + /** |
| + * Name of the domain enclosing this request. |
| + */ |
| + final String domainName; |
| + |
| + /** |
| + * Name of the notification, without the domain prefix. |
| + */ |
| + final String event; |
| + |
| + /** |
| + * Type of the object associated with the "params" key in the notification |
| + * object, or null if the notification has no parameters. |
| + */ |
| + final TypeObject params; |
| + |
| + Notification(this.domainName, this.event, this.params, dom.Element html) : |
| + super(html); |
| + |
| + /** |
| + * Get the name of the notification, including the domain prefix. |
| + */ |
| + String get longEvent => '$domainName.$event'; |
| + |
| + /** |
| + * Get the full type of the notification object, including the common "id" |
| + * and "error" fields. |
| + */ |
| + TypeDecl get notificationType { |
| + List<TypeObjectField> fields = [new TypeObjectField('event', |
| + new TypeReference('String', null), null, value: '$domainName.$event')]; |
| + if (params != null) { |
| + fields.add(new TypeObjectField('params', params, null)); |
| + } |
| + return new TypeObject(fields, null); |
| + } |
| +} |
| + |
| +/** |
| + * Description of a named type definition. |
| + */ |
| +class TypeDefinition extends ApiNode { |
| + final String name; |
| + final TypeDecl type; |
| + |
| + TypeDefinition(this.name, this.type, dom.Element html) : super(html); |
| +} |
| + |
| +/** |
| + * Base class for all possible types. |
| + */ |
| +abstract class TypeDecl extends ApiNode { |
| + TypeDecl(dom.Element html) : super(html); |
| + |
| + accept(ApiVisitor visitor); |
| +} |
| + |
| +/** |
| + * A reference to a type which is either defined elsewhere in the API or which |
| + * is built-in ([String], [bool], or [int]). |
| + */ |
| +class TypeReference extends TypeDecl { |
| + final String typeName; |
| + |
| + TypeReference(this.typeName, dom.Element html) : super(html) { |
| + if (typeName.isEmpty) { |
| + throw new Exception('Empty type name'); |
| + } |
| + } |
| + |
| + accept(ApiVisitor visitor) => visitor.visitTypeReference(this); |
| +} |
| + |
| +/** |
| + * Type of a JSON object with specified fields, some of which may be optional. |
| + */ |
| +class TypeObject extends TypeDecl { |
| + final List<TypeObjectField> fields; |
| + |
| + TypeObject(this.fields, dom.Element html) : super(html); |
| + |
| + accept(ApiVisitor visitor) => visitor.visitTypeObject(this); |
| +} |
| + |
| +/** |
| + * Description of a single field in a [TypeObject]. |
| + */ |
| +class TypeObjectField extends ApiNode { |
| + final String name; |
| + final TypeDecl type; |
| + final bool optional; |
| + |
| + /** |
| + * Value which the field is required to contain, or null if it may vary. |
| + */ |
| + final Object value; |
| + |
| + TypeObjectField(this.name, this.type, dom.Element html, {this.optional: |
| + false, this.value}) : super(html); |
| +} |
| + |
| +/** |
| + * Type of a JSON list. |
| + */ |
| +class TypeList extends TypeDecl { |
| + final TypeDecl itemType; |
| + |
| + TypeList(this.itemType, dom.Element html) : super(html); |
| + |
| + accept(ApiVisitor visitor) => visitor.visitTypeList(this); |
| +} |
| + |
| +/** |
| + * Type of a JSON map. |
| + */ |
| +class TypeMap extends TypeDecl { |
| + /** |
| + * Type of map keys. Note that since JSON map keys must always be strings, |
| + * this must either be a [TypeReference] for [String], or a [TypeReference] |
| + * to a type which is defined in the API as an enum or a synonym for [String]. |
| + */ |
| + final TypeReference keyType; |
| + |
| + /** |
| + * Type of map values. |
| + */ |
| + final TypeDecl valueType; |
| + |
| + TypeMap(this.keyType, this.valueType, dom.Element html) : super(html); |
| + |
| + accept(ApiVisitor visitor) => visitor.visitTypeMap(this); |
| +} |
| + |
| +/** |
| + * Type of an enum. We represent enums in JSON as strings, so this type |
| + * declaration simply lists the allowed values. |
| + */ |
| +class TypeEnum extends TypeDecl { |
| + final List<TypeEnumValue> values; |
| + |
| + TypeEnum(this.values, dom.Element html) : super(html); |
| + |
| + accept(ApiVisitor visitor) => visitor.visitTypeEnum(this); |
| +} |
| + |
| +/** |
| + * Description of a single allowed value for an enum. |
| + */ |
| +class TypeEnumValue extends ApiNode { |
| + final String value; |
| + |
| + TypeEnumValue(this.value, dom.Element html) : super(html); |
| +} |