DAS plugins: Move Assist code to analysis_server_plugin package

The PluginServer class needs access to AssistProcessor in order to
compute assists. That class needs access to a few other classes, which
then must also be moved:

* assist_core.dart - the Assist class
* assist_dart.dart - the DartAssistContext class
* assist_generators.dart - the registeredAssistGenerators variable
* assist_performance.dart - the AssistPerformance and
  GetAssistsPerformance classes
* assist_processor.dart - the AssistProcessor class with it's
  singular API, `compute()`
* performance.dart - the ProducerRequestPerformance class
* the `addCaretAtOffset` helper function, refactored into a
  `withCaretAt` extension method

This change is functionally a no-op.

Change-Id: Ic883d21e9cc8c3db1f6093f830f01ec6eb9a0976
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/416680
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
This commit is contained in:
Sam Rawlins
2025-03-19 13:44:51 -07:00
committed by Commit Queue
parent 0f2c6d7302
commit 331c700f33
24 changed files with 248 additions and 254 deletions
@@ -29,9 +29,7 @@ import 'package:analysis_server/src/protocol_server.dart' as server;
import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
import 'package:analysis_server/src/server/diagnostic_server.dart';
import 'package:analysis_server/src/server/message_scheduler.dart';
import 'package:analysis_server/src/server/performance.dart';
import 'package:analysis_server/src/services/completion/completion_performance.dart';
import 'package:analysis_server/src/services/correction/assist_performance.dart';
import 'package:analysis_server/src/services/correction/fix_performance.dart';
import 'package:analysis_server/src/services/correction/namespace.dart';
import 'package:analysis_server/src/services/dart_tooling_daemon/dtd_services.dart';
@@ -50,6 +48,8 @@ import 'package:analysis_server/src/utilities/process.dart';
import 'package:analysis_server/src/utilities/request_statistics.dart';
import 'package:analysis_server/src/utilities/tee_string_sink.dart';
import 'package:analysis_server/src/utilities/timing_byte_store.dart';
import 'package:analysis_server_plugin/src/correction/assist_performance.dart';
import 'package:analysis_server_plugin/src/correction/performance.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
@@ -2,9 +2,9 @@
// 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/plugin/edit/assist/assist_core.dart';
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/assist_internal.dart';
import 'package:analysis_server_plugin/src/correction/assist_core.dart';
import 'package:analysis_server_plugin/src/correction/assist_processor.dart';
import 'package:analysis_server_plugin/src/correction/dart_change_workspace.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/instrumentation/service.dart';
@@ -4,7 +4,6 @@
import 'dart:async';
import 'package:analysis_server/plugin/edit/assist/assist_core.dart';
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
@@ -12,8 +11,9 @@ import 'package:analysis_server/src/legacy_analysis_server.dart';
import 'package:analysis_server/src/plugin/result_converter.dart';
import 'package:analysis_server/src/request_handler_mixin.dart';
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/assist_internal.dart';
import 'package:analysis_server/src/services/correction/assist_performance.dart';
import 'package:analysis_server_plugin/src/correction/assist_core.dart';
import 'package:analysis_server_plugin/src/correction/assist_performance.dart';
import 'package:analysis_server_plugin/src/correction/assist_processor.dart';
import 'package:analysis_server_plugin/src/correction/dart_change_workspace.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/src/exception/exception.dart';
@@ -91,7 +91,6 @@ import 'package:analysis_server/src/server/diagnostic_server.dart';
import 'package:analysis_server/src/server/error_notifier.dart';
import 'package:analysis_server/src/server/features.dart';
import 'package:analysis_server/src/server/message_scheduler.dart';
import 'package:analysis_server/src/server/performance.dart';
import 'package:analysis_server/src/server/sdk_configuration.dart';
import 'package:analysis_server/src/services/completion/completion_state.dart';
import 'package:analysis_server/src/services/execution/execution_context.dart';
@@ -99,6 +98,7 @@ import 'package:analysis_server/src/services/flutter/widget_descriptions.dart';
import 'package:analysis_server/src/services/refactoring/legacy/refactoring_manager.dart';
import 'package:analysis_server/src/utilities/extensions/resource_provider.dart';
import 'package:analysis_server/src/utilities/process.dart';
import 'package:analysis_server_plugin/src/correction/performance.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
@@ -5,20 +5,20 @@
import 'dart:async';
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/plugin/edit/assist/assist_core.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/code_actions/abstract_code_actions_producer.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/protocol_server.dart'
hide AnalysisOptions, Position;
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/assist_internal.dart';
import 'package:analysis_server/src/services/correction/assist_performance.dart';
import 'package:analysis_server/src/services/correction/fix_performance.dart';
import 'package:analysis_server/src/services/refactoring/framework/refactoring_context.dart';
import 'package:analysis_server/src/services/refactoring/framework/refactoring_processor.dart';
import 'package:analysis_server/src/services/refactoring/legacy/refactoring.dart';
import 'package:analysis_server_plugin/edit/fix/dart_fix_context.dart';
import 'package:analysis_server_plugin/src/correction/assist_core.dart';
import 'package:analysis_server_plugin/src/correction/assist_performance.dart';
import 'package:analysis_server_plugin/src/correction/assist_processor.dart';
import 'package:analysis_server_plugin/src/correction/dart_change_workspace.dart';
import 'package:analysis_server_plugin/src/correction/fix_processor.dart';
import 'package:analyzer/dart/analysis/results.dart';
@@ -28,8 +28,8 @@ import 'package:analysis_server/src/server/detachable_filesystem_manager.dart';
import 'package:analysis_server/src/server/diagnostic_server.dart';
import 'package:analysis_server/src/server/error_notifier.dart';
import 'package:analysis_server/src/server/message_scheduler.dart';
import 'package:analysis_server/src/server/performance.dart';
import 'package:analysis_server/src/utilities/process.dart';
import 'package:analysis_server_plugin/src/correction/performance.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/error/error.dart';
@@ -1205,7 +1205,12 @@ class LspAnalysisServer extends AnalysisServer {
// TODO(dantup): Consider supporting per-workspace config by
// calling workspace/configuration whenever workspace folders change
// and caching the config for each one.
: _workspaceFolders.map((root) => resourceProvider.pathContext.join(root, excludePath)),
: _workspaceFolders.map(
(root) => resourceProvider.pathContext.join(
root,
excludePath,
),
),
)
.map(pathContext.normalize)
.toSet();
@@ -1,43 +0,0 @@
// 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/src/utilities/strings.dart';
import 'package:analysis_server_plugin/src/correction/performance.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
abstract class ProducerRequestPerformance extends RequestPerformance {
final String path;
final String snippet;
final List<ProducerTiming> producerTimings;
ProducerRequestPerformance({
required String operation,
required this.path,
required super.performance,
super.requestLatency,
super.startTime,
required String content,
required int offset,
required this.producerTimings,
}) : snippet = addCaretAtOffset(content, offset),
super(operation: 'GetAssists');
int get elapsedInMilliseconds => performance.elapsed.inMilliseconds;
}
class RequestPerformance {
static var _nextId = 1;
final int id;
final OperationPerformance performance;
final int? requestLatency;
final String operation;
final DateTime? startTime;
RequestPerformance({
required this.operation,
required this.performance,
this.requestLatency,
this.startTime,
}) : id = _nextId++;
}
@@ -2,8 +2,8 @@
// 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/src/server/performance.dart';
import 'package:analysis_server/src/utilities/strings.dart';
import 'package:analysis_server_plugin/src/correction/performance.dart';
import 'package:analysis_server_plugin/src/utilities/string_extensions.dart';
/// Overall performance of a code completion operation.
class CompletionPerformance extends RequestPerformance {
@@ -18,7 +18,7 @@ class CompletionPerformance extends RequestPerformance {
super.requestLatency,
required String content,
required int offset,
}) : snippet = addCaretAtOffset(content, offset),
}) : snippet = content.withCaretAt(offset),
super(operation: 'Completion');
String get computedSuggestionCountStr {
@@ -2,7 +2,7 @@
// 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/plugin/edit/assist/assist_dart.dart';
import 'package:analysis_server_plugin/src/correction/assist_dart.dart';
import 'package:analysis_server_plugin/src/correction/change_workspace.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/instrumentation/service.dart';
@@ -2,10 +2,6 @@
// 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/plugin/edit/assist/assist_core.dart';
import 'package:analysis_server/plugin/edit/assist/assist_dart.dart';
import 'package:analysis_server/src/services/correction/assist_generators.dart';
import 'package:analysis_server/src/services/correction/assist_performance.dart';
import 'package:analysis_server/src/services/correction/dart/add_diagnostic_property_reference.dart';
import 'package:analysis_server/src/services/correction/dart/add_digit_separators.dart';
import 'package:analysis_server/src/services/correction/dart/add_return_type.dart';
@@ -76,15 +72,8 @@ import 'package:analysis_server/src/services/correction/dart/split_and_condition
import 'package:analysis_server/src/services/correction/dart/split_variable_declaration.dart';
import 'package:analysis_server/src/services/correction/dart/surround_with.dart';
import 'package:analysis_server/src/services/correction/dart/use_curly_braces.dart';
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analysis_server_plugin/src/correction/assist_generators.dart';
import 'package:analysis_server_plugin/src/correction/fix_generators.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer_plugin/utilities/assist/assist.dart'
hide AssistContributor;
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/change_builder/conflicting_edit_exception.dart';
/// The set of built-in generators used to produce assists.
const Set<ProducerGenerator> _builtInGenerators = {
@@ -178,131 +167,3 @@ void registerBuiltInAssistGenerators() {
registeredAssistGenerators.registerMultiGenerator,
);
}
/// The computer for Dart assists.
class AssistProcessor {
final AssistPerformance? _performance;
final DartAssistContext _assistContext;
final Stopwatch _timer = Stopwatch();
final List<Assist> _assists = [];
AssistProcessor(this._assistContext, {AssistPerformance? performance})
: _performance = performance;
Future<List<Assist>> compute() async {
_timer.start();
await _addFromProducers();
_timer.stop();
_performance?.computeTime = _timer.elapsed;
return _assists;
}
void _addAssistFromBuilder(
ChangeBuilder builder,
AssistKind kind, {
List<Object>? args,
}) {
var change = builder.sourceChange;
if (change.edits.isEmpty) {
return;
}
change.id = kind.id;
change.message = formatList(kind.message, args);
_assists.add(Assist(kind, change));
}
Future<void> _addFromProducers() async {
var context = CorrectionProducerContext.createResolved(
libraryResult: _assistContext.libraryResult,
unitResult: _assistContext.unitResult,
selectionOffset: _assistContext.selectionOffset,
selectionLength: _assistContext.selectionLength,
);
Future<void> compute(CorrectionProducer producer) async {
var builder = ChangeBuilder(
workspace: _assistContext.workspace,
eol: producer.eol,
);
try {
if (_performance != null) {
var startTime = _timer.elapsedMilliseconds;
await producer.compute(builder);
_performance.producerTimings.add((
className: producer.runtimeType.toString(),
elapsedTime: _timer.elapsedMilliseconds - startTime,
));
} else {
await producer.compute(builder);
}
var assistKind = producer.assistKind;
if (assistKind != null) {
_addAssistFromBuilder(
builder,
assistKind,
args: producer.assistArguments,
);
}
} on ConflictingEditException catch (exception, stackTrace) {
// Handle the exception by (a) not adding an assist based on the
// producer and (b) logging the exception.
_assistContext.instrumentationService.logException(
exception,
stackTrace,
);
}
}
for (var generator in registeredAssistGenerators.producerGenerators) {
if (!_generatorAppliesToAnyLintRule(
generator,
registeredAssistGenerators.lintRuleMap[generator] ?? {},
)) {
var producer = generator(context: context);
await compute(producer);
}
}
for (var multiGenerator
in registeredAssistGenerators.multiProducerGenerators) {
var multiProducer = multiGenerator(context: context);
for (var producer in await multiProducer.producers) {
await compute(producer);
}
}
}
/// Returns whether [generator] applies to any enabled lint rule, among
/// [errorCodes].
bool _generatorAppliesToAnyLintRule(
ProducerGenerator generator,
Set<LintCode> errorCodes,
) {
if (errorCodes.isEmpty) {
return false;
}
var selectionEnd =
_assistContext.selectionOffset + _assistContext.selectionLength;
var locator = NodeLocator(_assistContext.selectionOffset, selectionEnd);
var node = locator.searchWithin(_assistContext.unitResult.unit);
if (node == null) {
return false;
}
var fileOffset = node.offset;
for (var error in _assistContext.unitResult.errors) {
var errorSource = error.source;
if (_assistContext.unitResult.path == errorSource.fullName) {
if (fileOffset >= error.offset &&
fileOffset <= error.offset + error.length) {
if (errorCodes.contains(error.errorCode)) {
return true;
}
}
}
}
return false;
}
}
@@ -2,7 +2,7 @@
// 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/src/server/performance.dart';
import 'package:analysis_server_plugin/src/correction/performance.dart';
/// Overall performance of a request for quick fixes operation.
class GetFixesPerformance extends ProducerRequestPerformance {
@@ -12,7 +12,7 @@ import 'package:analysis_server/src/lsp/error_or.dart';
import 'package:analysis_server/src/lsp/handlers/handler_states.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/server/message_scheduler.dart';
import 'package:analysis_server/src/server/performance.dart';
import 'package:analysis_server_plugin/src/correction/performance.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:dtd/dtd.dart';
import 'package:json_rpc_2/json_rpc_2.dart';
@@ -16,9 +16,7 @@ import 'package:analysis_server/src/lsp/lsp_analysis_server.dart'
show LspAnalysisServer;
import 'package:analysis_server/src/plugin/plugin_manager.dart';
import 'package:analysis_server/src/server/http_server.dart';
import 'package:analysis_server/src/server/performance.dart';
import 'package:analysis_server/src/services/completion/completion_performance.dart';
import 'package:analysis_server/src/services/correction/assist_performance.dart';
import 'package:analysis_server/src/services/correction/fix_performance.dart';
import 'package:analysis_server/src/socket_server.dart';
import 'package:analysis_server/src/status/ast_writer.dart';
@@ -26,6 +24,8 @@ import 'package:analysis_server/src/status/element_writer.dart';
import 'package:analysis_server/src/status/pages.dart';
import 'package:analysis_server/src/utilities/profiling.dart';
import 'package:analysis_server/src/utilities/stream_string_stink.dart';
import 'package:analysis_server_plugin/src/correction/assist_performance.dart';
import 'package:analysis_server_plugin/src/correction/performance.dart';
import 'package:analyzer/dart/analysis/context_root.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/src/context/source.dart';
@@ -6,7 +6,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:analysis_server/src/server/performance.dart';
import 'package:analysis_server_plugin/src/correction/performance.dart';
import 'package:collection/collection.dart';
String escape(String? text) => text == null ? '' : htmlEscape.convert(text);
@@ -12,35 +12,6 @@ const int CHAR_DOLLAR = 0x24;
/// "_"
const int CHAR_UNDERSCORE = 0x5F;
/// Build a string from [contents] that includes a caret (`^`) at the
/// given [offset].
///
/// This string is useful for displaying to users in a diagnostic context.
String addCaretAtOffset(String contents, int offset) {
if (offset < 0 || contents.length < offset) {
return '???';
}
var start = offset;
while (start > 0) {
var ch = contents[start - 1];
if (ch == '\r' || ch == '\n') {
break;
}
--start;
}
var end = offset;
while (end < contents.length) {
var ch = contents[end];
if (ch == '\r' || ch == '\n') {
break;
}
++end;
}
var prefix = contents.substring(start, offset);
var suffix = contents.substring(offset, end);
return '$prefix^$suffix';
}
String? capitalize(String? str) {
if (str == null || str.isEmpty) {
return str;
@@ -2,10 +2,10 @@
// 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/plugin/edit/assist/assist_core.dart';
import 'package:analysis_server/src/cider/assists.dart';
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/assist_internal.dart';
import 'package:analysis_server_plugin/src/correction/assist_core.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' show SourceEdit;
import 'package:analyzer_plugin/utilities/assist/assist.dart';
@@ -2,9 +2,9 @@
// 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/plugin/edit/assist/assist_core.dart';
import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server/src/services/correction/assist_internal.dart';
import 'package:analysis_server_plugin/src/correction/assist_core.dart';
import 'package:analysis_server_plugin/src/correction/assist_processor.dart';
import 'package:analysis_server_plugin/src/correction/change_workspace.dart';
import 'package:analysis_server_plugin/src/correction/dart_change_workspace.dart';
import 'package:analyzer/src/test_utilities/platform.dart';
@@ -7,16 +7,14 @@ import 'package:analyzer_plugin/protocol/protocol_common.dart'
import 'package:analyzer_plugin/utilities/assist/assist.dart';
/// A description of a single proposed assist.
///
/// Clients may not extend, implement or mix-in this class.
class Assist {
final class Assist {
/// A description of the assist being proposed.
final AssistKind kind;
/// The change to be made in order to apply the assist.
final SourceChange change;
/// Initialize a newly created assist to have the given [kind] and [change].
/// Initializes a newly created assist to have the given [kind] and [change].
Assist(this.kind, this.change);
@override
@@ -24,10 +22,7 @@ class Assist {
return 'Assist(kind=$kind, change=$change)';
}
/// A function that can be used to sort assists by their relevance.
///
/// The most relevant assists will be sorted before assists with a lower
/// relevance. Assists with the same relevance are sorted alphabetically.
/// Compares two assists by their relevance, then their message.
static int compareAssists(Assist a, Assist b) {
if (a.kind.priority != b.kind.priority) {
// A higher priority indicates a higher relevance
@@ -10,22 +10,22 @@ import 'package:analyzer/instrumentation/service.dart';
///
/// Clients may not extend, implement or mix-in this class.
abstract class DartAssistContext {
/// Return the instrumentation service used to report errors that prevent a
/// fix from being composed.
/// The instrumentation service used to report errors that prevent a fix from
/// being composed.
InstrumentationService get instrumentationService;
/// The resolved library result in which assist operates.
/// The resolved library result in which an assist operates.
ResolvedLibraryResult get libraryResult;
/// The length of the selection.
int get selectionLength;
/// The start of the selection.
/// The starting offset of the selection.
int get selectionOffset;
/// The unit result in which assist operates.
/// The unit result in which an assist operates.
ResolvedUnitResult get unitResult;
/// The workspace in which the fix contributor operates.
/// The workspace in which an assist operates.
ChangeWorkspace get workspace;
}
@@ -23,8 +23,7 @@ class _RegisteredAssistGenerators {
/// A mapping from registered _assist_ producer generators to the [LintCode]s
/// for which they may also act as a _fix_ producer generator.
Map<ProducerGenerator, Set<LintCode>> get lintRuleMap =>
_lintRuleMap ??= {
Map<ProducerGenerator, Set<LintCode>> get lintRuleMap => _lintRuleMap ??= {
for (var generator in producerGenerators)
generator: {
for (var MapEntry(key: lintName, value: generators)
@@ -2,7 +2,6 @@
// 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/src/server/performance.dart';
import 'package:analysis_server_plugin/src/correction/performance.dart';
/// A callback for recording assist request timings.
@@ -0,0 +1,131 @@
// Copyright (c) 2025, 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_plugin/edit/dart/correction_producer.dart';
import 'package:analysis_server_plugin/src/correction/assist_core.dart';
import 'package:analysis_server_plugin/src/correction/assist_dart.dart';
import 'package:analysis_server_plugin/src/correction/assist_generators.dart';
import 'package:analysis_server_plugin/src/correction/assist_performance.dart';
import 'package:analysis_server_plugin/src/correction/fix_generators.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/change_builder/conflicting_edit_exception.dart';
/// The computer for Dart assists.
class AssistProcessor {
final AssistPerformance? _performance;
final DartAssistContext _assistContext;
final Stopwatch _timer = Stopwatch();
final List<Assist> _assists = [];
AssistProcessor(this._assistContext, {AssistPerformance? performance})
: _performance = performance;
Future<List<Assist>> compute() async {
_timer.start();
await _addFromProducers();
_timer.stop();
_performance?.computeTime = _timer.elapsed;
return _assists;
}
Future<void> _addFromProducer(CorrectionProducer producer) async {
var builder = ChangeBuilder(
workspace: _assistContext.workspace,
eol: producer.eol,
);
try {
if (_performance != null) {
var startTime = _timer.elapsedMilliseconds;
await producer.compute(builder);
_performance.producerTimings.add((
className: producer.runtimeType.toString(),
elapsedTime: _timer.elapsedMilliseconds - startTime,
));
} else {
await producer.compute(builder);
}
var assistKind = producer.assistKind;
if (assistKind != null) {
var change = builder.sourceChange;
if (change.edits.isEmpty) {
return;
}
change.id = assistKind.id;
change.message = formatList(
assistKind.message,
producer.assistArguments,
);
_assists.add(Assist(assistKind, change));
}
} on ConflictingEditException catch (exception, stackTrace) {
// Handle the exception by (a) not adding an assist based on the
// producer and (b) logging the exception.
_assistContext.instrumentationService.logException(exception, stackTrace);
}
}
Future<void> _addFromProducers() async {
var context = CorrectionProducerContext.createResolved(
libraryResult: _assistContext.libraryResult,
unitResult: _assistContext.unitResult,
selectionOffset: _assistContext.selectionOffset,
selectionLength: _assistContext.selectionLength,
);
for (var generator in registeredAssistGenerators.producerGenerators) {
if (!_generatorAppliesToAnyLintRule(
generator,
registeredAssistGenerators.lintRuleMap[generator] ?? {},
)) {
var producer = generator(context: context);
await _addFromProducer(producer);
}
}
for (var multiGenerator
in registeredAssistGenerators.multiProducerGenerators) {
var multiProducer = multiGenerator(context: context);
for (var producer in await multiProducer.producers) {
await _addFromProducer(producer);
}
}
}
/// Returns whether [generator] applies to any enabled lint rule, among
/// [errorCodes].
bool _generatorAppliesToAnyLintRule(
ProducerGenerator generator,
Set<LintCode> errorCodes,
) {
if (errorCodes.isEmpty) {
return false;
}
var selectionEnd =
_assistContext.selectionOffset + _assistContext.selectionLength;
var locator = NodeLocator(_assistContext.selectionOffset, selectionEnd);
var node = locator.searchWithin(_assistContext.unitResult.unit);
if (node == null) {
return false;
}
var fileOffset = node.offset;
for (var error in _assistContext.unitResult.errors) {
var errorSource = error.source;
if (_assistContext.unitResult.path == errorSource.fullName) {
if (fileOffset >= error.offset &&
fileOffset <= error.offset + error.length) {
if (errorCodes.contains(error.errorCode)) {
return true;
}
}
}
}
return false;
}
}
@@ -2,6 +2,9 @@
// 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_plugin/src/utilities/string_extensions.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
/// Timing information for a (correction) producer's call to `compute()`.
typedef ProducerTiming = ({
/// The producer class name.
@@ -10,3 +13,42 @@ typedef ProducerTiming = ({
/// The time elapsed during `compute()`.
int elapsedTime,
});
abstract class ProducerRequestPerformance extends RequestPerformance {
final String path;
final String snippet;
final List<ProducerTiming> producerTimings;
ProducerRequestPerformance({
// TODO(srawlins): This should probably be used in the super call?
// ignore: avoid_unused_constructor_parameters
required String operation,
required this.path,
required super.performance,
super.requestLatency,
super.startTime,
required String content,
required int offset,
required this.producerTimings,
}) : snippet = content.withCaretAt(offset),
super(operation: 'GetAssists');
int get elapsedInMilliseconds => performance.elapsed.inMilliseconds;
}
class RequestPerformance {
static var _nextId = 1;
final int id;
final OperationPerformance performance;
final int? requestLatency;
final String operation;
final DateTime? startTime;
RequestPerformance({
required this.operation,
required this.performance,
this.requestLatency,
this.startTime,
}) : id = _nextId++;
}
@@ -0,0 +1,34 @@
// Copyright (c) 2025, 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.
extension StringExtension on String {
/// Builds a string from `this` that includes a caret (`^`) at the given
/// [offset].
///
/// This string is useful for displaying to users in a diagnostic context.
String withCaretAt(int offset) {
if (offset < 0 || length < offset) {
return '???';
}
var start = offset;
while (start > 0) {
var ch = this[start - 1];
if (ch == '\r' || ch == '\n') {
break;
}
--start;
}
var end = offset;
while (end < length) {
var ch = this[end];
if (ch == '\r' || ch == '\n') {
break;
}
++end;
}
var prefix = substring(start, offset);
var suffix = substring(offset, end);
return '$prefix^$suffix';
}
}