[analysis_server] Add helper functions to support flags enums
In the new interactive forms, some of the enums can be combined (eg. `FileExistence.New | FileExisting.Existing`). This adds a flag that produces some helper functions for `hasFlag()` and `combine()`. These helpers are not used outside of tests in this CL but will be used in a future CL (which I'm trying to avoid getting too big to simplify reviewing). Change-Id: I1b5f06f05d96781c4bc6a246a0a9309a786c1987 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505320 Reviewed-by: Keerti Parthasarathy <keertip@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Keerti Parthasarathy <keertip@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
0e3e174444
commit
026043d9ed
@@ -15,6 +15,30 @@ void main() {
|
||||
|
||||
ast.ArrayType _array(String name) => ast.ArrayType(_simple(name));
|
||||
|
||||
/// Helper to create a constant with defaults.
|
||||
ast.Constant _constant(Object value, {String name = 'x', String? type}) {
|
||||
return ast.Constant(
|
||||
name: name,
|
||||
type: type != null ? ast.TypeReference(type) : ast.TypeReference.lspAny,
|
||||
value: value.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Helper to create a enum with defaults.
|
||||
ast.LspEnum _enum({
|
||||
String name = 'x',
|
||||
String type = 'int',
|
||||
bool flags = false,
|
||||
required List<ast.Member> members,
|
||||
}) {
|
||||
return ast.LspEnum(
|
||||
name: name,
|
||||
typeOfValues: ast.TypeReference(type),
|
||||
flags: flags,
|
||||
members: members,
|
||||
);
|
||||
}
|
||||
|
||||
ast.TypeReference _simple(String name) => ast.TypeReference(name);
|
||||
|
||||
ast.UnionType _union(List<String> names) =>
|
||||
@@ -22,6 +46,27 @@ ast.UnionType _union(List<String> names) =>
|
||||
|
||||
@reflectiveTest
|
||||
class DartTest {
|
||||
void test_flags_requiresPowerOfTwo_double() {
|
||||
expect(
|
||||
() => _enum(flags: true, members: [_constant(1.2)]),
|
||||
throwsA(isA<AssertionError>()),
|
||||
);
|
||||
}
|
||||
|
||||
void test_flags_requiresPowerOfTwo_nonPowerOfTwo() {
|
||||
expect(
|
||||
() => _enum(flags: true, members: [_constant(3)]),
|
||||
throwsA(isA<AssertionError>()),
|
||||
);
|
||||
}
|
||||
|
||||
void test_flags_requiresPowerOfTwo_string() {
|
||||
expect(
|
||||
() => _enum(flags: true, members: [_constant('test')]),
|
||||
throwsA(isA<AssertionError>()),
|
||||
);
|
||||
}
|
||||
|
||||
void test_mapping_arrays() {
|
||||
expect(_array('string').dartTypeWithTypeArgs, equals('List<String>'));
|
||||
}
|
||||
|
||||
@@ -119,6 +119,23 @@ class GeneratedClassesTest {
|
||||
expect(a.hashCode, equals(b.hashCode));
|
||||
}
|
||||
|
||||
void test_generatedClasses_flagsEnum_combined() {
|
||||
var combined = FileExistence.combine([
|
||||
FileExistence.New,
|
||||
FileExistence.Existing,
|
||||
]);
|
||||
|
||||
expect(combined.toJson(), 3);
|
||||
expect(combined.hasFlag(FileExistence.New), isTrue);
|
||||
expect(combined.hasFlag(FileExistence.Existing), isTrue);
|
||||
expect(combined.hasFlag(FileExistence(64)), isFalse);
|
||||
}
|
||||
|
||||
void test_generatedClasses_flagsEnum_notCombined() {
|
||||
expect(FileExistence.New.hasFlag(FileExistence.New), isTrue);
|
||||
expect(FileExistence.New.hasFlag(FileExistence.Existing), isFalse);
|
||||
}
|
||||
|
||||
void test_interactiveForms_deserialize_formFieldsIntoSubclasses() {
|
||||
var stringField = FormField.fromJson({
|
||||
'type': {'kind': 'string'},
|
||||
|
||||
@@ -686,6 +686,22 @@ void _writeEnumClass(IndentableStringBuffer buffer, LspEnum namespace) {
|
||||
'static const $memberName = $namespaceName$constructorName($value);',
|
||||
);
|
||||
});
|
||||
if (namespace.flags) {
|
||||
buffer
|
||||
..writeln()
|
||||
..writeIndentedln(
|
||||
'static $namespaceName combine(List<$namespaceName> values) =>',
|
||||
)
|
||||
..indent()
|
||||
..writeIndentedln(
|
||||
'$namespaceName$constructorName(values.fold<$dartType>(0, (combinedValue, value) => combinedValue | value._value));',
|
||||
)
|
||||
..outdent()
|
||||
..writeln()
|
||||
..writeIndentedln(
|
||||
'bool hasFlag($namespaceName value) => (_value & value._value) == value._value;',
|
||||
);
|
||||
}
|
||||
buffer
|
||||
..writeln()
|
||||
..writeIndentedln('@override $dartType toJson() => _value;')
|
||||
|
||||
@@ -136,6 +136,7 @@ final interactiveFormClasses = <LspEntity>[
|
||||
LspEnum(
|
||||
name: 'FileExistence',
|
||||
typeOfValues: TypeReference.int,
|
||||
flags: true,
|
||||
members: [
|
||||
// Values should be powers of 2 to allow New|Existing.
|
||||
Constant(
|
||||
@@ -159,6 +160,7 @@ final interactiveFormClasses = <LspEntity>[
|
||||
LspEnum(
|
||||
name: 'FileType',
|
||||
typeOfValues: TypeReference.int,
|
||||
flags: true,
|
||||
members: [
|
||||
// Values should be powers of 2 to allow Regular|Directory.
|
||||
Constant(
|
||||
|
||||
@@ -23,6 +23,10 @@ bool isNullType(TypeBase t) =>
|
||||
bool isObjectType(TypeBase t) =>
|
||||
resolveTypeAlias(t).dartTypeWithTypeArgs == 'Object';
|
||||
|
||||
bool _isPowerOfTwo(int x) {
|
||||
return x > 0 && (x & (x - 1)) == 0;
|
||||
}
|
||||
|
||||
class AbstractGetter extends Member {
|
||||
final TypeBase type;
|
||||
|
||||
@@ -178,14 +182,23 @@ abstract class LspEntity {
|
||||
/// An enum parsed from the LSP JSON model.
|
||||
class LspEnum extends LspEntity {
|
||||
final TypeBase typeOfValues;
|
||||
final bool flags;
|
||||
final List<Member> members;
|
||||
LspEnum({
|
||||
required super.name,
|
||||
super.comment,
|
||||
super.isProposed,
|
||||
required this.typeOfValues,
|
||||
this.flags = false,
|
||||
required this.members,
|
||||
}) {
|
||||
}) : assert(
|
||||
!flags ||
|
||||
members
|
||||
.whereType<Constant>()
|
||||
.map((member) => int.tryParse(member.value))
|
||||
.every((value) => value != null && _isPowerOfTwo(value)),
|
||||
'flags enums require all enum values to be int powers of two.',
|
||||
) {
|
||||
members.sortBy((member) => member.name.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,7 @@ class LspMetaModelCleaner {
|
||||
comment: _cleanComment(namespace.comment),
|
||||
isProposed: namespace.isProposed,
|
||||
typeOfValues: namespace.typeOfValues,
|
||||
flags: namespace.flags,
|
||||
members: namespace.members
|
||||
.where(_includeEntityInOutput)
|
||||
.map((member) => _cleanMember(namespace.name, member))
|
||||
@@ -385,6 +386,7 @@ class LspMetaModelCleaner {
|
||||
comment: comment,
|
||||
isProposed: dest.isProposed,
|
||||
typeOfValues: dest.typeOfValues,
|
||||
flags: dest.flags || source.flags,
|
||||
members: [...dest.members, ...source.members],
|
||||
);
|
||||
} else if (source is Interface && dest is Interface) {
|
||||
@@ -530,6 +532,7 @@ class LspMetaModelCleaner {
|
||||
comment: type.comment,
|
||||
isProposed: type.isProposed,
|
||||
typeOfValues: type.typeOfValues,
|
||||
flags: type.flags,
|
||||
members: type.members,
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -2244,6 +2244,8 @@ class FileExistence implements ToJsonable {
|
||||
bool operator ==(Object other) =>
|
||||
other is FileExistence && other._value == _value;
|
||||
|
||||
bool hasFlag(FileExistence value) => (_value & value._value) == value._value;
|
||||
|
||||
@override
|
||||
int toJson() => _value;
|
||||
|
||||
@@ -2251,6 +2253,10 @@ class FileExistence implements ToJsonable {
|
||||
String toString() => _value.toString();
|
||||
|
||||
static bool canParse(Object? obj, LspJsonReporter reporter) => obj is int;
|
||||
|
||||
static FileExistence combine(List<FileExistence> values) =>
|
||||
FileExistence(values.fold<int>(
|
||||
0, (combinedValue, value) => combinedValue | value._value));
|
||||
}
|
||||
|
||||
/// FileType represents the expected filesystem resource type.
|
||||
@@ -2274,6 +2280,8 @@ class FileType implements ToJsonable {
|
||||
@override
|
||||
bool operator ==(Object other) => other is FileType && other._value == _value;
|
||||
|
||||
bool hasFlag(FileType value) => (_value & value._value) == value._value;
|
||||
|
||||
@override
|
||||
int toJson() => _value;
|
||||
|
||||
@@ -2281,6 +2289,9 @@ class FileType implements ToJsonable {
|
||||
String toString() => _value.toString();
|
||||
|
||||
static bool canParse(Object? obj, LspJsonReporter reporter) => obj is int;
|
||||
|
||||
static FileType combine(List<FileType> values) => FileType(values.fold<int>(
|
||||
0, (combinedValue, value) => combinedValue | value._value));
|
||||
}
|
||||
|
||||
class FlutterOutline implements ToJsonable {
|
||||
|
||||
Reference in New Issue
Block a user