Files
sdk/pkg/analysis_server/lib/src/computer/computer_hover.dart
T
Parker Lougheed bf7e9d13d7 Allow analyzer to fallback to getter documentation for setters
The analyzer server firsts looks for the setter's dartdoc, then the first parent setter's dartdoc, then the closest getter's dartdoc.

Fixes #38963

Bug: https://github.com/dart-lang/sdk/issues/38963
Change-Id: I09f7ae0e5aa49d2536ebefd44dcfd3d6101bb0a6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/148566
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
2020-05-20 23:00:20 +00:00

216 lines
7.6 KiB
Dart

// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/protocol/protocol_generated.dart'
show HoverInformation;
import 'package:analysis_server/src/computer/computer_overrides.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/element_locator.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dartdoc/dartdoc_directive_info.dart';
import 'package:path/path.dart' as path;
/// A computer for the hover at the specified offset of a Dart
/// [CompilationUnit].
class DartUnitHoverComputer {
final DartdocDirectiveInfo _dartdocInfo;
final CompilationUnit _unit;
final int _offset;
DartUnitHoverComputer(this._dartdocInfo, this._unit, this._offset);
bool get _isNonNullableByDefault {
return _unit.declaredElement.library.isNonNullableByDefault;
}
/// Returns the computed hover, maybe `null`.
HoverInformation compute() {
var node = NodeLocator(_offset).searchWithin(_unit);
if (node == null) {
return null;
}
if (node.parent is TypeName &&
node.parent.parent is ConstructorName &&
node.parent.parent.parent is InstanceCreationExpression) {
node = node.parent.parent.parent;
}
if (node.parent is ConstructorName &&
node.parent.parent is InstanceCreationExpression) {
node = node.parent.parent;
}
if (node is Expression) {
var expression = node;
// For constructor calls the whole expression is selected (above) but this
// results in the range covering the whole call so narrow it to just the
// ConstructorName.
var hover = expression is InstanceCreationExpression
? HoverInformation(
expression.constructorName.offset,
expression.constructorName.length,
)
: HoverInformation(expression.offset, expression.length);
// element
var element = ElementLocator.locate(expression);
if (element != null) {
// variable, if synthetic accessor
if (element is PropertyAccessorElement) {
PropertyAccessorElement accessor = element;
if (accessor.isSynthetic) {
element = accessor.variable;
}
}
// description
hover.elementDescription = _elementDisplayString(element);
if (node is InstanceCreationExpression && node.keyword == null) {
var prefix = node.isConst ? '(const) ' : '(new) ';
hover.elementDescription = prefix + hover.elementDescription;
}
hover.elementKind = element.kind.displayName;
hover.isDeprecated = element.hasDeprecated;
// not local element
if (element.enclosingElement is! ExecutableElement) {
// containing class
var containingClass = element.thisOrAncestorOfType<ClassElement>();
if (containingClass != null) {
hover.containingClassDescription = containingClass.displayName;
}
// containing library
var library = element.library;
if (library != null) {
var uri = library.source.uri;
if (uri.scheme != '' && uri.scheme == 'file') {
// for 'file:' URIs, use the path after the project root
var analysisSession = _unit.declaredElement.session;
var context = analysisSession.resourceProvider.pathContext;
var projectRootDir =
analysisSession.analysisContext.contextRoot.root.path;
var relativePath =
context.relative(context.fromUri(uri), from: projectRootDir);
if (context.style == path.Style.windows) {
var pathList = context.split(relativePath);
hover.containingLibraryName = pathList.join('/');
} else {
hover.containingLibraryName = relativePath;
}
} else {
hover.containingLibraryName = uri.toString();
}
hover.containingLibraryPath = library.source.fullName;
}
}
// documentation
hover.dartdoc = computeDocumentation(_dartdocInfo, element);
}
// parameter
hover.parameter = _elementDisplayString(
expression.staticParameterElement,
);
// types
{
var parent = expression.parent;
DartType staticType;
if (element == null || element is VariableElement) {
staticType = _getTypeOfDeclarationOrReference(node);
}
if (parent is MethodInvocation && parent.methodName == expression) {
staticType = parent.staticInvokeType;
if (staticType != null && staticType.isDynamic) {
staticType = null;
}
}
hover.staticType = _typeDisplayString(staticType);
}
// done
return hover;
}
// not an expression
return null;
}
String _elementDisplayString(Element element) {
return element?.getDisplayString(
withNullability: _isNonNullableByDefault,
);
}
String _typeDisplayString(DartType type) {
return type?.getDisplayString(withNullability: _isNonNullableByDefault);
}
static String computeDocumentation(
DartdocDirectiveInfo dartdocInfo, Element element) {
// TODO(dantup) We're reusing this in parameter information - move it
// somewhere shared?
if (element is FieldFormalParameterElement) {
element = (element as FieldFormalParameterElement).field;
}
if (element is ParameterElement) {
element = element.enclosingElement;
}
if (element == null) {
// This can happen when the code is invalid, such as having a field formal
// parameter for a field that does not exist.
return null;
}
Element documentedElement;
Element documentedGetter;
// Look for documentation comments of overridden members
var overridden = findOverriddenElements(element);
for (var candidate in [
element,
...overridden.superElements,
...overridden.interfaceElements
]) {
if (candidate.documentationComment != null) {
documentedElement = candidate;
break;
}
if (documentedGetter == null &&
candidate is PropertyAccessorElement &&
candidate.isSetter) {
var getter = candidate.correspondingGetter;
if (getter != null && getter.documentationComment != null) {
documentedGetter = getter;
}
}
}
// Use documentation of a corresponding getter if setters don't have it
documentedElement ??= documentedGetter;
if (documentedElement == null) {
return null;
}
var rawDoc = documentedElement.documentationComment;
var result = dartdocInfo.processDartdoc(rawDoc);
var documentedElementClass = documentedElement.enclosingElement;
if (documentedElementClass != element.enclosingElement) {
result += '\n\nCopied from `${documentedElementClass.displayName}`.';
}
return result;
}
static DartType _getTypeOfDeclarationOrReference(Expression node) {
if (node is SimpleIdentifier) {
var element = node.staticElement;
if (element is VariableElement) {
if (node.inDeclarationContext()) {
return element.type;
}
var parent2 = node.parent.parent;
if (parent2 is NamedExpression && parent2.name.label == node) {
return element.type;
}
}
}
return node.staticType;
}
}