blob: f73ecc1c16294e1f43f14ae6e427279204137a01 [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 'dart:async';
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/client_capabilities.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/protocol_server.dart' as protocol;
import 'package:analysis_server/src/request_handler_mixin.dart';
import 'package:analyzer/dart/analysis/analysis_options.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/analysis/results.dart' as engine;
import 'package:meta/meta.dart';
typedef CodeActionWithPriority = ({CodeAction action, int priority});
typedef CodeActionWithPriorityAndIndex = ({
CodeAction action,
int priority,
int index
});
/// A base for classes that produce [CodeAction]s for the LSP handler.
abstract class AbstractCodeActionsProducer
with RequestHandlerMixin<LspAnalysisServer> {
final File file;
final LineInfo lineInfo;
final int offset;
final int length;
final bool Function(CodeActionKind?) shouldIncludeKind;
final LspClientCapabilities capabilities;
final AnalysisOptions analysisOptions;
@override
final LspAnalysisServer server;
AbstractCodeActionsProducer(
this.server,
this.file,
this.lineInfo, {
required this.offset,
required this.length,
required this.shouldIncludeKind,
required this.capabilities,
required this.analysisOptions,
});
String get name;
String get path => file.path;
Set<DiagnosticTag> get supportedDiagnosticTags => capabilities.diagnosticTags;
bool get supportsApplyEdit => capabilities.applyEdit;
bool get supportsCodeDescription => capabilities.diagnosticCodeDescription;
bool get supportsLiterals => capabilities.literalCodeActions;
/// Creates a CodeAction to apply this assist. Note: This code will fetch the
/// version of each document being modified so it's important to call this
/// immediately after computing edits to ensure the document is not modified
/// before the version number is read.
@protected
CodeAction createAssistAction(
protocol.SourceChange change, String path, LineInfo lineInfo) {
return CodeAction(
title: change.message,
kind: toCodeActionKind(change.id, CodeActionKind.Refactor),
diagnostics: const [],
command: createLogActionCommand(change.id),
edit: createWorkspaceEdit(server, change,
allowSnippets: true, filePath: path, lineInfo: lineInfo),
);
}
/// Create an LSP [Diagnostic] for [error].
@protected
Diagnostic createDiagnostic(
LineInfo lineInfo, engine.ErrorsResultImpl result, AnalysisError error) {
return pluginToDiagnostic(
server.uriConverter,
(_) => lineInfo,
protocol.newAnalysisError_fromEngine(result, error),
supportedTags: supportedDiagnosticTags,
clientSupportsCodeDescription: supportsCodeDescription,
);
}
/// Creates a CodeAction to apply this fix. Note: This code will fetch the
/// version of each document being modified so it's important to call this
/// immediately after computing edits to ensure the document is not modified
/// before the version number is read.
@protected
CodeAction createFixAction(protocol.SourceChange change,
Diagnostic diagnostic, String path, LineInfo lineInfo) {
return CodeAction(
title: change.message,
kind: toCodeActionKind(change.id, CodeActionKind.QuickFix),
diagnostics: [diagnostic],
command: createLogActionCommand(change.id),
edit: createWorkspaceEdit(server, change,
allowSnippets: true, filePath: path, lineInfo: lineInfo),
);
}
/// Creates a command to log that a CodeAction was selected.
///
/// Code Actions that provide their edits inline (and not via a command) do
/// not normally call back to the server when an action is selected so this
/// provides some visibility of them being chosen.
Command? createLogActionCommand(String? action) {
if (action == null) {
return null;
}
return Command(
command: Commands.logAction,
title: 'Log Action',
arguments: [
{'action': action}
],
);
}
@protected
engine.ErrorsResultImpl createResult(
AnalysisSession session, LineInfo lineInfo, List<AnalysisError> errors) {
return engine.ErrorsResultImpl(
session: session,
file: file,
content: file.readAsStringSync(),
uri: server.uriConverter.toClientUri(path),
lineInfo: lineInfo,
isAugmentation: false,
isLibrary: true,
isMacroAugmentation: false,
isPart: false,
errors: errors,
analysisOptions: analysisOptions,
);
}
Future<List<CodeActionWithPriority>> getAssistActions();
Future<List<CodeActionWithPriority>> getFixActions();
Future<List<Either2<CodeAction, Command>>> getRefactorActions();
Future<List<Either2<CodeAction, Command>>> getSourceActions();
/// Return the contents of the [file], or `null` if the file does not exist or
/// cannot be read.
@protected
String? safelyRead(File file) {
try {
return file.readAsStringSync();
} on FileSystemException {
return null;
}
}
}