Skip to content

Commit fc7f12d

Browse files
DanTupCommit Queue
authored andcommitted
[analysis_server] Add enum support for editableArguments
Change-Id: I453d54b837577a96c0459a9e729b16443ba3d7fd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/394443 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Elliott Brooks <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent 104f455 commit fc7f12d

File tree

3 files changed

+179
-23
lines changed

3 files changed

+179
-23
lines changed

pkg/analysis_server/lib/src/lsp/handlers/custom/handler_editable_arguments.dart

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:analyzer/dart/analysis/results.dart';
1313
import 'package:analyzer/dart/constant/value.dart';
1414
import 'package:analyzer/dart/element/element2.dart';
1515
import 'package:analyzer/dart/element/nullability_suffix.dart';
16+
import 'package:analyzer/dart/element/type.dart';
1617
import 'package:analyzer/src/dart/ast/ast.dart';
1718
import 'package:analyzer/src/dart/element/element.dart';
1819
import 'package:analyzer/src/lint/linter.dart';
@@ -170,6 +171,13 @@ class EditableArgumentsHandler
170171
);
171172
}
172173

174+
/// Returns the name of an enum constant prefixed with the enum name.
175+
String? _qualifiedEnumConstant(FieldElement2 enumConstant) {
176+
var enumName = enumConstant.enclosingElement2?.name3;
177+
var name = enumConstant.name3;
178+
return enumName != null && name != null ? '$enumName.$name' : null;
179+
}
180+
173181
/// Converts a [parameter]/[argument] pair into an [EditableArgument] if it
174182
/// is an argument that can be edited.
175183
EditableArgument? _toEditableArgument(
@@ -200,20 +208,43 @@ class EditableArgumentsHandler
200208
} else if (parameter.type.isDartCoreString) {
201209
type = 'string';
202210
value = (values.argumentValue ?? values.parameterValue)?.toStringValue();
211+
} else if (parameter.type case InterfaceType(:EnumElement2 element3)) {
212+
type = 'enum';
213+
options =
214+
element3.constants2.map(_qualifiedEnumConstant).nonNulls.toList();
215+
216+
// Try to match the argument value up with the enum.
217+
var valueObject = values.argumentValue ?? values.parameterValue;
218+
if (valueObject?.type case InterfaceType(
219+
element3: EnumElement2 valueElement,
220+
) when element3 == valueElement) {
221+
var index = valueObject?.getField('index')?.toIntValue();
222+
if (index != null) {
223+
var enumConstant = element3.constants2.elementAtOrNull(index);
224+
if (enumConstant != null) {
225+
value = _qualifiedEnumConstant(enumConstant);
226+
}
227+
}
228+
}
203229
} else {
204-
// TODO(dantup): Enums.
205-
206230
// TODO(dantup): Determine which parameters we don't include (such as
207231
// Widgets) and which we include just without values.
208232
return null;
209233
}
210234

