[analysis_server] Add documentation to editable arguments that represent fields

If an argument is for a field parameter, this includes the documentation from that field, so that the property editor can show the same content you'd see by hovering over the argument name in the editor.

See https://github.com/flutter/devtools/issues/8938

Change-Id: Iebee757dd42fe9da2c8938c95febc99c0c96b609
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/411684
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Elliott Brooks <elliottbrooks@google.com>
This commit is contained in:
Danny Tuppeny
2025-02-24 11:30:28 -08:00
committed by Commit Queue
parent ef71100e1f
commit af68c2d5ea
5 changed files with 69 additions and 11 deletions
@@ -28,6 +28,14 @@ typedef EditableInvocationInfo =
mixin EditableArgumentsMixin {
DartdocDirectiveInfo getDartdocDirectiveInfoFor(ResolvedUnitResult result);
String? getDocumentation(ResolvedUnitResult result, Element2 element) {
var dartDocInfo = getDartdocDirectiveInfoFor(result);
var dartDocComputer = DartDocumentationComputer(dartDocInfo);
var dartDoc = dartDocComputer.compute(element);
return dartDoc?.full;
}
/// Gets the argument list at [offset] that can be edited.
EditableInvocationInfo? getInvocationInfo(
ResolvedUnitResult result,
@@ -48,19 +56,11 @@ mixin EditableArgumentsMixin {
widgetName = invocation.constructorName.type.name2.lexeme;
if (invocation.constructorName.element case var element?) {
var dartDocInfo = getDartdocDirectiveInfoFor(result);
var dartDocComputer = DartDocumentationComputer(dartDocInfo);
var dartDoc = dartDocComputer.compute(element);
widgetDocumentation = dartDoc?.full;
widgetDocumentation = getDocumentation(result, element);
}
} else if (invocation is InvocationExpression) {
if (invocation.function case Identifier(:var element?)) {
var dartDocInfo = getDartdocDirectiveInfoFor(result);
var dartDocComputer = DartDocumentationComputer(dartDocInfo);
var dartDoc = dartDocComputer.compute(element);
widgetDocumentation = dartDoc?.full;
widgetDocumentation = getDocumentation(result, element);
}
}
@@ -117,6 +117,7 @@ class EditableArgumentsHandler
var editableArguments = [
for (var parameter in parameters)
_toEditableArgument(
result,
parameter,
parameterArguments[parameter],
positionalIndex: positionalParameterIndexes[parameter],
@@ -148,6 +149,7 @@ class EditableArgumentsHandler
/// Converts a [parameter]/[argument] pair into an [EditableArgument] if it
/// is an argument that can be edited.
EditableArgument? _toEditableArgument(
ResolvedUnitResult result,
FormalParameterElement parameter,
Expression? argument, {
int? positionalIndex,
@@ -223,8 +225,11 @@ class EditableArgumentsHandler
displayValue = null;
}
var documentation = getDocumentation(result, parameter);
return EditableArgument(
name: parameter.displayName,
documentation: documentation,
type: type,
value: value,
displayValue: displayValue,
@@ -40,7 +40,7 @@ $content
return hasArgs(contains(matcher));
}
Matcher hasArgNamed(String argumentName) {
Matcher hasArgNamed(String argumentName, {String? doc}) {
return hasArg(isArg(argumentName));
}
@@ -70,6 +70,7 @@ $content
Matcher isArg(
String name, {
Object? documentation = anything,
Object? type = anything,
Object? value = anything,
Object? displayValue = anything,
@@ -83,6 +84,7 @@ $content
}) {
return isA<EditableArgument>()
.having((arg) => arg.name, 'name', name)
.having((arg) => arg.documentation, 'documentation', documentation)
.having((arg) => arg.type, 'type', type)
.having((arg) => arg.value, 'value', value)
.having((arg) => arg.displayValue, 'displayValue', displayValue)
@@ -223,6 +225,41 @@ class MyWidget extends StatelessWidget {
expect(result, hasArg(isArg('a', defaultValue: null)));
}
test_documentation_fieldParameter_literal() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
/// Documentation for x.
final int x;
/// Creates a MyWidget.
const MyWidget(this.x);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasArg(isArg('x', documentation: 'Documentation for x.')));
}
test_documentation_fieldParameter_macro() async {
var result = await getEditableArgumentsFor('''
/// {@template shared_docs}
/// Shared docs.
/// {@endtemplate}
class MyWidget extends StatelessWidget {
/// {@macro shared_docs}
final int x;
/// Creates a MyWidget.
const MyWidget(this.x);
@override
Widget build(BuildContext context) => MyW^idget(1);
}
''');
expect(result, hasArg(isArg('x', documentation: 'Shared docs.')));
}
test_documentation_literal() async {
var result = await getEditableArgumentsFor('''
class MyWidget extends StatelessWidget {
@@ -432,6 +432,7 @@ List<LspEntity> getCustomClasses() {
type: 'string',
comment: 'The name of the corresponding parameter.',
),
field('documentation', type: 'string', canBeUndefined: true),
field(
'type',
type: 'string',
@@ -1449,6 +1449,8 @@ class EditableArgument implements ToJsonable {
/// same as the value field, for example an expression or named constant.
final String? displayValue;
final String? documentation;
/// Whether an explicit argument exists for this parameter in the code.
///
/// This will be true even if the explicit argument is the same value as the
@@ -1499,6 +1501,7 @@ class EditableArgument implements ToJsonable {
EditableArgument({
this.defaultValue,
this.displayValue,
this.documentation,
required this.hasArgument,
required this.isEditable,
required this.isNullable,
@@ -1513,6 +1516,7 @@ class EditableArgument implements ToJsonable {
int get hashCode => Object.hash(
defaultValue,
displayValue,
documentation,
hasArgument,
isEditable,
isNullable,
@@ -1530,6 +1534,7 @@ class EditableArgument implements ToJsonable {
other.runtimeType == EditableArgument &&
defaultValue == other.defaultValue &&
displayValue == other.displayValue &&
documentation == other.documentation &&
hasArgument == other.hasArgument &&
isEditable == other.isEditable &&
isNullable == other.isNullable &&
@@ -1550,6 +1555,9 @@ class EditableArgument implements ToJsonable {
if (displayValue != null) {
result['displayValue'] = displayValue;
}
if (documentation != null) {
result['documentation'] = documentation;
}
result['hasArgument'] = hasArgument;
result['isEditable'] = isEditable;
result['isNullable'] = isNullable;
@@ -1577,6 +1585,10 @@ class EditableArgument implements ToJsonable {
allowsUndefined: true, allowsNull: false)) {
return false;
}
if (!_canParseString(obj, reporter, 'documentation',
allowsUndefined: true, allowsNull: false)) {
return false;
}
if (!_canParseBool(obj, reporter, 'hasArgument',
allowsUndefined: false, allowsNull: false)) {
return false;
@@ -1618,6 +1630,8 @@ class EditableArgument implements ToJsonable {
final defaultValue = defaultValueJson;
final displayValueJson = json['displayValue'];
final displayValue = displayValueJson as String?;
final documentationJson = json['documentation'];
final documentation = documentationJson as String?;
final hasArgumentJson = json['hasArgument'];
final hasArgument = hasArgumentJson as bool;
final isEditableJson = json['isEditable'];
@@ -1640,6 +1654,7 @@ class EditableArgument implements ToJsonable {
return EditableArgument(
defaultValue: defaultValue,
displayValue: displayValue,
documentation: documentation,
hasArgument: hasArgument,
isEditable: isEditable,
isNullable: isNullable,