blob: 827307966ec5b68d3b2b29f0108518979e9e5055 [file] [log] [blame]
// Copyright (c) 2018, 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.
import 'package:collection/collection.dart';
import 'codegen_dart.dart';
export 'meta_model_cleaner.dart';
export 'meta_model_reader.dart';
bool isLiteralType(TypeBase t) => t is LiteralType;
/// Whether this type is the equivalent of 'Object?' and may also be omitted
/// from JSON ("undefined").
bool isNullableAnyType(TypeBase t) =>
resolveTypeAlias(t).dartTypeWithTypeArgs == 'Object?';
bool isNullType(TypeBase t) =>
resolveTypeAlias(t).dartTypeWithTypeArgs == 'Null';
/// Whether this type is the equivalent of (non-nullable) 'Object'.
bool isObjectType(TypeBase t) =>
resolveTypeAlias(t).dartTypeWithTypeArgs == 'Object';
class ArrayType extends TypeBase {
final TypeBase elementType;
ArrayType(this.elementType);
@override
String get dartType => 'List';
@override
String get typeArgsString => '<${elementType.dartTypeWithTypeArgs}>';
}
/// A constant value parsed from the LSP JSON model.
///
/// Used for well-known values in the spec, such as request Method names and
/// error codes.
class Constant extends Member with LiteralValueMixin {
TypeBase type;
String value;
Constant({
required super.name,
super.comment,
required this.type,
required this.value,
});
String get valueAsLiteral => _asLiteral(value);
}
/// A field parsed from the LSP JSON model.
class Field extends Member {
final TypeBase type;
final bool allowsNull;
final bool allowsUndefined;
Field({
required super.name,
super.comment,
required this.type,
required this.allowsNull,
required this.allowsUndefined,
});
}
class FixedValueField extends Field {
final String value;
FixedValueField({
required super.name,
super.comment,
required this.value,
required super.type,
required super.allowsNull,
required super.allowsUndefined,
});
}
/// An interface/class parsed from the LSP JSON model.
class Interface extends LspEntity {
final List<TypeReference> baseTypes;
final List<Member> members;
Interface({
required super.name,
super.comment,
this.baseTypes = const [],
required this.members,
}) {
baseTypes.sortBy((type) => type.dartTypeWithTypeArgs.toLowerCase());
members.sortBy((member) => member.name.toLowerCase());
}
Interface.inline(String name, List<Member> members)
: this(name: name, members: members);
}
/// A type parsed from the LSP JSON model that has a singe literal value.
class LiteralType extends TypeBase with LiteralValueMixin {
final TypeBase type;
final String _literal;
LiteralType(this.type, this._literal);
@override
String get dartType => type.dartType;
@override
String get typeArgsString => type.typeArgsString;
@override
String get uniqueTypeIdentifier => '$_literal:${super.uniqueTypeIdentifier}';
String get valueAsLiteral => _asLiteral(_literal);
}
/// A special class of Union types where the values are all literals of the same
/// type.
///
/// This allows the Dart field for this type to be the the common base type
/// rather than an EitherX<>.
class LiteralUnionType extends UnionType {
final List<LiteralType> literalTypes;
LiteralUnionType(this.literalTypes) : super(literalTypes);
@override
String get dartType => types.first.dartType;
@override
String get typeArgsString => types.first.typeArgsString;
}
mixin LiteralValueMixin {
/// Returns [value] as the literal Dart code required to represent this value.
String _asLiteral(String value) {
if (num.tryParse(value) == null) {
// Add quotes around strings.
final prefix = value.contains(r'$') ? 'r' : '';
return "$prefix'$value'";
} else {
return value;
}
}
}
/// Base class for named entities (both classes/interfaces and members) parsed
/// from the LSP JSON model.
abstract class LspEntity {
final String name;
final String? comment;
final bool isDeprecated;
LspEntity({
required this.name,
required this.comment,
}) : isDeprecated = comment?.contains('@deprecated') ?? false;
}
/// An enum parsed from the LSP JSON model.
class LspEnum extends LspEntity {
final TypeBase typeOfValues;
final List<Member> members;
LspEnum({
required super.name,
super.comment,
required this.typeOfValues,
required this.members,
}) {
members.sortBy((member) => member.name.toLowerCase());
}
}
class LspMetaModel {
final List<LspEntity> types;
final List<String> methods;
LspMetaModel({required this.types, required this.methods});
}
/// A [Map] type parsed from the LSP JSON model.
class MapType extends TypeBase {
final TypeBase indexType;
final TypeBase valueType;
MapType(this.indexType, this.valueType);
@override
String get dartType => 'Map';
@override
String get typeArgsString =>
'<${indexType.dartTypeWithTypeArgs}, ${valueType.dartTypeWithTypeArgs}>';
}
/// Base class for members ([Constant] and [Fields]s) parsed from the LSP JSON
/// model.
abstract class Member extends LspEntity {
Member({
required super.name,
super.comment,
});
}
class NullableType extends TypeBase {
final TypeBase baseType;
NullableType(this.baseType);
@override
String get dartType => baseType.dartType;
@override
String get dartTypeWithTypeArgs => '${super.dartTypeWithTypeArgs}?';
@override
String get typeArgsString => baseType.typeArgsString;
}
class TypeAlias extends LspEntity {
final TypeBase baseType;
/// Whether this alias is just a simple rename and not a name for a more
/// complex type.
///
/// Renames will be followed when generating code, but other aliases may be
/// created as `typedef`s.
final bool isRename;
TypeAlias({
required super.name,
super.comment,
required this.baseType,
required this.isRename,
});
}
/// Base class for a Type parsed from the LSP JSON model.
abstract class TypeBase {
String get dartType;
String get dartTypeWithTypeArgs => '$dartType$typeArgsString';
String get typeArgsString;
/// A unique identifier for this type. Used for folding types together
/// (for example two types that resolve to "Object?" in Dart).
String get uniqueTypeIdentifier => dartTypeWithTypeArgs;
}
/// A reference to a Type by name.
class TypeReference extends TypeBase {
static final TypeBase Undefined = TypeReference('undefined');
static final TypeBase Null_ = TypeReference('Null');
/// Any object (but not null).
static final TypeBase LspObject = TypeReference('Object');
/// Any object (or null/undefined).
static final TypeBase LspAny = NullableType(TypeReference('Object'));
final String name;
final List<TypeBase> typeArgs;
TypeReference(this.name, {this.typeArgs = const []}) {
if (name == 'Array' || name.endsWith('[]')) {
throw 'Type should not be used for arrays, use ArrayType instead';
}
}
@override
String get dartType {
// Resolve any renames when asked for our type.
final resolvedType = resolveTypeAlias(this, onlyRenames: true);
if (resolvedType != this) {
return resolvedType.dartType;
}
const mapping = <String, String>{
'boolean': 'bool',
'string': 'String',
'number': 'num',
'integer': 'int',
'null': 'Null',
// Map decimal to num because clients may sent "1.0" or "1" and we want
// to consider both valid.
'decimal': 'num',
'uinteger': 'int',
'object': 'Object?',
// Simplify MarkedString from
// string | { language: string; value: string }
// to just String
'MarkedString': 'String'
};
final typeName = mapping[name] ?? name;
return typeName;
}
@override
String get typeArgsString {
// Resolve any renames when asked for our type.
final resolvedType = resolveTypeAlias(this, onlyRenames: true);
if (resolvedType != this) {
return resolvedType.typeArgsString;
}
return typeArgs.isNotEmpty
? '<${typeArgs.map((t) => t.dartTypeWithTypeArgs).join(', ')}>'
: '';
}
}
/// A union type parsed from the LSP JSON model.
///
/// Union types will be represented in Dart using a custom `EitherX<A, B, ...>`
/// class.
class UnionType extends TypeBase {
final List<TypeBase> types;
UnionType(this.types) {
// Ensure types are always sorted alphabetically to simplify sharing code
// because `Either2<A, B>` and `Either2<B, A>` are not the same.
types.sortBy((type) => type.dartTypeWithTypeArgs.toLowerCase());
}
@override
String get dartType {
if (types.length > 4) {
throw 'Unions of more than 4 types are not supported.';
}
return 'Either${types.length}';
}
@override
String get typeArgsString {
final typeArgs = types.map((t) => t.dartTypeWithTypeArgs).join(', ');
return '<$typeArgs>';
}
}