blob: 1ff780cba66af0ec62d27b434db92fd77481cd70 [file] [log] [blame]
// Copyright (c) 2018, 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/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/src/services/correction/change_workspace.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart'
hide AnalysisError;
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/utilities/assist/assist.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
import 'package:test/test.dart';
import '../../../../abstract_single_unit.dart';
/// A base class defining support for writing assist processor tests.
abstract class AssistProcessorTest extends AbstractSingleUnitTest {
int _offset;
int _length;
SourceChange _change;
String _resultCode;
/// Return the kind of assist expected by this class.
AssistKind get kind;
/// The workspace in which fixes contributor operates.
ChangeWorkspace get workspace {
return DartChangeWorkspace([session]);
}
void assertExitPosition({String before, String after}) {
Position exitPosition = _change.selection;
expect(exitPosition, isNotNull);
expect(exitPosition.file, testFile);
if (before != null) {
expect(exitPosition.offset, _resultCode.indexOf(before));
} else if (after != null) {
expect(exitPosition.offset, _resultCode.indexOf(after) + after.length);
} else {
fail("One of 'before' or 'after' expected.");
}
}
/// Asserts that there is an assist of the given [kind] at [_offset] which
/// produces the [expected] code when applied to [testCode]. The map of
/// [additionallyChangedFiles] can be used to test assists that can modify
/// more than the test file. The keys are expected to be the paths to the
/// files that are modified (other than the test file) and the values are
/// pairs of source code: the states of the code before and after the edits
/// have been applied.
Future<void> assertHasAssist(String expected,
{Map<String, List<String>> additionallyChangedFiles}) async {
_setSelection();
Assist assist = await _assertHasAssist();
_change = assist.change;
expect(_change.id, kind.id);
// apply to "file"
List<SourceFileEdit> fileEdits = _change.edits;
if (additionallyChangedFiles == null) {
expect(fileEdits, hasLength(1));
_resultCode = SourceEdit.applySequence(testCode, _change.edits[0].edits);
expect(_resultCode, expected);
} else {
expect(fileEdits, hasLength(additionallyChangedFiles.length + 1));
_resultCode = SourceEdit.applySequence(
testCode, _change.getFileEdit(testFile).edits);
expect(_resultCode, expected);
for (String filePath in additionallyChangedFiles.keys) {
List<String> pair = additionallyChangedFiles[filePath];
String resultCode = SourceEdit.applySequence(
pair[0], _change.getFileEdit(filePath).edits);
expect(resultCode, pair[1]);
}
}
}
/// Asserts that there is an [Assist] of the given [kind] at the offset of the
/// given [snippet] which produces the [expected] code when applied to [testCode].
Future<void> assertHasAssistAt(String snippet, String expected,
{int length = 0}) async {
_offset = findOffset(snippet);
_length = length;
Assist assist = await _assertHasAssist();
_change = assist.change;
expect(_change.id, kind.id);
// apply to "file"
List<SourceFileEdit> fileEdits = _change.edits;
expect(fileEdits, hasLength(1));
_resultCode = SourceEdit.applySequence(testCode, _change.edits[0].edits);
expect(_resultCode, expected);
}
void assertLinkedGroup(int groupIndex, List<String> expectedStrings,
[List<LinkedEditSuggestion> expectedSuggestions]) {
LinkedEditGroup group = _change.linkedEditGroups[groupIndex];
List<Position> expectedPositions = _findResultPositions(expectedStrings);
expect(group.positions, unorderedEquals(expectedPositions));
if (expectedSuggestions != null) {
expect(group.suggestions, unorderedEquals(expectedSuggestions));
}
}
/// Asserts that there is no [Assist] of the given [kind] at [_offset].
Future<void> assertNoAssist() async {
_setSelection();
List<Assist> assists = await _computeAssists();
for (Assist assist in assists) {
if (assist.kind == kind) {
fail('Unexpected assist $kind in\n${assists.join('\n')}');
}
}
}
/// Asserts that there is no [Assist] of the given [kind] at the offset of the
/// given [snippet].
Future<void> assertNoAssistAt(String snippet, {int length = 0}) async {
_offset = findOffset(snippet);
_length = length;
List<Assist> assists = await _computeAssists();
for (Assist assist in assists) {
if (assist.kind == kind) {
fail('Unexpected assist $kind in\n${assists.join('\n')}');
}
}
}
List<LinkedEditSuggestion> expectedSuggestions(
LinkedEditSuggestionKind kind, List<String> values) {
return values.map((value) {
return new LinkedEditSuggestion(value, kind);
}).toList();
}
/// Computes assists and verifies that there is an assist of the given kind.
Future<Assist> _assertHasAssist() async {
List<Assist> assists = await _computeAssists();
for (Assist assist in assists) {
if (assist.kind == kind) {
return assist;
}
}
fail('Expected to find assist $kind in\n${assists.join('\n')}');
}
Future<List<Assist>> _computeAssists() async {
var context = new DartAssistContextImpl(
workspace,
testAnalysisResult,
_offset,
_length,
);
var processor = new AssistProcessor(context);
return await processor.compute();
}
List<Position> _findResultPositions(List<String> searchStrings) {
List<Position> positions = <Position>[];
for (String search in searchStrings) {
int offset = _resultCode.indexOf(search);
positions.add(new Position(testFile, offset));
}
return positions;
}
void _setSelection() {
_offset = testCode.indexOf('/*caret*/');
if (_offset >= 0) {
_offset += '/*caret*/'.length;
_length = 0;
} else {
_offset = testCode.indexOf('// start\n');
if (_offset >= 0) {
_offset += '// start\n'.length;
_length = testCode.indexOf('// end') - _offset;
} else {
_offset = 0;
_length = 0;
}
}
}
}