Skip to content

Commit

Permalink
Version 2.14.0-369.0.dev
Browse files Browse the repository at this point in the history
Merge commit '2a99af9bc93e8fec4f4746168803fdeaff239c7b' into 'dev'
  • Loading branch information
Dart CI committed Jul 30, 2021
2 parents fa72449 + 2a99af9 commit 2e22cdf
Show file tree
Hide file tree
Showing 14 changed files with 385 additions and 121 deletions.
182 changes: 149 additions & 33 deletions pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
import 'package:analysis_server/lsp_protocol/protocol_special.dart';
import 'package:analysis_server/plugin/edit/assist/assist_core.dart';
import 'package:analysis_server/plugin/edit/fix/fix_core.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/plugin/plugin_manager.dart';
import 'package:analysis_server/src/protocol_server.dart' hide Position;
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/assist_internal.dart';
Expand All @@ -22,13 +21,36 @@ import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart'
show InconsistentAnalysisException;
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
import 'package:collection/collection.dart' show groupBy;

class CodeActionHandler extends MessageHandler<CodeActionParams,
List<Either2<Command, CodeAction>>> {
// Because server+plugin results are different types and we lose
// priorites when converting them to CodeActions, store the priorities
// against each action in an expando. This avoids wrapping CodeActions in
// another wrapper class (since we can't modify the LSP-spec-generated
// CodeAction class).
final codeActionPriorities = Expando<int>();

/// A comparator that can be used to sort [CodeActions]s using priorties
/// in [codeActionPriorities]. The highest number priority will be sorted
/// before lower number priorityies. Items with the same relevance are sorted
/// alphabetically by their title.
late final Comparator<CodeAction> _codeActionComparator =
(CodeAction a, CodeAction b) {
// We should never be sorting actions without priorities.
final aPriority = codeActionPriorities[a] ?? 0;
final bPriority = codeActionPriorities[b] ?? 0;
if (aPriority != bPriority) {
return bPriority - aPriority;
}
return a.title.compareTo(b.title);
};

CodeActionHandler(LspAnalysisServer server) : super(server);

@override
Expand Down Expand Up @@ -147,25 +169,25 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
/// 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.
CodeAction _createAssistAction(Assist assist) {
CodeAction _createAssistAction(SourceChange change) {
return CodeAction(
title: assist.change.message,
kind: toCodeActionKind(assist.change.id, CodeActionKind.Refactor),
title: change.message,
kind: toCodeActionKind(change.id, CodeActionKind.Refactor),
diagnostics: const [],
edit: createWorkspaceEdit(server, assist.change),
edit: createWorkspaceEdit(server, change),
);
}

/// 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.
CodeAction _createFixAction(Fix fix, Diagnostic diagnostic) {
CodeAction _createFixAction(SourceChange change, Diagnostic diagnostic) {
return CodeAction(
title: fix.change.message,
kind: toCodeActionKind(fix.change.id, CodeActionKind.QuickFix),
title: change.message,
kind: toCodeActionKind(change.id, CodeActionKind.QuickFix),
diagnostics: [diagnostic],
edit: createWorkspaceEdit(server, fix.change),
edit: createWorkspaceEdit(server, change),
);
}

Expand Down Expand Up @@ -222,6 +244,7 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
Future<List<Either2<Command, CodeAction>>> _getAssistActions(
bool Function(CodeActionKind?) shouldIncludeKind,
bool supportsLiteralCodeActions,
String path,
Range range,
int offset,
int length,
Expand All @@ -236,13 +259,28 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
length,
);
final processor = AssistProcessor(context);
final assists = await processor.compute();
assists.sort(Assist.SORT_BY_RELEVANCE);

final assistActions =
_dedupeActions(assists.map(_createAssistAction), range.start);

return assistActions
final serverFuture = processor.compute();
final pluginFuture = _getPluginAssistChanges(path, offset, length);

final assists = await serverFuture;
final pluginChanges = await pluginFuture;

final codeActions = <CodeAction>[];
codeActions.addAll(assists.map((assist) {
final action = _createAssistAction(assist.change);
codeActionPriorities[action] = assist.kind.priority;
return action;
}));
codeActions.addAll(pluginChanges.map((change) {
final action = _createAssistAction(change.change);
codeActionPriorities[action] = change.priority;
return action;
}));

final dedupedCodeActions = _dedupeActions(codeActions, range.start);
dedupedCodeActions.sort(_codeActionComparator);

return dedupedCodeActions
.where((action) => shouldIncludeKind(action.kind))
.map((action) => Either2<Command, CodeAction>.t2(action))
.toList();
Expand All @@ -268,11 +306,11 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
final results = await Future.wait([
_getSourceActions(shouldIncludeKind, supportsLiterals,
supportsWorkspaceApplyEdit, path),
_getAssistActions(
shouldIncludeKind, supportsLiterals, range, offset, length, unit),
_getAssistActions(shouldIncludeKind, supportsLiterals, path, range,
offset, length, unit),
_getRefactorActions(
shouldIncludeKind, supportsLiterals, path, offset, length, unit),
_getFixActions(shouldIncludeKind, supportsLiterals,
_getFixActions(shouldIncludeKind, supportsLiterals, path, offset,
supportedDiagnosticTags, range, unit),
]);
final flatResults = results.expand((x) => x).toList();
Expand All @@ -283,22 +321,23 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
Future<List<Either2<Command, CodeAction>>> _getFixActions(
bool Function(CodeActionKind?) shouldIncludeKind,
bool supportsLiteralCodeActions,
String path,
int offset,
Set<DiagnosticTag> supportedDiagnosticTags,
Range range,
ResolvedUnitResult unit,
) async {
final clientSupportsCodeDescription =
server.clientCapabilities?.diagnosticCodeDescription ?? false;
// TODO(dantup): We may be missing fixes for pubspec, analysis_options,
// android manifests (see _computeServerErrorFixes in EditDomainHandler).
final lineInfo = unit.lineInfo;
final codeActions = <CodeAction>[];
final fixContributor = DartFixContributor();

try {
final errorCodeCounts = <ErrorCode, int>{};
// Count the errors by code so we know whether to include a fix-all.
for (final error in unit.errors) {
errorCodeCounts[error.errorCode] =
(errorCodeCounts[error.errorCode] ?? 0) + 1;
}
final pluginFuture = _getPluginFixActions(unit, offset);

try {
for (final error in unit.errors) {
// Server lineNumber is one-based so subtract one.
var errorLine = lineInfo.getLocation(error.offset).lineNumber - 1;
Expand All @@ -317,22 +356,44 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
}, extensionCache: server.getExtensionCacheFor(unit));
final fixes = await fixContributor.computeFixes(context);
if (fixes.isNotEmpty) {
fixes.sort(Fix.SORT_BY_RELEVANCE);

final diagnostic = toDiagnostic(
unit,
error,
supportedTags: supportedDiagnosticTags,
clientSupportsCodeDescription:
server.clientCapabilities?.diagnosticCodeDescription ?? false,
clientSupportsCodeDescription: clientSupportsCodeDescription,
);
codeActions.addAll(
fixes.map((fix) => _createFixAction(fix, diagnostic)),
fixes.map((fix) {
final action = _createFixAction(fix.change, diagnostic);
codeActionPriorities[action] = fix.kind.priority;
return action;
}),
);
}
}

Diagnostic pluginErrorToDiagnostic(AnalysisError error) {
return pluginToDiagnostic(
(_) => lineInfo,
error,
supportedTags: supportedDiagnosticTags,
clientSupportsCodeDescription: clientSupportsCodeDescription,
);
}

final pluginFixes = await pluginFuture;
final pluginFixActions = pluginFixes.expand(
(fix) => fix.fixes.map((fixChange) {
final action = _createFixAction(
fixChange.change, pluginErrorToDiagnostic(fix.error));
codeActionPriorities[action] = fixChange.priority;
return action;
}),
);
codeActions.addAll(pluginFixActions);

final dedupedActions = _dedupeActions(codeActions, range.start);
dedupedActions.sort(_codeActionComparator);

return dedupedActions
.where((action) => shouldIncludeKind(action.kind))
Expand All @@ -346,6 +407,61 @@ class CodeActionHandler extends MessageHandler<CodeActionParams,
}
}

Future<Iterable<plugin.PrioritizedSourceChange>> _getPluginAssistChanges(
String path, int offset, int length) async {
final requestParams = plugin.EditGetAssistsParams(path, offset, length);
final driver = server.getAnalysisDriver(path);

Map<PluginInfo, Future<plugin.Response>> pluginFutures;
if (driver == null) {
pluginFutures = <PluginInfo, Future<plugin.Response>>{};
} else {
pluginFutures = server.pluginManager.broadcastRequest(
requestParams,
contextRoot: driver.analysisContext!.contextRoot,
);
}

final pluginChanges = <plugin.PrioritizedSourceChange>[];
final responses =
await waitForResponses(pluginFutures, requestParameters: requestParams);

for (final response in responses) {
final result = plugin.EditGetAssistsResult.fromResponse(response);
pluginChanges.addAll(result.assists);
}

return pluginChanges;
}

Future<Iterable<plugin.AnalysisErrorFixes>> _getPluginFixActions(
ResolvedUnitResult unit, int offset) async {
final file = unit.path;
final requestParams = plugin.EditGetFixesParams(file, offset);
final driver = server.getAnalysisDriver(file);

Map<PluginInfo, Future<plugin.Response>> pluginFutures;
if (driver == null) {
pluginFutures = <PluginInfo, Future<plugin.Response>>{};
} else {
pluginFutures = server.pluginManager.broadcastRequest(
requestParams,
contextRoot: driver.analysisContext!.contextRoot,
);
}

final pluginFixes = <plugin.AnalysisErrorFixes>[];
final responses =
await waitForResponses(pluginFutures, requestParameters: requestParams);

for (final response in responses) {
final result = plugin.EditGetFixesResult.fromResponse(response);
pluginFixes.addAll(result.fixes);
}

return pluginFixes;
}

Future<List<Either2<Command, CodeAction>>> _getRefactorActions(
bool Function(CodeActionKind) shouldIncludeKind,
bool supportsLiteralCodeActions,
Expand Down
17 changes: 14 additions & 3 deletions pkg/analysis_server/lib/src/lsp/mapping.dart
Original file line number Diff line number Diff line change
Expand Up @@ -866,19 +866,30 @@ lsp.ClosingLabel toClosingLabel(
range: toRange(lineInfo, label.offset, label.length),
label: label.label);

lsp.CodeActionKind toCodeActionKind(String? id, lsp.CodeActionKind fallback) {
/// Converts [id] to a [CodeActionKind] using [fallbackOrPrefix] as a fallback
/// or a prefix if the ID is not already a fix/refactor.
lsp.CodeActionKind toCodeActionKind(
String? id, lsp.CodeActionKind fallbackOrPrefix) {
if (id == null) {
return fallback;
return fallbackOrPrefix;
}
// Dart fixes and assists start with "dart.assist." and "dart.fix." but in LSP
// we want to use the predefined prefixes for CodeActions.
final newId = id
var newId = id
.replaceAll('dart.assist', lsp.CodeActionKind.Refactor.toString())
.replaceAll('dart.fix', lsp.CodeActionKind.QuickFix.toString())
.replaceAll(
'analysisOptions.assist', lsp.CodeActionKind.Refactor.toString())
.replaceAll(
'analysisOptions.fix', lsp.CodeActionKind.QuickFix.toString());

// If the ID does not start with either of the kinds above, prefix it as
// it will be an unqualified ID from a plugin.
if (!newId.startsWith(lsp.CodeActionKind.Refactor.toString()) &&
!newId.startsWith(lsp.CodeActionKind.QuickFix.toString())) {
newId = '$fallbackOrPrefix.$newId';
}

return lsp.CodeActionKind(newId);
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/analysis_server/lib/src/utilities/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ class TestPluginManager implements PluginManager {
plugin.AnalysisUpdateContentParams? analysisUpdateContentParams;
plugin.RequestParams? broadcastedRequest;
Map<PluginInfo, Future<plugin.Response>>? broadcastResults;
Map<PluginInfo, Future<plugin.Response>>? Function(plugin.RequestParams)?
handleRequest;

@override
List<PluginInfo> plugins = [];
Expand Down Expand Up @@ -192,7 +194,9 @@ class TestPluginManager implements PluginManager {
plugin.RequestParams params,
{analyzer.ContextRoot? contextRoot}) {
broadcastedRequest = params;
return broadcastResults ?? <PluginInfo, Future<plugin.Response>>{};
return handleRequest?.call(params) ??
broadcastResults ??
<PluginInfo, Future<plugin.Response>>{};
}

@override
Expand Down
Loading

0 comments on commit 2e22cdf

Please sign in to comment.