| // 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_plugin/protocol/protocol_common.dart'; | |
| import 'package:analyzer_testing/package_config_file_builder.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 == analysisNotificationErrors) { | |
| 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 == analysisNotificationNavigation) { | |
| navigationCount++; | |
| } | |
| if (notification.event == serverNotificationError) { | |
| 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 { | |
| writePackageConfig2( | |
| workspaceRootPath, | |
| config: PackageConfigFileBuilder() | |
| ..add(name: 'aaa', rootFolder: getFolder('$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); | |
| } | |
| 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); | |
| } | |
| 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>()); | |
| } | |
| } |