blob: 883fc081bc3ccbcea0fe3a418e6ffa0ad073a410 [file] [log] [blame]
// Copyright (c) 2022, 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/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/utilities/extensions/ast.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/source/line_info.dart';
/// A computer for LSP Inlay Hints.
///
/// Inlay hints are text labels used to show inferred labels such as type and
/// argument names where they are not already explicitly present in the source
/// but are being inferred.
class DartInlayHintComputer {
final LineInfo _lineInfo;
final CompilationUnit _unit;
final bool _isNonNullableByDefault;
final List<InlayHint> _hints = [];
DartInlayHintComputer(ResolvedUnitResult result)
: _unit = result.unit,
_lineInfo = result.lineInfo,
_isNonNullableByDefault = result.unit.isNonNullableByDefault;
List<InlayHint> compute() {
_unit.accept(_DartInlayHintComputerVisitor(this));
return _hints;
}
/// Adds a parameter name hint before [node] showing a label for [name].
///
/// A colon and padding will be added between the hint and [node]
/// automatically.
void _addParameterNamePrefix(SyntacticEntity nodeOrToken, String name) {
final offset = nodeOrToken.offset;
final position = toPosition(_lineInfo.getLocation(offset));
final labelParts = Either2<List<InlayHintLabelPart>, String>.t2('$name:');
_hints.add(InlayHint(
label: labelParts,
position: position,
kind: InlayHintKind.Parameter,
paddingRight: true,
));
}
/// Adds a type hint before [node] showing a label for the type [type].
///
/// Padding will be added between the hint and [node] automatically.
void _addTypePrefix(SyntacticEntity nodeOrToken, DartType type) {
final offset = nodeOrToken.offset;
final position = toPosition(_lineInfo.getLocation(offset));
final label =
type.getDisplayString(withNullability: _isNonNullableByDefault);
final labelParts = Either2<List<InlayHintLabelPart>, String>.t2(label);
_hints.add(InlayHint(
label: labelParts,
position: position,
kind: InlayHintKind.Type,
paddingRight: true,
));
}
}
/// An AST visitor for [DartInlayHintComputer].
class _DartInlayHintComputerVisitor extends GeneralizingAstVisitor<void> {
final DartInlayHintComputer _computer;
_DartInlayHintComputerVisitor(this._computer);
@override
void visitArgumentList(ArgumentList node) {
super.visitArgumentList(node);
for (final argument in node.arguments) {
if (argument is! NamedExpression) {
final parameter = argument.staticParameterElement;
if (parameter != null) {
_computer._addParameterNamePrefix(argument, parameter.name);
}
}
}
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
super.visitFunctionDeclaration(node);
// Has explicit type.
if (node.returnType != null) {
return;
}
// Don't add "void" for setters.
if (node.isSetter) {
return;
}
final declaration = node.declaredElement2;
if (declaration != null) {
// For getters/setters, the type must come before the property keyword,
// not the name.
final token = node.propertyKeyword ?? node.name;
_computer._addTypePrefix(token, declaration.returnType);
}
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
super.visitMethodDeclaration(node);
// Has explicit type.
if (node.returnType != null) {
return;
}
final declaration = node.declaredElement2;
if (declaration != null) {
_computer._addTypePrefix(node.name, declaration.returnType);
}
}
@override
void visitSimpleFormalParameter(SimpleFormalParameter node) {
super.visitSimpleFormalParameter(node);
// Has explicit type.
if (node.isExplicitlyTyped) {
return;
}
final declaration = node.declaredElement;
if (declaration != null) {
// Prefer to insert before `name` to avoid going before keywords like
// `required`.
_computer._addTypePrefix(node.name ?? node, declaration.type);
}
}
@override
void visitVariableDeclaration(VariableDeclaration node) {
super.visitVariableDeclaration(node);
final parent = node.parent;
// Unexpected parent or has explicit type.
if (parent is! VariableDeclarationList || parent.type != null) {
return;
}
final declaration = node.declaredElement2;
if (declaration != null) {
_computer._addTypePrefix(node, declaration.type);
}
}
}