blob: 88a392f8787d0488cdddbc06ae31baabffdbbe6d [file] [log] [blame]
// Copyright (c) 2020, 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 'dart:io' as io;
import 'package:analysis_server/src/domains/completion/available_suggestions.dart';
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/services/completion/completion_core.dart';
import 'package:analysis_server/src/services/completion/completion_performance.dart';
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
import 'package:analysis_server/src/services/completion/dart/utilities.dart';
import 'package:analysis_server/src/status/pages.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/services/available_declarations.dart';
import 'metrics_util.dart';
import 'visitors.dart';
// TODO(jwren) have the analysis root and verbose option be configurable via a
// command line UX
main() async {
await CompletionMetricsComputer('', true).computeCompletionMetrics();
}
/// This is the main metrics computer class for code completions. After the
/// object is constructed, [computeCompletionMetrics] is executed to do analysis
/// and print a summary of the metrics gathered from the completion tests.
class CompletionMetricsComputer {
/// The analysis root path that this CompletionMetrics class will be computed.
final String _rootPath;
/// When enabled, expected, but missing completion tokens will be printed to
/// stdout.
final bool _verbose;
CompletionMetricsComputer(this._rootPath, this._verbose);
Future computeCompletionMetrics() async {
int includedCount = 0;
int notIncludedCount = 0;
var completionKindCounter = Counter('completion kind counter');
var completionElementKindCounter =
Counter('completion element kind counter');
print('Analyzing root: \"$_rootPath\"');
if (!io.Directory(_rootPath).existsSync()) {
print('\tError: No such directory exists on this machine.\n');
return;
}
final collection = AnalysisContextCollection(
includedPaths: [_rootPath],
resourceProvider: PhysicalResourceProvider.INSTANCE,
);
for (var context in collection.contexts) {
var declarationsTracker = DeclarationsTracker(
MemoryByteStore(), PhysicalResourceProvider.INSTANCE);
declarationsTracker.addContext(context);
while (declarationsTracker.hasWork) {
declarationsTracker.doWork();
}
for (var filePath in context.contextRoot.analyzedFiles()) {
if (AnalysisEngine.isDartFileName(filePath)) {
try {
final resolvedUnitResult =
await context.currentSession.getResolvedUnit(filePath);
final visitor = ExpectedCompletionsVisitor();
resolvedUnitResult.unit.accept(visitor);
for (var expectedCompletion in visitor.expectedCompletions) {
var suggestions = await _computeCompletionSuggestions(
resolvedUnitResult,
expectedCompletion.offset,
declarationsTracker);
var fraction =
_placementInSuggestionList(suggestions, expectedCompletion);
if (fraction.denominator != 0) {
includedCount++;
} else {
notIncludedCount++;
if (_verbose) {
// The format "/file/path/foo.dart:3:4" makes for easier input
// with the Files dialog in IntelliJ
print(
'$filePath:${expectedCompletion.lineNumber}:${expectedCompletion.columnNumber}');
print(
'\tdid not include the expected completion: \"${expectedCompletion.completion}\", completion kind: ${expectedCompletion.kind.toString()}, element kind: ${expectedCompletion.elementKind.toString()}');
print('');
completionKindCounter
.count(expectedCompletion.kind.toString());
completionElementKindCounter
.count(expectedCompletion.elementKind.toString());
}
}
}
} catch (e) {
print('Exception caught analyzing: $filePath');
print(e.toString());
}
}
}
}
final totalCompletionCount = includedCount + notIncludedCount;
final percentIncluded = includedCount / totalCompletionCount;
final percentNotIncluded = 1 - percentIncluded;
completionKindCounter.printCounterValues();
completionElementKindCounter.printCounterValues();
print('Summary for $_rootPath:');
print('Total number of completion tests = $totalCompletionCount');
print(
'Number of successful completions = $includedCount (${printPercentage(percentIncluded)})');
print(
'Number of unsuccessful completions = $notIncludedCount (${printPercentage(percentNotIncluded)})');
includedCount = 0;
notIncludedCount = 0;
completionKindCounter.clear();
completionElementKindCounter.clear();
}
Future<List<CompletionSuggestion>> _computeCompletionSuggestions(
ResolvedUnitResult resolvedUnitResult, int offset,
[DeclarationsTracker declarationsTracker]) async {
var completionRequestImpl = CompletionRequestImpl(
resolvedUnitResult,
offset,
CompletionPerformance(),
);
// This gets all of the suggestions with relevances.
var suggestions =
await DartCompletionManager().computeSuggestions(completionRequestImpl);
// If a non-null declarationsTracker was passed, use it to call
// computeIncludedSetList, this current implementation just adds the set of
// included element names with relevance 0, future implementations should
// compute out the relevance that clients will set to each value.
if (declarationsTracker != null) {
var includedSuggestionSets = <IncludedSuggestionSet>[];
var includedElementNames = <String>{};
computeIncludedSetList(declarationsTracker, resolvedUnitResult,
includedSuggestionSets, includedElementNames);
for (var eltName in includedElementNames) {
suggestions.add(CompletionSuggestion(
CompletionSuggestionKind.INVOCATION,
0,
eltName,
0,
eltName.length,
false,
false));
}
}
suggestions.sort(completionComparator);
return suggestions;
}
Place _placementInSuggestionList(List<CompletionSuggestion> suggestions,
ExpectedCompletion expectedCompletion) {
var placeCounter = 1;
for (var completionSuggestion in suggestions) {
if (expectedCompletion.matches(completionSuggestion)) {
return Place(placeCounter, suggestions.length);
}
placeCounter++;
}
return Place.none();
}
}