blob: 55f01082b4685da4c64ed50d61f00b2b7109c268 [file] [log] [blame]
// Copyright (c) 2024, 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 Declaration, Element;
import 'package:analysis_server/src/lsp/client_configuration.dart';
import 'package:analysis_server/src/lsp/error_or.dart';
import 'package:analysis_server/src/lsp/handlers/code_lens/abstract_code_lens_provider.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer/src/utilities/extensions/element.dart';
class AugmentationCodeLensProvider extends AbstractCodeLensProvider {
AugmentationCodeLensProvider(super.server);
LspClientCodeLensConfiguration get codeLens =>
server.lspClientConfiguration.global.codeLens;
@override
Future<ErrorOr<List<CodeLens>>> handle(
CodeLensParams params,
MessageInfo message,
CancellationToken token,
Map<String, LineInfo?> lineInfoCache,
) async {
var performance = message.performance;
var path = pathOfDoc(params.textDocument);
var unit = await performance.runAsync(
'requireResolvedUnit',
(_) async => path.mapResult(requireResolvedUnit),
);
return await unit.mapResult((result) {
return performance.runAsync(
'_getCodeLenses',
(performance) =>
_getCodeLenses(result, token, performance, lineInfoCache),
);
});
}
@override
bool isAvailable(CodeLensParams params) {
return isDartDocument(params.textDocument) &&
// We need to run if either of these are enabled.
(codeLens.augmentation || codeLens.augmented) &&
clientSupportsGoToLocationCommand;
}
Future<ErrorOr<List<CodeLens>>> _getCodeLenses(
ResolvedUnitResult result,
CancellationToken token,
OperationPerformanceImpl performance,
Map<String, LineInfo?> lineInfoCache,
) async {
var lineInfo = result.lineInfo;
var codeLenses = <CodeLens>[];
/// Helper to add a CodeLens at [declaration] to [target] for [title].
void addCodeLens(String title, Element declaration, Element target) {
var command = getNavigationCommand(title, target, lineInfoCache);
if (command != null && declaration.nameOffset != -1) {
var range = toRange(
lineInfo,
declaration.nameOffset,
declaration.nameLength,
);
codeLenses.add(CodeLens(range: range, command: command));
}
}
var computer = _AugmentationComputer(result);
if (codeLens.augmented) {
for (var MapEntry(key: declaration, value: augmentated)
in computer.augmenteds.entries) {
addCodeLens('Go to Augmented', declaration, augmentated);
}
}
if (codeLens.augmentation) {
for (var MapEntry(key: declaration, value: augmentation)
in computer.augmentations.entries) {
addCodeLens('Go to Augmentation', declaration, augmentation);
}
}
return success(codeLenses);
}
}
class _AugmentationComputer {
/// A mapping of declarations to their augmentations.
final Map<Element, Element> augmentations = {};
/// A mapping of augmentations to their declarations.
final Map<Element, Element> augmenteds = {};
final ResolvedUnitResult result;
_AugmentationComputer(this.result) {
result.unit.declaredElement?.accept(_AugmentationVisitor(this));
}
void recordAugmentation(Element declaration, Element augmentation) {
augmentations[declaration] = augmentation;
}
void recordAugmented(Element declaration, Element augmented) {
augmenteds[declaration] = augmented;
}
}
/// Visits an AST and records mappings from augmentations to the declarations
/// they augment, and from declarations to their augmentations.
class _AugmentationVisitor extends GeneralizingElementVisitor<void> {
final _AugmentationComputer _computer;
_AugmentationVisitor(this._computer);
@override
void visitElement(Element element) {
assert(element.source?.fullName == _computer.result.path);
assert(element == element.declaration);
if (!element.isSynthetic) {
var agumentation = element.augmentation;
if (agumentation != null) {
_computer.recordAugmentation(element, agumentation);
}
var augmented = switch (element) {
ExecutableElement element => element.augmentationTarget,
InstanceElement element => element.augmentationTarget,
PropertyInducingElement element => element.augmentationTarget,
_ => null,
};
if (augmented != null) {
_computer.recordAugmented(element, augmented);
}
}
super.visitElement(element);
}
}