blob: 44cfcb2088fe43c7546ac40df284bf7378a90c07 [file] [log] [blame]
// Copyright (c) 2018, 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/src/computer/computer_documentation.dart';
import 'package:analysis_server/src/protocol_server.dart' hide Element;
import 'package:analysis_server/src/utilities/extensions/ast.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/dartdoc/dartdoc_directive_info.dart';
/// A computer for the signature at the specified offset of a Dart
/// [CompilationUnit].
class DartUnitSignatureComputer {
final AstNode? _node;
final int _offset;
final DocumentationPreference documentationPreference;
final DartDocumentationComputer _documentationComputer;
DartUnitSignatureComputer(
DartdocDirectiveInfo dartdocInfo,
CompilationUnit unit,
this._offset, {
this.documentationPreference = DocumentationPreference.full,
}) : _documentationComputer = DartDocumentationComputer(dartdocInfo),
_node = unit.nodeCovering(offset: _offset);
bool get offsetIsValid => _node != null;
/// Returns the computed signature information, maybe `null`.
SignatureInformation? compute() {
var argumentAndList = _findArgumentAndList();
if (argumentAndList == null) {
return null;
}
var (argumentList, argument) = argumentAndList;
String? name;
Element? element;
List<FormalParameterElement>? parameters;
var parent = argumentList.parent;
if (parent is MethodInvocation) {
name = parent.methodName.name;
element = ElementLocator.locate2(parent);
parameters =
element is FunctionTypedElement ? element.formalParameters : null;
} else if (parent is InstanceCreationExpression) {
name = parent.constructorName.type.qualifiedName;
var constructorName = parent.constructorName.name;
if (constructorName != null) {
name += '.${constructorName.name}';
}
element = ElementLocator.locate2(parent);
parameters =
element is FunctionTypedElement ? element.formalParameters : null;
} else if (parent case FunctionExpressionInvocation(
function: Identifier function,
)) {
name = function.name;
if (function.staticType case FunctionType functionType) {
// Standard function expression.
element = function.element;
parameters = functionType.formalParameters;
} else if (parent.element case ExecutableElement executableElement) {
// Callable class instance (where we'll look at the `call` method).
element = executableElement;
parameters = executableElement.formalParameters;
}
}
if (name == null || element == null || parameters == null) {
return null;
}
// Try to compute the active parameter so the IDE can highlight it.
int? activeParameterIndex;
if (argument case Expression(:var correspondingParameter?)) {
// If we know the active parameter, use its index.
activeParameterIndex = parameters.indexOf(correspondingParameter);
} else if (argument is! NamedExpression) {
// If we're not a named expression, then we can count how many positional
// parameters there are before us, and then find the index of the same
// index positional parameter.
var positionalArgsToSkip =
argumentList.arguments
.where((argument) => argument is! NamedExpression)
.takeWhile((argument) => argument.end < _offset)
.length;
for (var i = 0; i < parameters.length; i++) {
if (parameters[i].isPositional) {
// This is the first positional parameter after our skips, so this is
// the active parameter.
if (positionalArgsToSkip == 0) {
activeParameterIndex = i;
break;
}
positionalArgsToSkip--;
}
}
}
var dartdoc = _documentationComputer.computePreferred(
element,
documentationPreference,
);
return SignatureInformation(
name: name,
parameters: parameters,
argumentList: argumentList,
activeParameterIndex: activeParameterIndex,
dartdoc: dartdoc,
);
}
/// Return the closest argument list surrounding the [_node] and the node for
/// the active argument (if there is one).
(ArgumentList, AstNode?)? _findArgumentAndList() {
var node = _node;
while (node != null) {
// Certain nodes don't make sense to search above for an argument list
// (for example when inside a function expression).
if (node is FunctionExpression) {
return null;
}
if (node is ArgumentList) {
return (node, null);
}
if (node.parent case ArgumentList list) {
return (list, node);
}
node = node.parent;
}
return null;
}
}
/// Information about a function signature.
class SignatureInformation {
/// The name of the function/method.
final String name;
/// The parameters for the function/method.
final List<FormalParameterElement> parameters;
/// The current argument list at the invocation site.
final ArgumentList argumentList;
/// Documentation for the function/method.
final String? dartdoc;
/// The index in [parameters] for the parameter that matches where the offset
/// was in the invocation list.
///
/// This is only supplied when it can be computed. Positional arguments past
/// the number of positional parameters or named arguments with no matching
/// name will not be returned.
final int? activeParameterIndex;
SignatureInformation({
required this.name,
required this.parameters,
required this.argumentList,
required this.activeParameterIndex,
required this.dartdoc,
});
AnalysisGetSignatureResult toLegacyProtocol() {
return AnalysisGetSignatureResult(
name,
parameters.map(_convertParam).toList(),
dartdoc: dartdoc,
);
}
ParameterInfo _convertParam(FormalParameterElement param) {
return ParameterInfo(
param.isOptionalNamed
? ParameterKind.OPTIONAL_NAMED
: param.isOptionalPositional
? ParameterKind.OPTIONAL_POSITIONAL
: param.isRequiredNamed
? ParameterKind.REQUIRED_NAMED
: ParameterKind.REQUIRED_POSITIONAL,
param.displayName,
param.type.getDisplayString(),
defaultValue: param.defaultValueCode,
);
}
}