1ad68b9b16
As per our earlier conversation, I looked for places where a local variable was introduced to overcome field promotion and has the same name as the field. Where possible I made them final; where not possible I changed the name. Change-Id: Iad3d2139693ca66f1eb22a3926c3b84a4115f13d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/196552 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
468 lines
14 KiB
Dart
468 lines
14 KiB
Dart
// 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.
|
|
import 'dart:collection';
|
|
|
|
import 'package:html/dom.dart' as dom;
|
|
|
|
/// 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,
|
|
{bool experimental = false})
|
|
: super(html, experimental: experimental, deprecated: false);
|
|
}
|
|
|
|
/// Base class for objects in the API model.
|
|
class ApiNode {
|
|
/// A flag to indicate if this API is experimental.
|
|
final bool experimental;
|
|
|
|
/// A flag to indicate if this API is deprecated.
|
|
final bool deprecated;
|
|
|
|
/// Html element representing this part of the API, `null` if built-in.
|
|
final dom.Element? html;
|
|
|
|
ApiNode(this.html, {this.experimental = false, this.deprecated = false});
|
|
}
|
|
|
|
/// Base class for visiting the API definition.
|
|
abstract class ApiVisitor<T> {
|
|
/// Dispatch the given [type] to the visitor.
|
|
T visitTypeDecl(TypeDecl type) => type.accept(this);
|
|
T visitTypeEnum(TypeEnum typeEnum);
|
|
T visitTypeList(TypeList typeList);
|
|
T visitTypeMap(TypeMap typeMap);
|
|
T visitTypeObject(TypeObject typeObject);
|
|
T visitTypeReference(TypeReference typeReference);
|
|
|
|
T visitTypeUnion(TypeUnion typeUnion);
|
|
}
|
|
|
|
/// 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,
|
|
{bool experimental = false, bool deprecated = false})
|
|
: super(html, experimental: experimental, deprecated: deprecated);
|
|
|
|
@override
|
|
dom.Element get html => super.html!;
|
|
}
|
|
|
|
/// API visitor that visits the entire API hierarchically by default.
|
|
class HierarchicalApiVisitor extends ApiVisitor {
|
|
/// The API to visit.
|
|
final Api api;
|
|
|
|
HierarchicalApiVisitor(this.api);
|
|
|
|
/// If [type] is a [TypeReference] that is defined in the API, follow the
|
|
/// chain until a non-[TypeReference] is found, if possible.
|
|
///
|
|
/// If it is not possible (because the chain ends with a [TypeReference] that
|
|
/// is not defined in the API), then that final [TypeReference] is returned.
|
|
TypeDecl resolveTypeReferenceChain(TypeDecl type) {
|
|
while (type is TypeReference) {
|
|
var newTypeRef = api.types[type.typeName];
|
|
if (newTypeRef == null) {
|
|
break;
|
|
}
|
|
type = newTypeRef.type;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
void visitApi() {
|
|
api.domains.forEach(visitDomain);
|
|
visitTypes(api.types);
|
|
visitRefactorings(api.refactorings);
|
|
}
|
|
|
|
void visitDomain(Domain domain) {
|
|
domain.requests.forEach(visitRequest);
|
|
domain.notifications.forEach(visitNotification);
|
|
}
|
|
|
|
void visitNotification(Notification notification) {
|
|
var params = notification.params;
|
|
if (params != null) {
|
|
visitTypeDecl(params);
|
|
}
|
|
}
|
|
|
|
void visitRefactoring(Refactoring refactoring) {
|
|
var feedback = refactoring.feedback;
|
|
if (feedback != null) {
|
|
visitTypeDecl(feedback);
|
|
}
|
|
|
|
var options = refactoring.options;
|
|
if (options != null) {
|
|
visitTypeDecl(options);
|
|
}
|
|
}
|
|
|
|
void visitRefactorings(Refactorings refactorings) {
|
|
refactorings.forEach(visitRefactoring);
|
|
}
|
|
|
|
void visitRequest(Request request) {
|
|
var params = request.params;
|
|
if (params != null) {
|
|
visitTypeDecl(params);
|
|
}
|
|
|
|
var result = request.result;
|
|
if (result != null) {
|
|
visitTypeDecl(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) {}
|
|
|
|
void visitTypes(Types types) {
|
|
types.forEach(visitTypeDefinition);
|
|
}
|
|
|
|
@override
|
|
void visitTypeUnion(TypeUnion typeUnion) {
|
|
typeUnion.choices.forEach(visitTypeDecl);
|
|
}
|
|
}
|
|
|
|
/// Description of a notification 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,
|
|
{bool experimental = false})
|
|
: super(html, experimental: experimental, deprecated: false);
|
|
|
|
/// 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 {
|
|
var fields = <TypeObjectField>[
|
|
TypeObjectField('event', TypeReference('String', null), null,
|
|
value: '$domainName.$event')
|
|
];
|
|
|
|
final params = this.params;
|
|
if (params != null) {
|
|
fields.add(TypeObjectField('params', params, null));
|
|
}
|
|
|
|
return TypeObject(fields, null);
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
{bool experimental = false})
|
|
: super(html, experimental: experimental, deprecated: false);
|
|
}
|
|
|
|
/// A collection of refactoring definitions.
|
|
class Refactorings extends ApiNode with IterableMixin<Refactoring> {
|
|
final List<Refactoring> refactorings;
|
|
|
|
Refactorings(this.refactorings, dom.Element? html,
|
|
{bool experimental = false})
|
|
: super(html, experimental: experimental, deprecated: false);
|
|
|
|
@override
|
|
Iterator<Refactoring> get iterator => refactorings.iterator;
|
|
}
|
|
|
|
/// 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,
|
|
{bool experimental = false, bool deprecated = false})
|
|
: super(html, experimental: experimental, deprecated: deprecated);
|
|
|
|
/// 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 {
|
|
var fields = <TypeObjectField>[
|
|
TypeObjectField('id', TypeReference('String', null), null),
|
|
TypeObjectField('method', TypeReference('String', null), null,
|
|
value: '$domainName.$method')
|
|
];
|
|
|
|
final params = this.params;
|
|
if (params != null) {
|
|
fields.add(TypeObjectField('params', params, null));
|
|
}
|
|
|
|
return TypeObject(fields, null);
|
|
}
|
|
|
|
/// Get the full type of the response object, including the common "id" and
|
|
/// "error" fields.
|
|
TypeDecl get responseType {
|
|
var fields = <TypeObjectField>[
|
|
TypeObjectField('id', TypeReference('String', null), null),
|
|
TypeObjectField('error', TypeReference('RequestError', null), null,
|
|
optional: true)
|
|
];
|
|
|
|
final result = this.result;
|
|
if (result != null) {
|
|
fields.add(TypeObjectField('result', result, null));
|
|
}
|
|
|
|
return TypeObject(fields, null);
|
|
}
|
|
}
|
|
|
|
/// Base class for all possible types.
|
|
abstract class TypeDecl extends ApiNode {
|
|
TypeDecl(dom.Element? html,
|
|
{bool experimental = false, bool deprecated = false})
|
|
: super(html, experimental: experimental, deprecated: deprecated);
|
|
|
|
T accept<T>(ApiVisitor<T> visitor);
|
|
}
|
|
|
|
/// Description of a named type definition.
|
|
class TypeDefinition extends ApiNode {
|
|
final String name;
|
|
final TypeDecl type;
|
|
|
|
bool isExternal = false;
|
|
|
|
TypeDefinition(this.name, this.type, dom.Element html,
|
|
{bool experimental = false, bool deprecated = false})
|
|
: super(html, experimental: experimental, deprecated: deprecated);
|
|
}
|
|
|
|
/// 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,
|
|
{bool experimental = false, bool deprecated = false})
|
|
: super(html, experimental: experimental, deprecated: deprecated);
|
|
|
|
@override
|
|
T accept<T>(ApiVisitor<T> 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,
|
|
{bool experimental = false, bool deprecated = false})
|
|
: super(html, experimental: experimental, deprecated: deprecated);
|
|
|
|
@override
|
|
dom.Element get html => super.html!;
|
|
}
|
|
|
|
/// Type of a JSON list.
|
|
class TypeList extends TypeDecl {
|
|
final TypeDecl itemType;
|
|
|
|
TypeList(this.itemType, dom.Element html, {bool experimental = false})
|
|
: super(html, experimental: experimental, deprecated: false);
|
|
|
|
@override
|
|
T accept<T>(ApiVisitor<T> 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,
|
|
{bool experimental = false})
|
|
: super(html, experimental: experimental, deprecated: false);
|
|
|
|
@override
|
|
T accept<T>(ApiVisitor<T> visitor) => visitor.visitTypeMap(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,
|
|
{bool experimental = false, bool deprecated = false})
|
|
: super(html, experimental: experimental, deprecated: deprecated);
|
|
|
|
@override
|
|
T accept<T>(ApiVisitor<T> visitor) => visitor.visitTypeObject(this);
|
|
|
|
/// Return the field with the given [name], or null if there is no such field.
|
|
TypeObjectField? getField(String name) {
|
|
for (var field in fields) {
|
|
if (field.name == name) {
|
|
return field;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Description of a single field in a [TypeObject].
|
|
class TypeObjectField extends ApiNode {
|
|
final String name;
|
|
final TypeDecl type;
|
|
final bool optional;
|
|
|
|
/// Value that 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,
|
|
bool experimental = false,
|
|
bool deprecated = false})
|
|
: super(html, experimental: experimental, deprecated: deprecated);
|
|
}
|
|
|
|
/// 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, {bool experimental = false})
|
|
: super(html, experimental: experimental, deprecated: false) {
|
|
if (typeName.isEmpty) {
|
|
throw Exception('Empty type name');
|
|
}
|
|
}
|
|
|
|
@override
|
|
T accept<T>(ApiVisitor<T> visitor) => visitor.visitTypeReference(this);
|
|
}
|
|
|
|
/// A collection of type definitions.
|
|
class Types extends ApiNode with IterableMixin<TypeDefinition> {
|
|
final Map<String, TypeDefinition> types;
|
|
|
|
List<String> importUris = <String>[];
|
|
|
|
Types(this.types, dom.Element? html, {bool experimental = false})
|
|
: super(html, experimental: experimental, deprecated: false);
|
|
|
|
@override
|
|
Iterator<TypeDefinition> get iterator => types.values.iterator;
|
|
|
|
Iterable<String> get keys => types.keys;
|
|
|
|
TypeDefinition? operator [](String typeName) => types[typeName];
|
|
|
|
bool containsKey(String typeName) => types.containsKey(typeName);
|
|
}
|
|
|
|
/// Type which represents a union among multiple choices.
|
|
class TypeUnion extends TypeDecl {
|
|
final List<TypeDecl> choices;
|
|
|
|
/// The field that is used to disambiguate this union
|
|
final String field;
|
|
|
|
TypeUnion(this.choices, this.field, dom.Element html,
|
|
{bool experimental = false})
|
|
: super(html, experimental: experimental, deprecated: false);
|
|
|
|
@override
|
|
T accept<T>(ApiVisitor<T> visitor) => visitor.visitTypeUnion(this);
|
|
}
|