[Property Editor] Use dot-shorthand syntax in enum edits when possible
Bug: https://github.com/dart-lang/sdk/issues/60727 Change-Id: I41388422b0c317e7500166d29ba4f9e7b656a24f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/441862 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Elliott Brooks <elliottbrooks@google.com>
This commit is contained in:
committed by
Commit Queue
parent
289278b584
commit
a1699ecfb0
+35
@@ -4,6 +4,7 @@
|
||||
|
||||
import 'package:analysis_server/src/computer/computer_documentation.dart';
|
||||
import 'package:analysis_server/src/utilities/extensions/numeric.dart';
|
||||
import 'package:analyzer/dart/analysis/features.dart';
|
||||
import 'package:analyzer/dart/analysis/results.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/src/dart/ast/ast.dart';
|
||||
@@ -35,6 +36,35 @@ mixin EditableArgumentsMixin {
|
||||
return dartDoc?.full;
|
||||
}
|
||||
|
||||
/// Returns the name of an enum constant prefixed with only a dot.
|
||||
///
|
||||
/// If the dot-shorthands feature is not enabled, this method returns null.
|
||||
String? getDotShorthandEnumConstantName(FieldElement enumConstant) {
|
||||
if (!_supportsDotShorthandSyntax(enumConstant)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var name = enumConstant.name;
|
||||
return name != null ? '.$name' : null;
|
||||
}
|
||||
|
||||
/// Returns an enum constant [FieldElement] of the given [element] matching
|
||||
/// the provided fully qualified name.
|
||||
///
|
||||
/// This method iterates through all constants of the [element] and compares
|
||||
/// their fully qualified names against the [matching] string.
|
||||
FieldElement? getEnumConstantMatching(
|
||||
EnumElement element, {
|
||||
required String matching,
|
||||
}) {
|
||||
for (var enumConstant in element.constants) {
|
||||
if (getQualifiedEnumConstantName(enumConstant) == matching) {
|
||||
return enumConstant;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Gets the argument list at [offset] that can be edited.
|
||||
EditableInvocationInfo? getInvocationInfo(
|
||||
ResolvedUnitResult result,
|
||||
@@ -161,6 +191,11 @@ mixin EditableArgumentsMixin {
|
||||
List<String> getQualifiedEnumConstantNames(EnumElement element) =>
|
||||
element.constants.map(getQualifiedEnumConstantName).nonNulls.toList();
|
||||
|
||||
/// Determines whether or not the dot-shortands feature is enabled for the
|
||||
/// given [element].
|
||||
bool _supportsDotShorthandSyntax(Element element) =>
|
||||
element.library?.featureSet.isEnabled(Feature.dot_shorthands) ?? false;
|
||||
|
||||
/// Returns the name of an enum constant prefixed with the enum name.
|
||||
static String? getQualifiedEnumConstantName(FieldElement enumConstant) {
|
||||
var enumName = enumConstant.enclosingElement.name;
|
||||
|
||||
+35
-6
@@ -164,6 +164,29 @@ class EditArgumentHandler extends SharedMessageHandler<EditArgumentParams, Null>
|
||||
});
|
||||
}
|
||||
|
||||
/// Computes the appropriate enum value for the String [requestValue].
|
||||
///
|
||||
/// This method tries to use dot-shorthand syntax for the enum value when the
|
||||
/// [currentArgument] is a [DotShorthandPropertyAccess], a [SimpleIdentifier],
|
||||
/// or `null`.
|
||||
String _computeEnumValue({
|
||||
required String? requestValue,
|
||||
required FieldElement enumConstant,
|
||||
required Expression? currentArgument,
|
||||
}) {
|
||||
var preferDotShorthand =
|
||||
currentArgument is DotShorthandPropertyAccess ||
|
||||
currentArgument is SimpleIdentifier ||
|
||||
currentArgument == null;
|
||||
|
||||
var enumValue =
|
||||
preferDotShorthand
|
||||
? getDotShorthandEnumConstantName(enumConstant) ?? requestValue
|
||||
: requestValue;
|
||||
|
||||
return enumValue.toString();
|
||||
}
|
||||
|
||||
/// Computes the string of Dart code that should be used as the new value
|
||||
/// for this argument.
|
||||
///
|
||||
@@ -188,7 +211,7 @@ class EditArgumentHandler extends SharedMessageHandler<EditArgumentParams, Null>
|
||||
} else {
|
||||
return error(
|
||||
ServerErrorCodes.EditArgumentInvalidValue,
|
||||
"The value for the parameter '${edit.name}' cannot be null",
|
||||
"The value for the parameter '${edit.name}' can't be null",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -211,14 +234,20 @@ class EditArgumentHandler extends SharedMessageHandler<EditArgumentParams, Null>
|
||||
);
|
||||
} else if (parameter.type case InterfaceType(
|
||||
:EnumElement element,
|
||||
) when value is String?) {
|
||||
var allowedValues = getQualifiedEnumConstantNames(element);
|
||||
if (allowedValues.contains(value)) {
|
||||
return success(value.toString());
|
||||
) when value is String) {
|
||||
var enumConstant = getEnumConstantMatching(element, matching: value);
|
||||
if (enumConstant != null) {
|
||||
return success(
|
||||
_computeEnumValue(
|
||||
requestValue: value,
|
||||
enumConstant: enumConstant,
|
||||
currentArgument: argument,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return error(
|
||||
ServerErrorCodes.EditArgumentInvalidValue,
|
||||
"The value for the parameter '${edit.name}' should be one of ${allowedValues.map((v) => "'$v'").join(', ')} but was '$value'",
|
||||
"The value for the parameter '${edit.name}' should be one of ${getQualifiedEnumConstantNames(element).map((v) => "'$v'").join(', ')} but was '$value'",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -435,7 +435,7 @@ mixin SharedEditArgumentTests
|
||||
originalArgs: '(x: true)',
|
||||
edit: ArgumentEdit(name: 'x'),
|
||||
errorCode: ServerErrorCodes.EditArgumentInvalidValue,
|
||||
message: "The value for the parameter 'x' cannot be null",
|
||||
message: "The value for the parameter 'x' can't be null",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -483,7 +483,7 @@ mixin SharedEditArgumentTests
|
||||
originalArgs: '(x: 1.0)',
|
||||
edit: ArgumentEdit(name: 'x'),
|
||||
errorCode: ServerErrorCodes.EditArgumentInvalidValue,
|
||||
message: "The value for the parameter 'x' cannot be null",
|
||||
message: "The value for the parameter 'x' can't be null",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -523,6 +523,75 @@ mixin SharedEditArgumentTests
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_type_enum_dotshorthand_addNew() async {
|
||||
await _expectSimpleArgumentEdit(
|
||||
additionalCode: 'enum E { one, two }',
|
||||
params: '({ E? x })',
|
||||
originalArgs: '()',
|
||||
edit: ArgumentEdit(name: 'x', newValue: 'E.two'),
|
||||
expectedArgs: '(x: .two)',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_type_enum_dotshorthand_disabled_addNew() async {
|
||||
await _expectSimpleArgumentEdit(
|
||||
additionalCode: 'enum E { one, two }',
|
||||
params: '({ E? x })',
|
||||
originalArgs: '()',
|
||||
edit: ArgumentEdit(name: 'x', newValue: 'E.two'),
|
||||
expectedArgs: '(x: E.two)',
|
||||
fileComment: '// @dart = 3.8',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_type_enum_dotshorthand_disabled_replaceLiteral() async {
|
||||
await _expectSimpleArgumentEdit(
|
||||
additionalCode: 'enum E { one, two }',
|
||||
params: '({ E? x })',
|
||||
originalArgs: '(x: E.one)',
|
||||
edit: ArgumentEdit(name: 'x', newValue: 'E.two'),
|
||||
expectedArgs: '(x: E.two)',
|
||||
fileComment: '// @dart = 3.8',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_type_enum_dotshorthand_disabled_replaceNonLiteral() async {
|
||||
await _expectSimpleArgumentEdit(
|
||||
additionalCode: '''
|
||||
enum E { one, two }
|
||||
const E myConst = E.one;
|
||||
''',
|
||||
params: '({ E? x })',
|
||||
originalArgs: '(x: myConst)',
|
||||
edit: ArgumentEdit(name: 'x', newValue: 'E.two'),
|
||||
expectedArgs: '(x: E.two)',
|
||||
fileComment: '// @dart = 3.8',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_type_enum_dotshorthand_replaceLiteral() async {
|
||||
await _expectSimpleArgumentEdit(
|
||||
additionalCode: 'enum E { one, two }',
|
||||
params: '({ E? x })',
|
||||
originalArgs: '(x: .one)',
|
||||
edit: ArgumentEdit(name: 'x', newValue: 'E.two'),
|
||||
expectedArgs: '(x: .two)',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_type_enum_dotshorthand_replaceNonLiteral() async {
|
||||
await _expectSimpleArgumentEdit(
|
||||
additionalCode: '''
|
||||
enum E { one, two }
|
||||
const E myConst = .one;
|
||||
''',
|
||||
params: '({ E? x })',
|
||||
originalArgs: '(x: myConst)',
|
||||
edit: ArgumentEdit(name: 'x', newValue: 'E.two'),
|
||||
expectedArgs: '(x: .two)',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_type_enum_invalidType() async {
|
||||
await _expectFailedEdit(
|
||||
additionalCode: 'enum E { one, two }',
|
||||
@@ -552,7 +621,7 @@ mixin SharedEditArgumentTests
|
||||
originalArgs: '(x: E.one)',
|
||||
edit: ArgumentEdit(name: 'x'),
|
||||
errorCode: ServerErrorCodes.EditArgumentInvalidValue,
|
||||
message: "The value for the parameter 'x' cannot be null",
|
||||
message: "The value for the parameter 'x' can't be null",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -575,7 +644,7 @@ const myConst = E.one;
|
||||
params: '({ E? x })',
|
||||
originalArgs: '(x: myConst)',
|
||||
edit: ArgumentEdit(name: 'x', newValue: 'E.two'),
|
||||
expectedArgs: '(x: E.two)',
|
||||
expectedArgs: '(x: .two)',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -604,7 +673,7 @@ const myConst = E.one;
|
||||
originalArgs: '(x: 1)',
|
||||
edit: ArgumentEdit(name: 'x'),
|
||||
errorCode: ServerErrorCodes.EditArgumentInvalidValue,
|
||||
message: "The value for the parameter 'x' cannot be null",
|
||||
message: "The value for the parameter 'x' can't be null",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -687,7 +756,7 @@ const myConst = E.one;
|
||||
originalArgs: "(x: 'a')",
|
||||
edit: ArgumentEdit(name: 'x'),
|
||||
errorCode: ServerErrorCodes.EditArgumentInvalidValue,
|
||||
message: "The value for the parameter 'x' cannot be null",
|
||||
message: "The value for the parameter 'x' can't be null",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -857,10 +926,11 @@ class MyWidget extends StatelessWidget {
|
||||
required String originalArgs,
|
||||
required ArgumentEdit edit,
|
||||
required String expectedArgs,
|
||||
String? additionalCode,
|
||||
String? additionalCode = '',
|
||||
String? fileComment = '',
|
||||
}) async {
|
||||
additionalCode ??= '';
|
||||
var content = '''
|
||||
$fileComment
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
$additionalCode
|
||||
@@ -874,6 +944,7 @@ class MyWidget extends StatelessWidget {
|
||||
''';
|
||||
var expectedContent = '''
|
||||
>>>>>>>>>> lib/test.dart
|
||||
$fileComment
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
$additionalCode
|
||||
|
||||
Reference in New Issue
Block a user