afcfbbeba8
(Part of https://github.com/dart-lang/sdk/issues/63288) This change migrates the packages owned by the developer experience team to use the new constructor declaration syntax, described in https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations. This change was performed in an automated fashion, by (a) bumping the packages' SDK constraints to `3.13.0-0`, (b) enabling the lints `unnecessary_type_name_in_constructor` and `unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint failures using `dart fix`, and then (d) reformatting the affected files. To ease code review, I've reverted unrelated formatting changes. Since this change requires bumping SDK constaints to `3.13.0-0`, it was only performed on packages that are *not* published on pub. (Packages that *are* published on pub should remain on lower language versions until at least after the stable version of 3.13 is released, so that we don't block users on the stable channel from receiving updates to those packages.) Change-Id: Ibb4daebafd239da58251e838ea6a3f336a6a6964 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505046 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
372 lines
14 KiB
Dart
372 lines
14 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_documentation.dart';
|
|
import 'package:analyzer/dart/ast/ast.dart';
|
|
import 'package:analyzer/dart/ast/syntactic_entity.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/dartdoc/dartdoc_directive_info.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
/// Information about a library to display in a hover.
|
|
typedef _LibraryInfo = ({String? libraryName, String? libraryPath})?;
|
|
typedef _OffsetLength = ({int offset, int length});
|
|
|
|
/// A computer for the hover at the specified offset of a Dart
|
|
/// [CompilationUnit].
|
|
class DartUnitHoverComputer {
|
|
final CompilationUnit _unit;
|
|
final int _offset;
|
|
final DocumentationPreference documentationPreference;
|
|
final DartDocumentationComputer _documentationComputer;
|
|
|
|
new(
|
|
DartdocDirectiveInfo dartdocInfo,
|
|
this._unit,
|
|
this._offset, {
|
|
this.documentationPreference = DocumentationPreference.full,
|
|
}) : _documentationComputer = DartDocumentationComputer(dartdocInfo);
|
|
|
|
/// Returns the computed hover, maybe `null`.
|
|
HoverInformation? compute() {
|
|
var node = _unit.nodeCovering(offset: _offset);
|
|
if (node == null) {
|
|
return null;
|
|
}
|
|
|
|
var locationEntity = _locationEntity(node, _offset);
|
|
node = _targetNode(node);
|
|
if (node == null || locationEntity == null) {
|
|
return null;
|
|
}
|
|
|
|
if (node is CompilationUnitMember ||
|
|
node is CatchClauseParameter ||
|
|
node is EnumConstantDeclaration ||
|
|
node is Expression ||
|
|
node is FormalParameter ||
|
|
node is MethodDeclaration ||
|
|
node is NamedArgument ||
|
|
node is NamedType ||
|
|
node is ConstructorDeclaration ||
|
|
node is DeclaredIdentifier ||
|
|
node is RecordLiteralNamedField ||
|
|
node is VariableDeclaration ||
|
|
node is VariablePattern ||
|
|
node is PatternFieldName ||
|
|
node is PrimaryConstructorBody ||
|
|
node is PrimaryConstructorDeclaration ||
|
|
node is PrimaryConstructorName ||
|
|
node is DartPattern ||
|
|
(node is LibraryDirective && node.name == null) ||
|
|
(node is SimpleIdentifier && node.parent is ImportDirective) ||
|
|
node is ImportPrefixReference) {
|
|
var range = _hoverRange(node, locationEntity);
|
|
var hover = HoverInformation(range.offset, range.length);
|
|
// element
|
|
var element = ElementLocator.locate(node);
|
|
if (element != null) {
|
|
// short code that illustrates the element meaning.
|
|
hover.elementDescription = _elementDisplayString(node, element);
|
|
hover.elementKind = element.kind.displayName;
|
|
hover.isDeprecated = element.isDeprecatedWithKind('use');
|
|
// not local element
|
|
if (element.enclosingElement is! ExecutableElement) {
|
|
// containing class
|
|
hover.containingClassDescription = _containingClass(element);
|
|
// containing library
|
|
var libraryInfo = _libraryInfo(element);
|
|
hover.containingLibraryName = libraryInfo?.libraryName;
|
|
hover.containingLibraryPath = libraryInfo?.libraryPath;
|
|
}
|
|
// documentation
|
|
hover.dartdoc = _documentationComputer.computePreferred(
|
|
element,
|
|
documentationPreference,
|
|
);
|
|
}
|
|
// parameter
|
|
hover.parameter = _parameterDisplayString(node);
|
|
// types
|
|
hover.staticType = _typeDisplayString(node, element);
|
|
// done
|
|
return hover;
|
|
}
|
|
// not an expression
|
|
return null;
|
|
}
|
|
|
|
/// Gets the name of the containing class of [element].
|
|
String? _containingClass(Element element) {
|
|
var containingClass = element.thisOrAncestorOfType<InterfaceElement>();
|
|
return containingClass != null && containingClass != element
|
|
? containingClass.displayName
|
|
: null;
|
|
}
|
|
|
|
/// Gets the display string for [element].
|
|
///
|
|
/// This is usually `element.getDisplayString()` but may contain additional
|
|
/// information to disambiguate things like constructors from types (and
|
|
/// whether they are const).
|
|
String? _elementDisplayString(AstNode node, Element? element) {
|
|
var displayString = element?.displayString(multiline: true);
|
|
|
|
if (displayString != null) {
|
|
if (node is InstanceCreationExpression && node.keyword == null) {
|
|
var prefix = node.isConst ? '(const) ' : '(new) ';
|
|
displayString = prefix + displayString;
|
|
} else if (node is DotShorthandConstructorInvocation) {
|
|
var prefix = node.isConst ? '(const) ' : '(new) ';
|
|
displayString = prefix + displayString;
|
|
}
|
|
}
|
|
|
|
return displayString;
|
|
}
|
|
|
|
/// Computes the range this hover applies to.
|
|
///
|
|
/// This is usually the range of [entity] but may be adjusted for entities
|
|
/// like constructor names.
|
|
_OffsetLength _hoverRange(AstNode node, SyntacticEntity entity) {
|
|
// For constructors, the location should cover the type name and
|
|
// constructor name (for both calls and declarations).
|
|
if (node is InstanceCreationExpression) {
|
|
return (
|
|
offset: node.constructorName.offset,
|
|
length: node.constructorName.length,
|
|
);
|
|
} else if (node is DotShorthandConstructorInvocation) {
|
|
return (offset: node.offset, length: node.length);
|
|
} else if (node is ConstructorDeclaration) {
|
|
var offset =
|
|
node.typeName?.offset ??
|
|
node.newKeyword?.offset ??
|
|
node.factoryKeyword!.offset;
|
|
var end =
|
|
node.name?.end ??
|
|
node.typeName?.end ??
|
|
node.newKeyword?.end ??
|
|
node.factoryKeyword!.end;
|
|
var length = end - offset;
|
|
return (offset: offset, length: length);
|
|
} else if (node is PrimaryConstructorDeclaration) {
|
|
if (node.constructorName != null) {
|
|
return (offset: entity.offset, length: entity.length);
|
|
}
|
|
var offset = node.typeName.offset;
|
|
var end = node.typeName.end;
|
|
var length = end - offset;
|
|
return (offset: offset, length: length);
|
|
} else {
|
|
return (offset: entity.offset, length: entity.length);
|
|
}
|
|
}
|
|
|
|
/// Returns information about the library that contains [element].
|
|
_LibraryInfo _libraryInfo(Element element) {
|
|
var library = element.library;
|
|
if (library == null) {
|
|
return null;
|
|
}
|
|
|
|
var definingSource = library.firstFragment.source;
|
|
var uri = definingSource.uri;
|
|
var analysisSession = _unit.declaredFragment?.element.session;
|
|
|
|
String? libraryName, libraryPath;
|
|
// for 'file:' URIs, use the path after the package root
|
|
if (uri.isScheme('file') && analysisSession != null) {
|
|
var pathContext = analysisSession.resourceProvider.pathContext;
|
|
var libraryFilePath = pathContext.fromUri(uri);
|
|
var contextRoot = analysisSession.analysisContext.contextRoot;
|
|
var workspace = contextRoot.workspace;
|
|
|
|
// Prefer using the root of the package that contains this library, since
|
|
// the context root could now be a Pub Workspace. If we don't find one,
|
|
// fall back to the context root.
|
|
var package = workspace.packages.packageForPath(libraryFilePath);
|
|
var packageRootDir = package?.rootFolder.path ?? contextRoot.root.path;
|
|
|
|
var relativePath = pathContext.relative(
|
|
libraryFilePath,
|
|
from: packageRootDir,
|
|
);
|
|
if (pathContext.style == path.Style.windows) {
|
|
var pathList = pathContext.split(relativePath);
|
|
libraryName = pathList.join('/');
|
|
} else {
|
|
libraryName = relativePath;
|
|
}
|
|
} else {
|
|
libraryName = uri.toString();
|
|
}
|
|
libraryPath = definingSource.fullName;
|
|
|
|
return (libraryName: libraryName, libraryPath: libraryPath);
|
|
}
|
|
|
|
/// Returns the [SyntacticEntity] that should be used as the range for this
|
|
/// hover.
|
|
///
|
|
/// Returns `null` if there is no valid entity for this hover.
|
|
SyntacticEntity? _locationEntity(AstNode node, int offset) {
|
|
return switch (node) {
|
|
BinaryExpression() => node.operator,
|
|
ConditionalExpression()
|
|
when offset >= node.question.offset && offset <= node.question.end =>
|
|
node.question,
|
|
ConditionalExpression()
|
|
when offset >= node.colon.offset && offset <= node.colon.end =>
|
|
node.colon,
|
|
AssignmentExpression() => node.operator,
|
|
PrefixExpression() => node.operator,
|
|
PostfixExpression() => node.operator,
|
|
CatchClauseParameter() => node.name,
|
|
ClassDeclaration() => node.namePart.typeName,
|
|
ConstructorDeclaration() =>
|
|
node.name ?? node.typeName ?? node.newKeyword ?? node.factoryKeyword,
|
|
DeclaredIdentifier() => node.name,
|
|
EnumConstantDeclaration() => node.name,
|
|
EnumDeclaration() => node.namePart.typeName,
|
|
Expression() => node,
|
|
ExtensionDeclaration() => node.name,
|
|
ExtensionTypeDeclaration() => node.namePart.typeName,
|
|
FormalParameter() => node.name,
|
|
FunctionDeclaration() => node.name,
|
|
ImportPrefixReference() => node.name,
|
|
LibraryDirective() => node.libraryKeyword,
|
|
MethodDeclaration() => node.name,
|
|
NamedArgument() => node.name,
|
|
MixinDeclaration() => node.name,
|
|
NameWithTypeParameters() => node.typeName,
|
|
NamedType() => node.name,
|
|
PatternFieldName() => node.name,
|
|
PrimaryConstructorBody() =>
|
|
node.declaration?.constructorName ?? node.declaration?.typeName,
|
|
PrimaryConstructorDeclaration() => node.constructorName ?? node.typeName,
|
|
PrimaryConstructorName() => node.name,
|
|
RecordLiteralNamedField() => node.name,
|
|
TypeAlias() => node.name,
|
|
VariableDeclaration() => node.name,
|
|
VariablePattern() => node.name,
|
|
WildcardPattern() => node.name,
|
|
_ => null,
|
|
};
|
|
}
|
|
|
|
/// Gets the display string for the type of the parameter.
|
|
///
|
|
/// Returns `null` if the node doesn't correspond to a parameter.
|
|
String? _parameterDisplayString(AstNode node) {
|
|
FormalParameterElement? parameter;
|
|
if (node is Argument) {
|
|
parameter = node.correspondingParameter;
|
|
}
|
|
return switch (parameter?.enclosingElement) {
|
|
// Expressions passed as arguments to setters and binary expressions
|
|
// will have parameters here but we don't want them to show as such in
|
|
// hovers because information about those functions are already available
|
|
// by hovering over the function name or the operator.
|
|
SetterElement() => null,
|
|
MethodElement method when method.isOperator => null,
|
|
_ => _elementDisplayString(node, parameter),
|
|
};
|
|
}
|
|
|
|
/// Adjusts the target node for constructors.
|
|
AstNode? _targetNode(AstNode node) {
|
|
var parent = node.parent;
|
|
var parent2 = parent?.parent;
|
|
if (node is ClassNamePart) {
|
|
return parent;
|
|
} else if (parent is NamedType &&
|
|
parent2 is ConstructorName &&
|
|
parent2.parent is InstanceCreationExpression) {
|
|
return parent2.parent;
|
|
} else if (parent is ConstructorName &&
|
|
parent2 is InstanceCreationExpression) {
|
|
return parent2;
|
|
} else if (node is SimpleIdentifier &&
|
|
parent is ConstructorDeclaration &&
|
|
parent.name != null) {
|
|
return parent;
|
|
} else if (node is SimpleIdentifier &&
|
|
parent is DotShorthandConstructorInvocation) {
|
|
return parent;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/// Returns information about the static type of [node].
|
|
String? _typeDisplayString(AstNode node, Element? element) {
|
|
var parent = node.parent;
|
|
DartType? staticType;
|
|
if (node is NamedArgument) {
|
|
staticType = node.correspondingParameter?.type;
|
|
} else if (node is Expression &&
|
|
(element == null ||
|
|
element is VariableElement ||
|
|
element is GetterElement ||
|
|
element is SetterElement)) {
|
|
staticType = _getTypeOfDeclarationOrReference(node);
|
|
} else if (element is VariableElement) {
|
|
staticType = element.type;
|
|
} else if (parent is MethodInvocation && parent.methodName == node) {
|
|
staticType = parent.staticInvokeType;
|
|
if (staticType != null && staticType is DynamicType) {
|
|
staticType = null;
|
|
}
|
|
} else if (parent is PrefixedIdentifier && parent.identifier == node) {
|
|
staticType = parent.identifier.staticType;
|
|
if (staticType != null && staticType is DynamicType) {
|
|
staticType = null;
|
|
}
|
|
} else if (parent is DotShorthandInvocation && parent.memberName == node) {
|
|
staticType = parent.staticInvokeType;
|
|
if (staticType != null && staticType is DynamicType) {
|
|
staticType = null;
|
|
}
|
|
} else if (node is PatternFieldName && parent is PatternField) {
|
|
staticType = parent.pattern.matchedValueType;
|
|
} else if (node is DartPattern) {
|
|
staticType = node.matchedValueType;
|
|
}
|
|
return staticType?.getDisplayString();
|
|
}
|
|
|
|
static DartType? _getTypeOfDeclarationOrReference(Expression node) {
|
|
if (node is SimpleIdentifier) {
|
|
var element = node.element;
|
|
if (element is VariableElement) {
|
|
if (node.inDeclarationContext()) {
|
|
return element.type;
|
|
}
|
|
}
|
|
var parent = node.parent;
|
|
var parent2 = parent?.parent;
|
|
|
|
if (parent is AssignmentExpression && parent.leftHandSide == node) {
|
|
// Direct setter reference
|
|
return parent.writeType;
|
|
} else if (parent2 is AssignmentExpression &&
|
|
parent2.leftHandSide == parent) {
|
|
if (parent is PrefixedIdentifier && parent.identifier == node) {
|
|
// Prefixed setter (`myInstance.foo =`)
|
|
return parent2.writeType;
|
|
} else if (parent is PropertyAccess && parent.propertyName == node) {
|
|
// Expression prefix (`A<int>().foo =`)
|
|
return parent2.writeType;
|
|
}
|
|
}
|
|
}
|
|
return node.staticType;
|
|
}
|
|
}
|