From af68c2d5ead2ff270dc94c8daac93173693b3101 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Mon, 24 Feb 2025 11:30:28 -0800 Subject: [PATCH] [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 Commit-Queue: Brian Wilkerson Reviewed-by: Elliott Brooks --- .../editable_arguments_mixin.dart | 20 +++++----- .../handler_editable_arguments.dart | 5 +++ .../shared_editable_arguments_tests.dart | 39 ++++++++++++++++++- .../tool/lsp_spec/generate_all.dart | 1 + .../lib/protocol_custom_generated.dart | 15 +++++++ 5 files changed, 69 insertions(+), 11 deletions(-) diff --git a/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/editable_arguments_mixin.dart b/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/editable_arguments_mixin.dart index 29000e310e9..28898074478 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/editable_arguments_mixin.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/editable_arguments_mixin.dart @@ -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); } } diff --git a/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_editable_arguments.dart b/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_editable_arguments.dart index 0e57615aec3..73dcaf70ad2 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_editable_arguments.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_editable_arguments.dart @@ -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, diff --git a/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart b/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart index 7048364a790..2127be9d586 100644 --- a/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart +++ b/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart @@ -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() .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 { diff --git a/pkg/analysis_server/tool/lsp_spec/generate_all.dart b/pkg/analysis_server/tool/lsp_spec/generate_all.dart index da66d786219..acf7963233f 100644 --- a/pkg/analysis_server/tool/lsp_spec/generate_all.dart +++ b/pkg/analysis_server/tool/lsp_spec/generate_all.dart @@ -432,6 +432,7 @@ List getCustomClasses() { type: 'string', comment: 'The name of the corresponding parameter.', ), + field('documentation', type: 'string', canBeUndefined: true), field( 'type', type: 'string', diff --git a/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart b/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart index 1a3f5379b21..2d3770a5eba 100644 --- a/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart +++ b/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart @@ -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,