9db372e6f4
In https://dart-review.googlesource.com/c/sdk/+/191862 we added two new required fields to `Location`. Unfortunately this was a breaking change because plugins using an older version of the `analyzer_plugin` produce location objects without those fields, leading to deserialization failures. This CL makes those fields optional in order to fix the deserialization issue. Unfortunately, the `analyzer_plugin` package was published after the required fields were added. Making them optional is a breaking change because the constructor parameters go from being positional to being named parameters. We also neglected to update the version number of the protocol as part of the previous CL. Technically this is also a breaking change for clients of the analysis server, but given that they had no way to test to see whether these fields existed they would need to have been written as if the fields were optional in order to reference them at all, so I think that from a practical standpoint it isn't a breaking change. That does, however, raise the question of whether we should increment the version numbers as part of this CL. Change-Id: I35fc1f8e950669a3d8dd33cee6b81890261b5c47 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/206942 Reviewed-by: Danny Tuppeny <danny@tuppeny.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
223 lines
6.8 KiB
Dart
223 lines
6.8 KiB
Dart
// Copyright (c) 2014, 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/protocol/protocol_generated.dart';
|
|
import 'package:analysis_server/src/edit/edit_domain.dart';
|
|
import 'package:analysis_server/src/plugin/plugin_manager.dart';
|
|
import 'package:analyzer/instrumentation/service.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import '../analysis_abstract.dart';
|
|
import '../mocks.dart';
|
|
import '../src/plugin/plugin_manager_test.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(FixesTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class FixesTest extends AbstractAnalysisTest {
|
|
@override
|
|
void setUp() {
|
|
super.setUp();
|
|
handler = EditDomainHandler(server);
|
|
}
|
|
|
|
Future<void> test_fileOutsideRoot() async {
|
|
final outsideFile = '/foo/test.dart';
|
|
newFile(outsideFile, content: 'bad code to create error');
|
|
|
|
// Set up the original project, as the code fix code won't run at all
|
|
// if there are no contexts.
|
|
createProject();
|
|
await waitForTasksFinished();
|
|
|
|
var request =
|
|
EditGetFixesParams(convertPath(outsideFile), 0).toRequest('0');
|
|
var response = await waitResponse(request);
|
|
expect(
|
|
response,
|
|
isResponseFailure('0', RequestErrorCode.GET_FIXES_INVALID_FILE),
|
|
);
|
|
}
|
|
|
|
Future<void> test_fixUndefinedClass() async {
|
|
createProject();
|
|
addTestFile('''
|
|
main() {
|
|
Completer<String> x = null;
|
|
print(x);
|
|
}
|
|
''');
|
|
await waitForTasksFinished();
|
|
doAllDeclarationsTrackerWork();
|
|
var errorFixes = await _getFixesAt('Completer<String>');
|
|
expect(errorFixes, hasLength(1));
|
|
var fixes = errorFixes[0].fixes;
|
|
expect(fixes, hasLength(3));
|
|
expect(fixes[0].message, matches('Import library'));
|
|
expect(fixes[1].message, matches('Create class'));
|
|
expect(fixes[2].message, matches('Create mixin'));
|
|
}
|
|
|
|
Future<void> test_fromPlugins() async {
|
|
PluginInfo info = DiscoveredPluginInfo('a', 'b', 'c',
|
|
TestNotificationManager(), InstrumentationService.NULL_SERVICE);
|
|
var fixes = plugin.AnalysisErrorFixes(AnalysisError(
|
|
AnalysisErrorSeverity.ERROR,
|
|
AnalysisErrorType.HINT,
|
|
Location('', 0, 0, 0, 0, endLine: 0, endColumn: 0),
|
|
'message',
|
|
'code'));
|
|
var result = plugin.EditGetFixesResult(<plugin.AnalysisErrorFixes>[fixes]);
|
|
pluginManager.broadcastResults = <PluginInfo, Future<plugin.Response>>{
|
|
info: Future.value(result.toResponse('-', 1))
|
|
};
|
|
|
|
createProject();
|
|
addTestFile('main() {}');
|
|
await waitForTasksFinished();
|
|
var errorFixes = await _getFixesAt('in(');
|
|
expect(errorFixes, hasLength(1));
|
|
}
|
|
|
|
Future<void> test_hasFixes() async {
|
|
createProject();
|
|
addTestFile('''
|
|
foo() {
|
|
print(1)
|
|
}
|
|
bar() {
|
|
print(10) print(20)
|
|
}
|
|
''');
|
|
await waitForTasksFinished();
|
|
// print(1)
|
|
{
|
|
var errorFixes = await _getFixesAt('print(1)');
|
|
expect(errorFixes, hasLength(1));
|
|
_isSyntacticErrorWithSingleFix(errorFixes[0]);
|
|
}
|
|
// print(10)
|
|
{
|
|
var errorFixes = await _getFixesAt('print(10)');
|
|
expect(errorFixes, hasLength(2));
|
|
_isSyntacticErrorWithSingleFix(errorFixes[0]);
|
|
_isSyntacticErrorWithSingleFix(errorFixes[1]);
|
|
}
|
|
}
|
|
|
|
Future<void> test_invalidFilePathFormat_notAbsolute() async {
|
|
var request = EditGetFixesParams('test.dart', 0).toRequest('0');
|
|
var response = await waitResponse(request);
|
|
expect(
|
|
response,
|
|
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
|
|
);
|
|
}
|
|
|
|
Future<void> test_invalidFilePathFormat_notNormalized() async {
|
|
var request = EditGetFixesParams(convertPath('/foo/../bar/test.dart'), 0)
|
|
.toRequest('0');
|
|
var response = await waitResponse(request);
|
|
expect(
|
|
response,
|
|
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
|
|
);
|
|
}
|
|
|
|
Future<void> test_overlayOnlyFile() async {
|
|
createProject();
|
|
testCode = '''
|
|
main() {
|
|
print(1)
|
|
}
|
|
''';
|
|
_addOverlay(testFile, testCode);
|
|
// ask for fixes
|
|
await waitForTasksFinished();
|
|
var errorFixes = await _getFixesAt('print(1)');
|
|
expect(errorFixes, hasLength(1));
|
|
_isSyntacticErrorWithSingleFix(errorFixes[0]);
|
|
}
|
|
|
|
Future<void> test_suggestImportFromDifferentAnalysisRoot() async {
|
|
newFolder('/aaa');
|
|
newDotPackagesFile('/aaa', content: '''
|
|
aaa:${toUri('/aaa/lib')}
|
|
bbb:${toUri('/bbb/lib')}
|
|
''');
|
|
newPubspecYamlFile('/aaa', r'''
|
|
dependencies:
|
|
bbb: any
|
|
''');
|
|
|
|
newFolder('/bbb');
|
|
newDotPackagesFile('/bbb', content: '''
|
|
bbb:${toUri('/bbb/lib')}
|
|
''');
|
|
newFile('/bbb/lib/target.dart', content: 'class Foo() {}');
|
|
newFile('/bbb/lib/target.generated.dart', content: 'class Foo() {}');
|
|
newFile('/bbb/lib/target.template.dart', content: 'class Foo() {}');
|
|
|
|
handleSuccessfulRequest(
|
|
AnalysisSetAnalysisRootsParams(
|
|
[convertPath('/aaa'), convertPath('/bbb')], []).toRequest('0'),
|
|
handler: analysisHandler);
|
|
|
|
// Configure the test file.
|
|
testFile = convertPath('/aaa/main.dart');
|
|
testCode = 'main() { new Foo(); }';
|
|
_addOverlay(testFile, testCode);
|
|
|
|
await waitForTasksFinished();
|
|
doAllDeclarationsTrackerWork();
|
|
|
|
var fixes = (await _getFixesAt('Foo()'))
|
|
.single
|
|
.fixes
|
|
.map((f) => f.message)
|
|
.toList();
|
|
expect(fixes, contains("Import library 'package:bbb/target.dart'"));
|
|
expect(
|
|
fixes, contains("Import library 'package:bbb/target.generated.dart'"));
|
|
|
|
// Context: http://dartbug.com/39401
|
|
expect(fixes.contains("Import library 'package:bbb/target.template.dart'"),
|
|
isFalse);
|
|
}
|
|
|
|
void _addOverlay(String name, String contents) {
|
|
var request =
|
|
AnalysisUpdateContentParams({name: AddContentOverlay(contents)})
|
|
.toRequest('0');
|
|
handleSuccessfulRequest(request, handler: analysisHandler);
|
|
}
|
|
|
|
Future<List<AnalysisErrorFixes>> _getFixes(int offset) async {
|
|
var request = EditGetFixesParams(testFile, offset).toRequest('0');
|
|
var response = await waitResponse(request);
|
|
var result = EditGetFixesResult.fromResponse(response);
|
|
return result.fixes;
|
|
}
|
|
|
|
Future<List<AnalysisErrorFixes>> _getFixesAt(String search) async {
|
|
var offset = findOffset(search);
|
|
return await _getFixes(offset);
|
|
}
|
|
|
|
void _isSyntacticErrorWithSingleFix(AnalysisErrorFixes fixes) {
|
|
var error = fixes.error;
|
|
expect(error.severity, AnalysisErrorSeverity.ERROR);
|
|
expect(error.type, AnalysisErrorType.SYNTACTIC_ERROR);
|
|
expect(fixes.fixes, hasLength(1));
|
|
}
|
|
}
|