| // Copyright (c) 2019, 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/lsp_protocol/protocol_generated.dart'; |
| import 'package:analysis_server/lsp_protocol/protocol_special.dart'; |
| import 'package:analysis_server/src/protocol/protocol_internal.dart'; |
| import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Position; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import 'server_abstract.dart'; |
| |
| void main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(DocumentChangesTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class DocumentChangesTest extends AbstractLspAnalysisServerTest { |
| String get content => ''' |
| class Foo { |
| String get bar => 'baz'; |
| } |
| '''; |
| |
| String get contentAfterUpdate => ''' |
| class Bar { |
| String get bar => 'updated'; |
| } |
| '''; |
| |
| Future<void> test_documentChange_notifiesPlugins() async { |
| await _initializeAndOpen(); |
| await changeFile(2, mainFileUri, [ |
| Either2<TextDocumentContentChangeEvent1, |
| TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1( |
| range: Range( |
| start: Position(line: 0, character: 6), |
| end: Position(line: 0, character: 9)), |
| text: 'Bar', |
| )), |
| Either2<TextDocumentContentChangeEvent1, |
| TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1( |
| range: Range( |
| start: Position(line: 1, character: 21), |
| end: Position(line: 1, character: 24)), |
| text: 'updated', |
| )), |
| ]); |
| |
| final notifiedChanges = pluginManager.analysisUpdateContentParams! |
| .files[mainFilePath] as ChangeContentOverlay; |
| |
| expect( |
| applySequenceOfEdits(content, notifiedChanges.edits), |
| contentAfterUpdate, |
| ); |
| } |
| |
| Future<void> test_documentChange_updatesOverlay() async { |
| await _initializeAndOpen(); |
| await changeFile(2, mainFileUri, [ |
| Either2<TextDocumentContentChangeEvent1, |
| TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1( |
| range: Range( |
| start: Position(line: 0, character: 6), |
| end: Position(line: 0, character: 9)), |
| text: 'Bar', |
| )), |
| Either2<TextDocumentContentChangeEvent1, |
| TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1( |
| range: Range( |
| start: Position(line: 1, character: 21), |
| end: Position(line: 1, character: 24)), |
| text: 'updated', |
| )), |
| ]); |
| |
| expect(server.resourceProvider.hasOverlay(mainFilePath), isTrue); |
| expect(server.resourceProvider.getFile(mainFilePath).readAsStringSync(), |
| equals(contentAfterUpdate)); |
| } |
| |
| Future<void> test_documentClose_deletesOverlay() async { |
| await _initializeAndOpen(); |
| await closeFile(mainFileUri); |
| |
| expect(server.resourceProvider.hasOverlay(mainFilePath), isFalse); |
| } |
| |
| Future<void> test_documentClose_notifiesPlugins() async { |
| await _initializeAndOpen(); |
| await closeFile(mainFileUri); |
| |
| expect(pluginManager.analysisUpdateContentParams!.files, |
| equals({mainFilePath: RemoveContentOverlay()})); |
| } |
| |
| Future<void> |
| test_documentOpen_addsOverlayOnlyToDriver_onlyIfInsideRoots() async { |
| // Ensures that opening a file doesn't add it to the driver if it's outside |
| // of the drivers root. |
| final fileInsideRootPath = mainFilePath; |
| final fileOutsideRootPath = convertPath('/home/unrelated/main.dart'); |
| await initialize(); |
| await openFile(Uri.file(fileInsideRootPath), content); |
| await openFile(Uri.file(fileOutsideRootPath), content); |
| |
| // Expect both files return the same driver |
| final driverForInside = server.getAnalysisDriver(fileInsideRootPath)!; |
| final driverForOutside = server.getAnalysisDriver(fileOutsideRootPath)!; |
| expect(driverForInside, equals(driverForOutside)); |
| // But that only the file inside the root was added. |
| expect(driverForInside.addedFiles, contains(fileInsideRootPath)); |
| expect(driverForInside.addedFiles, isNot(contains(fileOutsideRootPath))); |
| } |
| |
| Future<void> test_documentOpen_createsOverlay() async { |
| await _initializeAndOpen(); |
| |
| expect(server.resourceProvider.hasOverlay(mainFilePath), isTrue); |
| expect(server.resourceProvider.getFile(mainFilePath).readAsStringSync(), |
| equals(content)); |
| } |
| |
| Future<void> test_documentOpen_notifiesPlugins() async { |
| await _initializeAndOpen(); |
| |
| expect(pluginManager.analysisUpdateContentParams!.files, |
| equals({mainFilePath: AddContentOverlay(content)})); |
| } |
| |
| Future<void> test_documentOpen_setsPriorityFileIfEarly() async { |
| // When initializing with config support, the server will call back to the client |
| // which can delay analysis roots being configured. This can result in files |
| // being opened before analysis roots are set which has previously caused the |
| // files not to be marked as priority on the created drivers. |
| // https://github.com/Dart-Code/Dart-Code/issues/2438 |
| // https://github.com/dart-lang/sdk/issues/42994 |
| |
| // Initialize the server, but delay providing the configuration until after |
| // we've opened the file. |
| final completer = Completer<void>(); |
| |
| // Send the initialize request but do not await it. |
| final initResponse = initialize( |
| workspaceCapabilities: withDidChangeConfigurationDynamicRegistration( |
| withConfigurationSupport(emptyWorkspaceClientCapabilities))); |
| |
| // When asked for config, delay the response until we have sent the openFile notification. |
| final config = provideConfig( |
| () => initResponse, |
| completer.future.then((_) => {'dart.foo': false}), |
| ); |
| |
| // Wait for initialization to finish, open the file, then allow config to complete. |
| await initResponse; |
| await openFile(mainFileUri, content); |
| completer.complete(); |
| await config; |
| await pumpEventQueue(times: 5000); |
| |
| // Ensure the opened file is in the priority list. |
| expect(server.getAnalysisDriver(mainFilePath)!.priorityFiles, |
| equals([mainFilePath])); |
| } |
| |
| Future<void> _initializeAndOpen() async { |
| await initialize(); |
| await openFile(mainFileUri, content); |
| } |
| } |