235+
// If the value is not a literal, include the source as displayValue.
236+
var displayValue =
237+
valueExpression is! Literal ? valueExpression?.toSource() : null;
238+
// Unless it turns out to match the value (converted to string).
239+
if (displayValue != null && displayValue == value?.toString()) {
240+
displayValue = null;
241+
}
242+
211243
return EditableArgument(
212244
name: parameter.displayName,
213245
type: type,
214246
value: value,
215-
displayValue:
216-
valueExpression is! Literal ? valueExpression?.toSource() : null,
247+
displayValue: displayValue,
217248
options: options,
218249
isDefault: values.isDefault,
219250
hasArgument: valueExpression != null,

pkg/analysis_server/test/lsp/editable_arguments_test.dart

Lines changed: 139 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ $content
6464
Object? isDefault = anything,
6565
Object? isRequired = anything,
6666
Object? isNullable = anything,
67+
Object? options = anything,
6768
}) {
6869
return isA<EditableArgument>()
6970
.having((arg) => arg.name, 'name', name)
@@ -74,6 +75,7 @@ $content
7475
.having((arg) => arg.isDefault, 'isDefault', isDefault)
7576
.having((arg) => arg.isRequired, 'isRequired', isRequired)
7677
.having((arg) => arg.isNullable, 'isNullable', isNullable)
78+
.having((arg) => arg.options, 'options', options)
7779
// Some extra checks that should be true for all.
7880
.having(
7981
(arg) =>
@@ -593,15 +595,20 @@ class MyWidget extends StatelessWidget {
593595
result,
594596
hasArgs(
595597
orderedEquals([
596-
// TODO(dantup): Should we be able to get the `value` here?
597-
isArg('aVar', value: isNull, displayValue: 'myVar'),
598-
isArg('aConst', value: true, displayValue: 'myConst'),
598+
isArg('aVar', type: 'bool', value: isNull, displayValue: 'myVar'),
599+
isArg('aConst', type: 'bool', value: true, displayValue: 'myConst'),
599600
isArg(
600601
'aExpr',
602+
type: 'bool',
601603
value: isNull,
602604
displayValue: 'DateTime.now().isBefore(DateTime.now())',
603605
),
604-
isArg('aConstExpr', value: false, displayValue: '1 == 2'),
606+
isArg(
607+
'aConstExpr',
608+
type: 'bool',
609+
value: false,
610+
displayValue: '1 == 2',
611+
),
605612
]),
606613
),
607614
);
@@ -693,15 +700,121 @@ class MyWidget extends StatelessWidget {
693700
result,
694701
hasArgs(
695702
orderedEquals([
696-
// TODO(dantup): Should we be able to get the `value` here?
697-
isArg('aVar', value: isNull, displayValue: 'myVar'),
698-
isArg('aConst', value: 1.0, displayValue: 'myConst'),
703+
isArg('aVar', type: 'double', value: isNull, displayValue: 'myVar'),
704+
isArg('aConst', type: 'double', value: 1.0, displayValue: 'myConst'),
699705
isArg(
700706
'aExpr',
707+
type: 'double',
701708
value: isNull,
702709
displayValue: 'DateTime.now().millisecondsSinceEpoch.toDouble()',
703710
),
704-
isArg('aConstExpr', value: 2.0, displayValue: '1.0 + myConst'),
711+
isArg(
712+
'aConstExpr',
713+
type: 'double',
714+
value: 2.0,
715+
displayValue: '1.0 + myConst',
716+
),
717+
]),
718+
),
719+
);
720+
}
721+
722+
test_type_enum() async {
723+
var result = await getEditableArgumentsFor('''
724+
enum E { one, two }
725+
class MyWidget extends StatelessWidget {
726+
const MyWidget({
727+
E supplied = E.one,
728+
E suppliedAsDefault = E.one,
729+
E notSupplied = E.one,
730+
});
731+
732+
@override
733+
Widget build(BuildContext context) => MyW^idget(
734+
supplied: E.two,
735+
suppliedAsDefault: E.one,
736+
);
737+
}
738+
''');
739+
740+
var optionsMatcher = equals(['E.one', 'E.two']);
741+
expect(
742+
result,
743+
hasArgs(
744+
orderedEquals([
745+
isArg(
746+
'supplied',
747+
type: 'enum',
748+
value: 'E.two',
749+
isDefault: false,
750+
options: optionsMatcher,
751+
),
752+
isArg(
753+
'suppliedAsDefault',
754+
type: 'enum',
755+
value: 'E.one',
756+
isDefault: true,
757+
options: optionsMatcher,
758+
),
759+
isArg(
760+
'notSupplied',
761+
type: 'enum',
762+
value: 'E.one',
763+
isDefault: true,
764+
options: optionsMatcher,
765+
),
766+
]),
767+
),
768+
);
769+
}
770+
771+
test_type_enum_nonLiterals() async {
772+
var result = await getEditableArgumentsFor('''
773+
enum E { one, two }
774+
var myVar = E.one;
775+
const myConst = E.one;
776+
class MyWidget extends StatelessWidget {
777+
const MyWidget({
778+
E? aVar,
779+
E? aConst,
780+
E? aExpr,
781+
});
782+
783+
@override
784+
Widget build(BuildContext context) => MyW^idget(
785+
aVar: myVar,
786+
aConst: myConst,
787+
aExpr: E.values.first,
788+
);
789+
}
790+
''');
791+
792+
var optionsMatcher = equals(['E.one', 'E.two']);
793+
expect(
794+
result,
795+
hasArgs(
796+
orderedEquals([
797+
isArg(
798+
'aVar',
799+
type: 'enum',
800+
value: isNull,
801+
displayValue: 'myVar',
802+
options: optionsMatcher,
803+
),
804+
isArg(
805+
'aConst',
806+
type: 'enum',
807+
value: 'E.one',
808+
displayValue: 'myConst',
809+
options: optionsMatcher,
810+
),
811+
isArg(
812+
'aExpr',
813+
type: 'enum',
814+
value: isNull,
815+
displayValue: 'E.values.first',
816+
options: optionsMatcher,
817+
),
705818
]),
706819
),
707820
);
@@ -760,15 +873,20 @@ class MyWidget extends StatelessWidget {
760873
result,
761874
hasArgs(
762875
orderedEquals([
763-
// TODO(dantup): Should we be able to get the `value` here?
764-
isArg('aVar', value: isNull, displayValue: 'myVar'),
765-
isArg('aConst', value: 1, displayValue: 'myConst'),
876+
isArg('aVar', type: 'int', value: isNull, displayValue: 'myVar'),
877+
isArg('aConst', type: 'int', value: 1, displayValue: 'myConst'),
766878
isArg(
767879
'aExpr',
880+
type: 'int',
768881
value: isNull,
769882
displayValue: 'DateTime.now().millisecondsSinceEpoch',
770883
),
771-
isArg('aConstExpr', value: 2, displayValue: '1 + myConst'),
884+
isArg(
885+
'aConstExpr',
886+
type: 'int',
887+
value: 2,
888+
displayValue: '1 + myConst',
889+
),
772890
]),
773891
),
774892
);
@@ -832,15 +950,20 @@ class MyWidget extends StatelessWidget {
832950
result,
833951
hasArgs(
834952
orderedEquals([
835-
// TODO(dantup): Should we be able to get the `value` here?
836-
isArg('aVar', value: isNull, displayValue: 'myVar'),
837-
isArg('aConst', value: 'a', displayValue: 'myConst'),
953+
isArg('aVar', type: 'string', value: isNull, displayValue: 'myVar'),
954+
isArg('aConst', type: 'string', value: 'a', displayValue: 'myConst'),
838955
isArg(
839956
'aExpr',
957+
type: 'string',
840958
value: isNull,
841959
displayValue: 'DateTime.now().toString()',
842960
),
843-
isArg('aConstExpr', value: 'ab', displayValue: "'a' + 'b'"),
961+
isArg(
962+
'aConstExpr',
963+
type: 'string',
964+
value: 'ab',
965+
displayValue: "'a' + 'b'",
966+
),
844967
]),
845968
),
846969
);

pkg/analyzer/lib/src/dart/element/element.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3501,7 +3501,8 @@ class EnumElementImpl extends InterfaceElementImpl
35013501
}
35023502

35033503
@override
3504-
List<FieldElement2> get constants2 => constants.cast<FieldElement2>();
3504+
List<FieldElement2> get constants2 =>
3505+
constants.map((e) => e.asElement2 as FieldElement2).toList();
35053506

35063507
@override
35073508
EnumElementImpl2 get element {
@@ -3559,7 +3560,8 @@ class EnumElementImpl2 extends InterfaceElementImpl2
35593560
}
35603561

35613562
@override
3562-
List<FieldElement2> get constants2 => constants.cast<FieldElement2>();
3563+
List<FieldElement2> get constants2 =>
3564+
constants.map((e) => e.asElement2 as FieldElement2).toList();
35633565

35643566
@override
35653567
T? accept2<T>(ElementVisitor2<T> visitor) {
@@ -4201,7 +4203,7 @@ class FieldElementImpl2 extends PropertyInducingElementImpl2
42014203
ElementKind get kind => ElementKind.FIELD;
42024204

42034205
@override
4204-
String? get name3 => firstFragment.name;
4206+
String get name3 => firstFragment.name;
42054207

42064208
@override
42074209
SetterElement? get setter2 => firstFragment.setter?.element as SetterElement?;

0 commit comments

Comments
 (0)