| // 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 { |
| 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 { |
| 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(); |
| } |
| |
| @override |
| Future<void> resolveTestUnit(String code) async { |
| var offset = code.indexOf('/*caret*/'); |
| if (offset >= 0) { |
| var endOffset = offset + '/*caret*/'.length; |
| code = code.substring(0, offset) + code.substring(endOffset); |
| _offset = offset; |
| _length = 0; |
| } else { |
| var startOffset = code.indexOf('// start\n'); |
| var endOffset = code.indexOf('// end\n'); |
| if (startOffset >= 0 && endOffset >= 0) { |
| var startLength = '// start\n'.length; |
| code = code.substring(0, startOffset) + |
| code.substring(startOffset + startLength, endOffset) + |
| code.substring(endOffset + '// end\n'.length); |
| _offset = startOffset; |
| _length = endOffset - startLength - _offset; |
| } else { |
| _offset = 0; |
| _length = 0; |
| } |
| } |
| return super.resolveTestUnit(code); |
| } |
| |
| /// 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; |
| } |
| } |