-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathmeta_model.dart
348 lines (285 loc) · 9.16 KB
/
meta_model.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
// 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,
super.isProposed,
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,
super.isProposed,
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;
final bool abstract;
Interface({
required super.name,
super.comment,
super.isProposed,
this.baseTypes = const [],
required this.members,
this.abstract = false,
}) {
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 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.
var 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 isProposed;
final bool isDeprecated;
LspEntity({
required this.name,
required this.comment,
this.isProposed = false,
}) : 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,
super.isProposed,
required this.typeOfValues,
required this.members,
}) {
members.sortBy((member) => member.name.toLowerCase());
}
}
class LspMetaModel {
final List<LspEntity> types;
final List<Constant> 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, super.isProposed});
}
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 should be resolved to its base type when generating
/// code.
final bool renameReferences;
/// Whether a typedef should be created for this alias.
final bool generateTypeDef;
TypeAlias({
required super.name,
super.comment,
super.isProposed,
required this.baseType,
required this.renameReferences,
bool? generateTypeDef,
}) : generateTypeDef = generateTypeDef ?? !renameReferences;
}
/// 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');
static final TypeBase string = TypeReference('string');
static final TypeBase int = TypeReference('int');
/// Any object (but not null).
static final TypeBase lspObject = TypeReference('Object');
/// Any object (or null/undefined).
static final TypeBase lspAny = NullableType(TypeReference.lspObject);
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.
var 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',
};
var typeName = mapping[name] ?? name;
return typeName;
}
@override
String get typeArgsString {
// Resolve any renames when asked for our type.
var 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 {
var typeArgs = types.map((t) => t.dartTypeWithTypeArgs).join(', ');
return '<$typeArgs>';
}
}