[analysis_server] Preserve type aliases from the LSP spec as typedefs in generated code
+ improve the use of LSPAny/LSPObject where LSPAny = anything, including null or undefined LSPObject = any object (equiv of non-null Map<String, Object?>) Change-Id: I335b299aad8e58b1cb4ee33cf27dc0dd887ec916 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/248781 Commit-Queue: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
committed by
Commit Bot
parent
73bc88f957
commit
b46a2d3039
@@ -16,6 +16,12 @@ import 'package:analysis_server/src/protocol/protocol_internal.dart';
|
||||
|
||||
const jsonEncoder = JsonEncoder.withIndent(' ');
|
||||
|
||||
typedef DocumentUri = String;
|
||||
typedef LSPAny = Object?;
|
||||
typedef LSPObject = Object;
|
||||
typedef TextDocumentEditEdits
|
||||
= List<Either3<AnnotatedTextEdit, SnippetTextEdit, TextEdit>>;
|
||||
|
||||
class AnalyzerStatusParams implements ToJsonable {
|
||||
static const jsonHandler = LspJsonHandler(
|
||||
AnalyzerStatusParams.canParse,
|
||||
@@ -761,7 +767,7 @@ class IncomingMessage implements Message, ToJsonable {
|
||||
@override
|
||||
final String jsonrpc;
|
||||
final Method method;
|
||||
final Object? params;
|
||||
final LSPAny params;
|
||||
|
||||
@override
|
||||
Map<String, Object?> toJson() {
|
||||
@@ -925,7 +931,7 @@ class NotificationMessage implements IncomingMessage, ToJsonable {
|
||||
@override
|
||||
final Method method;
|
||||
@override
|
||||
final Object? params;
|
||||
final LSPAny params;
|
||||
|
||||
@override
|
||||
Map<String, Object?> toJson() {
|
||||
@@ -1363,7 +1369,7 @@ class RequestMessage implements IncomingMessage, ToJsonable {
|
||||
@override
|
||||
final Method method;
|
||||
@override
|
||||
final Object? params;
|
||||
final LSPAny params;
|
||||
|
||||
@override
|
||||
Map<String, Object?> toJson() {
|
||||
@@ -1548,7 +1554,7 @@ class ResponseMessage implements Message, ToJsonable {
|
||||
final Either2<int, String>? id;
|
||||
@override
|
||||
final String jsonrpc;
|
||||
final Object? result;
|
||||
final LSPAny result;
|
||||
|
||||
@override
|
||||
Map<String, Object?> toJson() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:analysis_server/lsp_protocol/protocol.dart';
|
||||
import 'package:analysis_server/lsp_protocol/protocol.dart' hide Declaration;
|
||||
import 'package:analysis_server/protocol/protocol_generated.dart';
|
||||
import 'package:analysis_server/src/domains/completion/available_suggestions.dart';
|
||||
import 'package:analysis_server/src/lsp/client_capabilities.dart';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:analysis_server/lsp_protocol/protocol.dart';
|
||||
import 'package:analysis_server/lsp_protocol/protocol.dart' hide Declaration;
|
||||
import 'package:analysis_server/lsp_protocol/protocol.dart' as lsp;
|
||||
import 'package:analysis_server/src/collections.dart';
|
||||
import 'package:analysis_server/src/lsp/client_capabilities.dart';
|
||||
|
||||
@@ -1753,7 +1753,7 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
|
||||
Future<ResponseMessage> sendDidChangeConfiguration() {
|
||||
final request = makeRequest(
|
||||
Method.workspace_didChangeConfiguration,
|
||||
DidChangeConfigurationParams(),
|
||||
DidChangeConfigurationParams(settings: {}),
|
||||
);
|
||||
return sendRequestToServer(request);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ void main() {
|
||||
test('handles basic types', () {
|
||||
expect(_simple('string').dartType, equals('String'));
|
||||
expect(_simple('boolean').dartType, equals('bool'));
|
||||
expect(_simple('any').dartType, equals('Object?'));
|
||||
expect(_simple('object').dartType, equals('Object?'));
|
||||
expect(_simple('int').dartType, equals('int'));
|
||||
expect(_simple('num').dartType, equals('num'));
|
||||
|
||||
@@ -4,11 +4,18 @@
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../../tool/lsp_spec/codegen_dart.dart';
|
||||
import '../../../tool/lsp_spec/generate_all.dart';
|
||||
import '../../../tool/lsp_spec/meta_model.dart';
|
||||
import 'matchers.dart';
|
||||
|
||||
void main() {
|
||||
group('meta model reader', () {
|
||||
setUpAll(() {
|
||||
// Ensure any custom types like LSPAny are registered so that they can
|
||||
// be resolved.
|
||||
recordTypes(getCustomClasses());
|
||||
});
|
||||
test('reads an interface', () {
|
||||
final input = {
|
||||
"structures": [
|
||||
@@ -445,7 +452,7 @@ Sometimes after a blank line we'll have a note.
|
||||
expect(union.types[1], isSimpleType('string'));
|
||||
});
|
||||
|
||||
test('reads an union including LSPObject into a single type', () {
|
||||
test('reads an union including LSPAny into a single type', () {
|
||||
final input = {
|
||||
"structures": [
|
||||
{
|
||||
@@ -457,7 +464,7 @@ Sometimes after a blank line we'll have a note.
|
||||
"kind": "or",
|
||||
"items": [
|
||||
{"kind": "base", "name": "string"},
|
||||
{"kind": "base", "name": "LSPObject"},
|
||||
{"kind": "base", "name": "LSPAny"},
|
||||
]
|
||||
},
|
||||
},
|
||||
@@ -473,7 +480,7 @@ Sometimes after a blank line we'll have a note.
|
||||
final field = interface.members.first as Field;
|
||||
expect(field, const TypeMatcher<Field>());
|
||||
expect(field.name, equals('label'));
|
||||
expect(field.type, isSimpleType('LSPObject'));
|
||||
expect(field.type, isSimpleType('LSPAny'));
|
||||
});
|
||||
|
||||
test('reads literal string values', () {
|
||||
|
||||
@@ -43,7 +43,15 @@ String generateDartForTypes(List<LspEntity> types) {
|
||||
_canParseFunctions.clear();
|
||||
_unionFunctions.clear();
|
||||
final buffer = IndentableStringBuffer();
|
||||
_getSortedUnique(types).forEach((t) => _writeType(buffer, t));
|
||||
final sortedTypes = _getSortedUnique(types);
|
||||
// Bump typedefs to the top.
|
||||
final fileSortedTypes = [
|
||||
...sortedTypes.whereType<TypeAlias>(),
|
||||
...sortedTypes.where((type) => type is! TypeAlias),
|
||||
];
|
||||
for (var type in fileSortedTypes) {
|
||||
_writeType(buffer, type);
|
||||
}
|
||||
for (var function in _canParseFunctions.values) {
|
||||
buffer.writeln(function);
|
||||
}
|
||||
@@ -79,9 +87,16 @@ void recordTypes(List<LspEntity> types) {
|
||||
_sortSubtypes();
|
||||
}
|
||||
|
||||
TypeBase resolveTypeAlias(TypeBase type, {bool resolveEnumClasses = false}) {
|
||||
/// Resolves [type] to its base type if it is a reference to another type.
|
||||
///
|
||||
/// If [resolveEnums] is `true`, will resolve them to the type of their values.
|
||||
///
|
||||
/// If [onlyRenames] is true, references to [TypeAlias]es will only be resolved
|
||||
/// if they are renames.
|
||||
TypeBase resolveTypeAlias(TypeBase type,
|
||||
{bool resolveEnums = false, bool onlyRenames = false}) {
|
||||
if (type is TypeReference) {
|
||||
if (resolveEnumClasses) {
|
||||
if (resolveEnums) {
|
||||
// Enums are no longer recorded with TypeAliases (as they were in the
|
||||
// Markdown/TS spec) so must be resolved explicitly to their base types.
|
||||
final enum_ = _namespaces[type.name];
|
||||
@@ -89,19 +104,20 @@ TypeBase resolveTypeAlias(TypeBase type, {bool resolveEnumClasses = false}) {
|
||||
return enum_.typeOfValues;
|
||||
}
|
||||
}
|
||||
// The LSP spec contains type aliases for `integer` and `uinteger` that map
|
||||
// into the `number` type, with comments stating they must be integers. To
|
||||
// preserve the improved typing, do _not_ resolve them to the `number`
|
||||
// type.
|
||||
if (type.name == 'integer' || type.name == 'uinteger') {
|
||||
return type;
|
||||
}
|
||||
|
||||
final alias = _typeAliases[type.name];
|
||||
// Only follow the type if we're not an enum, or we wanted to follow enums.
|
||||
if (alias != null &&
|
||||
(!_namespaces.containsKey(alias.name) || resolveEnumClasses)) {
|
||||
return alias.baseType;
|
||||
if (alias != null && (!onlyRenames || alias.isRename)) {
|
||||
// Resolve aliases recursively.
|
||||
var resolved = alias.baseType;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
final newResolved = resolveTypeAlias(resolved,
|
||||
resolveEnums: resolveEnums, onlyRenames: onlyRenames);
|
||||
if (newResolved == resolved) {
|
||||
return resolved;
|
||||
}
|
||||
resolved = newResolved;
|
||||
}
|
||||
throw 'Failed to resolve type after 10 iterations: ${alias.name}';
|
||||
}
|
||||
}
|
||||
return type;
|
||||
@@ -215,7 +231,8 @@ bool _isSimpleType(TypeBase type) {
|
||||
bool _isSpecType(TypeBase type) {
|
||||
type = resolveTypeAlias(type);
|
||||
return type is TypeReference &&
|
||||
!isAnyType(type) &&
|
||||
type != TypeReference.LspObject &&
|
||||
type != TypeReference.LspAny &&
|
||||
(_interfaces.containsKey(type.name) ||
|
||||
(_namespaces.containsKey(type.name)));
|
||||
}
|
||||
@@ -246,14 +263,17 @@ String _memberNameForType(TypeBase type) {
|
||||
if (type is TypeReference) {
|
||||
type = resolveTypeAlias(type);
|
||||
}
|
||||
var dartType = type is UnionType
|
||||
? type.types.map(_memberNameForType).join()
|
||||
: type is ArrayType
|
||||
? 'List${_memberNameForType(type.elementType)}'
|
||||
: type is MapType
|
||||
? 'Map${_memberNameForType(type.indexType)}${_memberNameForType(type.valueType)}'
|
||||
: type.dartType;
|
||||
return capitalize(dartType.replaceAll('?', '_'));
|
||||
|
||||
var dartType = type is NullableType
|
||||
? '${type.dartType}?'
|
||||
: type is UnionType
|
||||
? type.types.map(_memberNameForType).join()
|
||||
: type is ArrayType
|
||||
? 'List${_memberNameForType(type.elementType)}'
|
||||
: type is MapType
|
||||
? 'Map${_memberNameForType(type.indexType)}${_memberNameForType(type.valueType)}'
|
||||
: type.dartType;
|
||||
return capitalize(dartType.replaceAll('?', 'Nullable'));
|
||||
}
|
||||
|
||||
String _rewriteCommentReference(String comment) {
|
||||
@@ -308,8 +328,7 @@ void _sortSubtypes() {
|
||||
String _specJsonType(TypeBase type) {
|
||||
if (type is TypeReference && _namespaces.containsKey(type.name)) {
|
||||
final valueType = _namespaces[type.name]!.typeOfValues;
|
||||
return resolveTypeAlias(valueType, resolveEnumClasses: true)
|
||||
.dartTypeWithTypeArgs;
|
||||
return valueType.dartTypeWithTypeArgs;
|
||||
}
|
||||
return 'Map<String, Object?>';
|
||||
}
|
||||
@@ -346,8 +365,9 @@ void _writeCanParseMethod(IndentableStringBuffer buffer, Interface interface) {
|
||||
// In order to consider this valid for parsing, all fields that must not be
|
||||
// undefined must be present and also type check for the correct type.
|
||||
// Any fields that are optional but present, must still type check.
|
||||
final fields =
|
||||
_getAllFields(interface).whereNot((f) => isAnyType(f.type)).toList();
|
||||
final fields = _getAllFields(interface)
|
||||
.whereNot((f) => isNullableAnyType(f.type))
|
||||
.toList();
|
||||
for (var i = 0; i < fields.length; i++) {
|
||||
final field = fields[i];
|
||||
var type = field.type;
|
||||
@@ -463,7 +483,7 @@ void _writeConstructor(IndentableStringBuffer buffer, Interface interface) {
|
||||
final isRequired = !isLiteral &&
|
||||
!field.allowsNull &&
|
||||
!field.allowsUndefined &&
|
||||
!isAnyType(field.type);
|
||||
!isNullableAnyType(field.type);
|
||||
final requiredKeyword = isRequired ? 'required' : '';
|
||||
final valueCode =
|
||||
isLiteral ? ' = ${(field.type as LiteralType).valueAsLiteral}' : '';
|
||||
@@ -633,8 +653,8 @@ void _writeEqualsExpression(IndentableStringBuffer buffer, TypeBase type,
|
||||
void _writeField(
|
||||
IndentableStringBuffer buffer, Interface interface, Field field) {
|
||||
_writeDocCommentsAndAnnotations(buffer, field);
|
||||
final needsNullable =
|
||||
(field.allowsNull || field.allowsUndefined) && !isAnyType(field.type);
|
||||
final needsNullable = (field.allowsNull || field.allowsUndefined) &&
|
||||
!isNullableAnyType(field.type);
|
||||
if (_isOverride(interface, field)) {
|
||||
buffer.writeIndentedln('@override');
|
||||
}
|
||||
@@ -654,7 +674,11 @@ void _writeFromJsonCode(
|
||||
}) {
|
||||
type = resolveTypeAlias(type);
|
||||
final nullOperator = allowsNull ? '?' : '';
|
||||
final cast = requiresCast && type.dartTypeWithTypeArgs != 'Object?'
|
||||
final cast = requiresCast &&
|
||||
// LSPAny
|
||||
!isNullableAnyType(type) &&
|
||||
// LSPObject marked as optional
|
||||
!(isObjectType(type) && allowsNull)
|
||||
? ' as ${type.dartTypeWithTypeArgs}$nullOperator'
|
||||
: '';
|
||||
|
||||
@@ -741,7 +765,7 @@ void _writeFromJsonCodeForUnion(
|
||||
|
||||
for (var i = 0; i < union.types.length; i++) {
|
||||
final type = union.types[i];
|
||||
final isAny = isAnyType(type);
|
||||
final isAny = isNullableAnyType(type);
|
||||
|
||||
// "any" matches all type checks, so only emit it if required.
|
||||
if (!isAny) {
|
||||
@@ -1028,15 +1052,20 @@ void _writeType(IndentableStringBuffer buffer, LspEntity type) {
|
||||
} else if (type is LspEnum) {
|
||||
_writeEnumClass(buffer, type);
|
||||
} else if (type is TypeAlias) {
|
||||
// For now type aliases are not supported, so are collected at the start
|
||||
// of the process in a map, and just replaced with the aliased type during
|
||||
// generation.
|
||||
// _writeTypeAlias(buffer, type);
|
||||
_writeTypeAlias(buffer, type);
|
||||
} else {
|
||||
throw 'Unknown type';
|
||||
}
|
||||
}
|
||||
|
||||
void _writeTypeAlias(IndentableStringBuffer buffer, TypeAlias alias) {
|
||||
if (alias.isRename) return;
|
||||
final baseType = alias.baseType;
|
||||
final typeName = baseType.dartTypeWithTypeArgs;
|
||||
_writeDocCommentsAndAnnotations(buffer, alias);
|
||||
buffer.writeIndentedln('typedef ${alias.name} = $typeName;');
|
||||
}
|
||||
|
||||
void _writeTypeCheckCondition(IndentableStringBuffer buffer,
|
||||
Interface? interface, String valueCode, TypeBase type, String reporter,
|
||||
{bool negation = false, bool parenForCollection = false}) {
|
||||
@@ -1049,8 +1078,11 @@ void _writeTypeCheckCondition(IndentableStringBuffer buffer,
|
||||
final and = negation ? '||' : '&&';
|
||||
final every = negation ? 'any' : 'every';
|
||||
|
||||
if (fullDartType == 'Object?') {
|
||||
if (isNullableAnyType(type)) {
|
||||
buffer.write(negation ? 'false' : 'true');
|
||||
} else if (isObjectType(type)) {
|
||||
final notEqual = negation ? '==' : '!=';
|
||||
buffer.write('$valueCode $notEqual null');
|
||||
} else if (_isSimpleType(type)) {
|
||||
buffer.write('$valueCode is$operator $fullDartType');
|
||||
} else if (type is LiteralType) {
|
||||
|
||||
@@ -142,11 +142,13 @@ List<LspEntity> getCustomClasses() {
|
||||
final customTypes = <LspEntity>[
|
||||
TypeAlias(
|
||||
name: 'LSPAny',
|
||||
baseType: TypeReference.Any,
|
||||
baseType: TypeReference.LspAny,
|
||||
isRename: false,
|
||||
),
|
||||
TypeAlias(
|
||||
name: 'LSPObject',
|
||||
baseType: TypeReference.Any,
|
||||
baseType: TypeReference.LspObject,
|
||||
isRename: false,
|
||||
),
|
||||
// The DocumentFilter more complex in v3.17's meta_model (to allow
|
||||
// TextDocumentFilters to be guaranteed to have at least one of language,
|
||||
@@ -156,6 +158,7 @@ List<LspEntity> getCustomClasses() {
|
||||
TypeAlias(
|
||||
name: 'DocumentFilter',
|
||||
baseType: TypeReference('TextDocumentFilter2'),
|
||||
isRename: true,
|
||||
),
|
||||
// Similarly, the meta_model includes String as an option for
|
||||
// DocumentSelector which is deprecated and we never previously supported
|
||||
@@ -165,6 +168,7 @@ List<LspEntity> getCustomClasses() {
|
||||
TypeAlias(
|
||||
name: 'DocumentSelector',
|
||||
baseType: ArrayType(TypeReference('TextDocumentFilterWithScheme')),
|
||||
isRename: true,
|
||||
),
|
||||
interface('Message', [
|
||||
field('jsonrpc', type: 'string'),
|
||||
@@ -231,6 +235,7 @@ List<LspEntity> getCustomClasses() {
|
||||
TypeAlias(
|
||||
name: 'DocumentUri',
|
||||
baseType: TypeReference('string'),
|
||||
isRename: false,
|
||||
),
|
||||
|
||||
interface('DartDiagnosticServer', [field('port', type: 'int')]),
|
||||
@@ -335,6 +340,7 @@ List<LspEntity> getCustomClasses() {
|
||||
TypeReference('TextEdit'),
|
||||
]),
|
||||
),
|
||||
isRename: false,
|
||||
)
|
||||
];
|
||||
return customTypes;
|
||||
|
||||
@@ -9,19 +9,19 @@ import 'codegen_dart.dart';
|
||||
export 'meta_model_cleaner.dart';
|
||||
export 'meta_model_reader.dart';
|
||||
|
||||
/// Whether this type allows any value (including null).
|
||||
bool isAnyType(TypeBase t) =>
|
||||
t is TypeReference &&
|
||||
(t.name == 'any' ||
|
||||
t.name == 'LSPAny' ||
|
||||
t.name == 'object' ||
|
||||
t.name == 'LSPObject');
|
||||
|
||||
bool isLiteralType(TypeBase t) => t is LiteralType;
|
||||
|
||||
bool isNullType(TypeBase t) => t is TypeReference && t.name == 'null';
|
||||
/// 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 isUndefinedType(TypeBase t) => t is TypeReference && t.name == 'undefined';
|
||||
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;
|
||||
@@ -202,12 +202,35 @@ abstract class Member extends LspEntity {
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -215,6 +238,7 @@ class TypeAlias extends LspEntity {
|
||||
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
|
||||
@@ -225,8 +249,13 @@ abstract class TypeBase {
|
||||
/// 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 Any = TypeReference('any');
|
||||
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;
|
||||
|
||||
@@ -238,8 +267,8 @@ class TypeReference extends TypeBase {
|
||||
|
||||
@override
|
||||
String get dartType {
|
||||
// Always resolve type aliases when asked for our Dart type.
|
||||
final resolvedType = resolveTypeAlias(this);
|
||||
// Resolve any renames when asked for our type.
|
||||
final resolvedType = resolveTypeAlias(this, onlyRenames: true);
|
||||
if (resolvedType != this) {
|
||||
return resolvedType.dartType;
|
||||
}
|
||||
@@ -249,14 +278,12 @@ class TypeReference extends TypeBase {
|
||||
'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',
|
||||
'any': 'Object?',
|
||||
'LSPAny': 'Object?',
|
||||
'object': 'Object?',
|
||||
'LSPObject': 'Object?',
|
||||
// Simplify MarkedString from
|
||||
// string | { language: string; value: string }
|
||||
// to just String
|
||||
@@ -269,8 +296,8 @@ class TypeReference extends TypeBase {
|
||||
|
||||
@override
|
||||
String get typeArgsString {
|
||||
// Always resolve type aliases when asked for our Dart type.
|
||||
final resolvedType = resolveTypeAlias(this);
|
||||
// Resolve any renames when asked for our type.
|
||||
final resolvedType = resolveTypeAlias(this, onlyRenames: true);
|
||||
if (resolvedType != this) {
|
||||
return resolvedType.typeArgsString;
|
||||
}
|
||||
@@ -294,8 +321,6 @@ class UnionType extends TypeBase {
|
||||
types.sortBy((type) => type.dartTypeWithTypeArgs.toLowerCase());
|
||||
}
|
||||
|
||||
UnionType.nullable(TypeBase type) : this([type, TypeReference.Null_]);
|
||||
|
||||
@override
|
||||
String get dartType {
|
||||
if (types.length > 4) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// 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 'meta_model.dart';
|
||||
|
||||
/// Helper methods to clean the meta model to produce better Dart classes.
|
||||
@@ -162,7 +164,8 @@ class LspMetaModelCleaner {
|
||||
return TypeAlias(
|
||||
name: typeAlias.name,
|
||||
comment: _cleanComment(typeAlias.comment),
|
||||
baseType: typeAlias.baseType,
|
||||
baseType: _cleanType(typeAlias.baseType),
|
||||
isRename: typeAlias.isRename,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -184,8 +187,8 @@ class LspMetaModelCleaner {
|
||||
// types, we should just treat the whole thing as Object? as we get no value
|
||||
// typing Either4<bool, String, num, Object?> but it becomes much more
|
||||
// difficult to use.
|
||||
if (uniqueTypes.any(isAnyType)) {
|
||||
return uniqueTypes.firstWhere(isAnyType);
|
||||
if (uniqueTypes.any(isNullableAnyType)) {
|
||||
return uniqueTypes.firstWhere(isNullableAnyType);
|
||||
}
|
||||
|
||||
// Finally, sort the types by name so that we always generate the same type
|
||||
@@ -196,11 +199,19 @@ class LspMetaModelCleaner {
|
||||
// Recursively clean the inner types.
|
||||
uniqueTypes = uniqueTypes.map(_cleanType).toList();
|
||||
|
||||
return uniqueTypes.length == 1
|
||||
? uniqueTypes.single
|
||||
: uniqueTypes.every(isLiteralType)
|
||||
? LiteralUnionType(uniqueTypes.cast<LiteralType>())
|
||||
: UnionType(uniqueTypes);
|
||||
if (uniqueTypes.length == 1) {
|
||||
return uniqueTypes.single;
|
||||
} else if (uniqueTypes.every(isLiteralType)) {
|
||||
return LiteralUnionType(uniqueTypes.cast<LiteralType>());
|
||||
} else if (uniqueTypes.any(isNullType)) {
|
||||
final remainingTypes = uniqueTypes.whereNot(isNullType).toList();
|
||||
final nonNullType = remainingTypes.length == 1
|
||||
? remainingTypes.single
|
||||
: UnionType(remainingTypes);
|
||||
return NullableType(nonNullType);
|
||||
} else {
|
||||
return UnionType(uniqueTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/// Improves types in code generated from the LSP model, including:
|
||||
@@ -235,7 +246,7 @@ class LspMetaModelCleaner {
|
||||
? ArrayType(TypeReference(
|
||||
improvedTypeName.substring(0, improvedTypeName.length - 2)))
|
||||
: improvedTypeName.endsWith('?')
|
||||
? UnionType.nullable(TypeReference(
|
||||
? NullableType(TypeReference(
|
||||
improvedTypeName.substring(0, improvedTypeName.length - 1)))
|
||||
: TypeReference(improvedTypeName)
|
||||
: null;
|
||||
@@ -353,29 +364,42 @@ class LspMetaModelCleaner {
|
||||
'SignatureInformationParameterInformation',
|
||||
'TextDocumentFilter2': 'TextDocumentFilterWithScheme',
|
||||
'PrepareRenameResult1': 'PlaceholderAndRange',
|
||||
'URI': 'LspUri',
|
||||
};
|
||||
|
||||
for (final type in types) {
|
||||
if (type is Interface) {
|
||||
final newName = renames[type.name];
|
||||
if (newName != null) {
|
||||
// Replace with renamed interface.
|
||||
yield Interface(
|
||||
name: newName,
|
||||
comment: type.comment,
|
||||
baseTypes: type.baseTypes,
|
||||
members: type.members,
|
||||
);
|
||||
// Plus a TypeAlias for the old name.
|
||||
yield TypeAlias(
|
||||
name: type.name,
|
||||
comment: type.comment,
|
||||
baseType: TypeReference(newName),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
final newName = renames[type.name];
|
||||
if (newName == null) {
|
||||
yield type;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add a TypeAlias for the old name.
|
||||
yield TypeAlias(
|
||||
name: type.name,
|
||||
comment: type.comment,
|
||||
baseType: TypeReference(newName),
|
||||
isRename: true,
|
||||
);
|
||||
|
||||
// Replace the type with an equivalent with the same name.
|
||||
if (type is Interface) {
|
||||
yield Interface(
|
||||
name: newName,
|
||||
comment: type.comment,
|
||||
baseTypes: type.baseTypes,
|
||||
members: type.members,
|
||||
);
|
||||
} else if (type is TypeAlias) {
|
||||
yield TypeAlias(
|
||||
name: newName,
|
||||
comment: type.comment,
|
||||
baseType: type.baseType,
|
||||
isRename: type.isRename,
|
||||
);
|
||||
} else {
|
||||
throw 'Renaming ${type.runtimeType} is not implemented';
|
||||
}
|
||||
yield type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class LspMetaModelReader {
|
||||
...?enums?.map((e) => _readEnum(e)),
|
||||
...?typeAliases?.map(_readTypeAlias),
|
||||
].forEach(_addType);
|
||||
|
||||
final methodsEnum = _createMethodNamesEnum(methodNames);
|
||||
if (methodsEnum != null) {
|
||||
_addType(methodsEnum);
|
||||
@@ -174,6 +175,7 @@ class LspMetaModelReader {
|
||||
final generatedName = _generateAvailableTypeName(parentName, fieldName);
|
||||
return _extractType(generatedName, null, item);
|
||||
}).toList();
|
||||
|
||||
return UnionType(types);
|
||||
} else if (model['kind'] == 'tuple') {
|
||||
// We currently just map tuples to an array of any of the types. The
|
||||
@@ -278,6 +280,7 @@ class LspMetaModelReader {
|
||||
name: name,
|
||||
comment: model['documentation'] as String?,
|
||||
baseType: _extractType(name, null, model['type']),
|
||||
isRename: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user