3021cc4913
Work towards https://github.com/dart-lang/sdk/issues/55660 Change-Id: I3df2cd374f6b3ef9e027f0e07c748be6ea21ebb2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427586 Reviewed-by: Paul Berry <paulberry@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com>
305 lines
10 KiB
Dart
305 lines
10 KiB
Dart
// Copyright (c) 2015, 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.dart';
|
|
import 'package:analysis_server/protocol/protocol_constants.dart';
|
|
import 'package:analysis_server/protocol/protocol_generated.dart';
|
|
import 'package:analyzer/file_system/file_system.dart';
|
|
import 'package:analyzer/utilities/package_config_file_builder.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import '../analysis_server_base.dart';
|
|
import '../mocks.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(UpdateContentTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class UpdateContentTest extends PubPackageAnalysisServerTest {
|
|
Map<File, List<String>> filesErrors = {};
|
|
int serverErrorCount = 0;
|
|
int navigationCount = 0;
|
|
|
|
@override
|
|
void processNotification(Notification notification) {
|
|
if (notification.event == ANALYSIS_NOTIFICATION_ERRORS) {
|
|
var decoded = AnalysisErrorsParams.fromNotification(
|
|
notification,
|
|
clientUriConverter: server.uriConverter,
|
|
);
|
|
String format(AnalysisError e) => '${e.location.startLine}: ${e.message}';
|
|
filesErrors[getFile(decoded.file)] = decoded.errors.map(format).toList();
|
|
}
|
|
if (notification.event == ANALYSIS_NOTIFICATION_NAVIGATION) {
|
|
navigationCount++;
|
|
}
|
|
if (notification.event == SERVER_NOTIFICATION_ERROR) {
|
|
serverErrorCount++;
|
|
}
|
|
}
|
|
|
|
void test_illegal_ChangeContentOverlay() async {
|
|
// It should be illegal to send a ChangeContentOverlay for a file that
|
|
// doesn't have an overlay yet.
|
|
addTestFile('');
|
|
await setRoots(included: [workspaceRootPath], excluded: []);
|
|
var response = await handleRequest(
|
|
AnalysisUpdateContentParams({
|
|
testFile.path: ChangeContentOverlay([SourceEdit(0, 0, '')]),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
assertResponseFailure(
|
|
response,
|
|
requestId: '0',
|
|
errorCode: RequestErrorCode.INVALID_OVERLAY_CHANGE,
|
|
);
|
|
}
|
|
|
|
Future<void> test_invalidFilePathFormat_notAbsolute() async {
|
|
var response = await handleRequest(
|
|
AnalysisUpdateContentParams({
|
|
'test.dart': AddContentOverlay(''),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
expect(
|
|
response,
|
|
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
|
|
);
|
|
}
|
|
|
|
Future<void> test_invalidFilePathFormat_notNormalized() async {
|
|
var response = await handleRequest(
|
|
AnalysisUpdateContentParams({
|
|
convertPath('/foo/../bar/test.dart'): AddContentOverlay(''),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
expect(
|
|
response,
|
|
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
|
|
);
|
|
}
|
|
|
|
Future<void> test_multiple_contexts() async {
|
|
writePackageConfig(
|
|
workspaceRootPath,
|
|
config:
|
|
PackageConfigFileBuilder()
|
|
..add(name: 'aaa', rootPath: '$workspaceRootPath/aaa'),
|
|
);
|
|
|
|
var aaa = newFile('$workspaceRootPath/aaa/lib/aaa.dart', r'''
|
|
void f(int _) {}
|
|
''');
|
|
|
|
var foo = newFile('$workspaceRootPath/foo/lib/foo.dart', r'''
|
|
import 'package:aaa/aaa.dart';
|
|
void g() {
|
|
f();
|
|
}
|
|
''');
|
|
|
|
var bar = newFile('$workspaceRootPath/bar/lib/bar.dart', r'''
|
|
import 'package:aaa/aaa.dart';
|
|
void g() {
|
|
f();
|
|
}
|
|
''');
|
|
|
|
await setRoots(included: [foo.parent.path, bar.parent.path], excluded: []);
|
|
|
|
{
|
|
await waitForTasksFinished();
|
|
// Files foo.dart and bar.dart should both have errors, since they both
|
|
// call f() with the wrong number of arguments.
|
|
expect(filesErrors[foo], hasLength(1));
|
|
expect(filesErrors[bar], hasLength(1));
|
|
// Overlay the content of baz.dart to eliminate the errors.
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
aaa.path: AddContentOverlay('void f() {}'),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
}
|
|
|
|
{
|
|
await waitForTasksFinished();
|
|
// The overlay should have been propagated to both contexts, causing both
|
|
// foo.dart and bar.dart to be reanalyzed and found to be free of errors.
|
|
expect(filesErrors[foo], isEmpty);
|
|
expect(filesErrors[bar], isEmpty);
|
|
}
|
|
}
|
|
|
|
@failingTest
|
|
Future<void> test_overlay_addPreviouslyImported() async {
|
|
// The list of errors doesn't include errors for '/project/target.dart'.
|
|
var project = newFolder('/project');
|
|
await setRoots(included: [project.path], excluded: []);
|
|
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
'/project/main.dart': AddContentOverlay('import "target.dart";'),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
expect(filesErrors, {
|
|
'/project/main.dart': ["1: Target of URI doesn't exist: 'target.dart'."],
|
|
'/project/target.dart': [],
|
|
});
|
|
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
'/project/target.dart': AddContentOverlay('import "none.dart";'),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
expect(filesErrors, {
|
|
'/project/main.dart': ['1: Unused import.'],
|
|
'/project/target.dart': ["1: Target of URI doesn't exist: 'none.dart'."],
|
|
'/project/none.dart': [],
|
|
});
|
|
}
|
|
|
|
Future<void> test_overlayOnly() async {
|
|
var a = newFile('$testPackageLibPath/a.dart', '');
|
|
var b = getFile('$testPackageLibPath/b.dart');
|
|
|
|
await setRoots(included: [workspaceRootPath], excluded: []);
|
|
await waitForTasksFinished();
|
|
expect(filesErrors[a], isEmpty);
|
|
expect(filesErrors[b], isNull);
|
|
|
|
// Add `b.dart` overlay, analyzed.
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
b.path: AddContentOverlay(''),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
expect(filesErrors[a], isEmpty);
|
|
expect(filesErrors[b], isEmpty);
|
|
|
|
// Add `b.dart` overlay, analyzed.
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
b.path: RemoveContentOverlay(),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
expect(filesErrors[a], isEmpty);
|
|
// TODO(scheglov): We should get "flush" notification.
|
|
// expect(filesErrors[b], isNull);
|
|
}
|
|
|
|
@failingTest
|
|
Future<void> test_sendNoticesAfterNopChange() async {
|
|
// The errors are empty on the last line.
|
|
addTestFile('');
|
|
await setRoots(included: [workspaceRootPath], excluded: []);
|
|
await waitForTasksFinished();
|
|
// add an overlay
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
testFile.path: AddContentOverlay('void f() {} void f() {}'),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
// clear errors and make a no-op change
|
|
filesErrors.clear();
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
testFile.path: ChangeContentOverlay([SourceEdit(5, 1, 'f')]),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
// errors should have been resent
|
|
expect(filesErrors, isNotEmpty);
|
|
}
|
|
|
|
@failingTest
|
|
Future<void> test_sendNoticesAfterNopChange_flushedUnit() async {
|
|
// The list of errors is empty on the last line.
|
|
addTestFile('');
|
|
await setRoots(included: [workspaceRootPath], excluded: []);
|
|
await waitForTasksFinished();
|
|
// add an overlay
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
testFile.path: AddContentOverlay('void f() {} void f() {}'),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
// clear errors and make a no-op change
|
|
filesErrors.clear();
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
testFile.path: ChangeContentOverlay([SourceEdit(5, 1, 'f')]),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
// errors should have been resent
|
|
expect(filesErrors, isNotEmpty);
|
|
}
|
|
|
|
Future<void> test_sentToPlugins() async {
|
|
var filePath = convertPath('$testPackageLibPath/a.dart');
|
|
var fileContent = 'import "none.dart";';
|
|
//
|
|
// Add
|
|
//
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams(<String, Object>{
|
|
filePath: AddContentOverlay(fileContent),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
var params = pluginManager.analysisUpdateContentParams!;
|
|
var files = params.files;
|
|
expect(files, hasLength(1));
|
|
var overlay = files[filePath];
|
|
expect(overlay, const TypeMatcher<AddContentOverlay>());
|
|
var addOverlay = overlay as AddContentOverlay;
|
|
expect(addOverlay.content, fileContent);
|
|
//
|
|
// Change
|
|
//
|
|
pluginManager.analysisUpdateContentParams = null;
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams(<String, Object>{
|
|
filePath: ChangeContentOverlay(<SourceEdit>[
|
|
SourceEdit(8, 1, "'"),
|
|
SourceEdit(18, 1, "'"),
|
|
]),
|
|
}).toRequest('1', clientUriConverter: server.uriConverter),
|
|
);
|
|
params = pluginManager.analysisUpdateContentParams!;
|
|
expect(params, isNotNull);
|
|
files = params.files;
|
|
expect(files, hasLength(1));
|
|
overlay = files[filePath];
|
|
expect(overlay, const TypeMatcher<ChangeContentOverlay>());
|
|
var changeOverlay = overlay as ChangeContentOverlay;
|
|
expect(changeOverlay.edits, hasLength(2));
|
|
//
|
|
// Remove
|
|
//
|
|
pluginManager.analysisUpdateContentParams = null;
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams(<String, Object>{
|
|
filePath: RemoveContentOverlay(),
|
|
}).toRequest('2', clientUriConverter: server.uriConverter),
|
|
);
|
|
params = pluginManager.analysisUpdateContentParams!;
|
|
expect(params, isNotNull);
|
|
files = params.files;
|
|
expect(files, hasLength(1));
|
|
overlay = files[filePath];
|
|
expect(overlay, const TypeMatcher<RemoveContentOverlay>());
|
|
}
|
|
}
|