| // 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' hide Element; |
| 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/element.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 the name for |
| /// [parameter]. |
| /// |
| /// A colon and padding will be added between the hint and [node] |
| /// automatically. |
| void _addParameterNamePrefix( |
| SyntacticEntity nodeOrToken, ParameterElement parameter) { |
| final name = parameter.name; |
| final offset = nodeOrToken.offset; |
| final position = toPosition(_lineInfo.getLocation(offset)); |
| final labelParts = Either2<List<InlayHintLabelPart>, String>.t1([ |
| InlayHintLabelPart( |
| value: '$name:', |
| location: _locationForElement(parameter), |
| ), |
| ]); |
| _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>.t1([ |
| InlayHintLabelPart( |
| value: label, |
| location: _locationForElement(type.element), |
| ) |
| ]); |
| _hints.add(InlayHint( |
| label: labelParts, |
| position: position, |
| kind: InlayHintKind.Type, |
| paddingRight: true, |
| )); |
| } |
| |
| Location? _locationForElement(Element? element) { |
| if (element == null) { |
| return null; |
| } |
| final compilationUnit = |
| element.thisOrAncestorOfType<CompilationUnitElement>(); |
| final path = compilationUnit?.source.fullName; |
| final lineInfo = compilationUnit?.lineInfo; |
| if (path == null || lineInfo == null) { |
| return null; |
| } |
| return Location( |
| uri: Uri.file(path), |
| range: toRange(lineInfo, element.nameOffset, element.nameLength), |
| ); |
| } |
| } |
| |
| /// 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); |
| } |
| } |
| } |
| } |
| |
| @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.declaredElement; |
| 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.declaredElement; |
| 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.declaredElement; |
| if (declaration != null) { |
| _computer._addTypePrefix(node, declaration.type); |
| } |
| } |
| } |