blob: cf146e8b1dfd215b6f3c1e7bb1531433f1af5522 [file] [log] [blame]
// 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.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, [
TextDocumentContentChangeEvent.t1(TextDocumentContentChangeEvent1(
range: Range(
start: Position(line: 0, character: 6),
end: Position(line: 0, character: 9)),
text: 'Bar',
)),
TextDocumentContentChangeEvent.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, [
TextDocumentContentChangeEvent.t1(TextDocumentContentChangeEvent1(
range: Range(
start: Position(line: 0, character: 6),
end: Position(line: 0, character: 9)),
text: 'Bar',
)),
TextDocumentContentChangeEvent.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_contentChanged_analysis() async {
const content = '// original content';
const newContent = '// new content';
newFile(mainFilePath, content);
// Wait for initial analysis to provide diagnostics for the file.
await Future.wait([
waitForDiagnostics(mainFileUri),
initialize(),
]);
// Capture any further diagnostics sent after we open the file.
List<Diagnostic>? diagnostics;
unawaited(waitForDiagnostics(mainFileUri).then((d) => diagnostics = d));
await openFile(mainFileUri, newContent);
await pumpEventQueue(times: 5000);
// Expect diagnostics, because changing the content will have triggered
// analysis.
expect(diagnostics, isNotNull);
}
Future<void> test_documentOpen_contentUnchanged_noAnalysis() async {
const content = '// original content';
newFile(mainFilePath, content);
// Wait for initial analysis to provide diagnostics for the file.
await Future.wait([
waitForDiagnostics(mainFileUri),
initialize(),
]);
// Capture any further diagnostics sent after we open the file.
List<Diagnostic>? diagnostics;
unawaited(waitForDiagnostics(mainFileUri).then((d) => diagnostics = d));
await openFile(mainFileUri, content);
await pumpEventQueue(times: 5000);
// Expect no diagnostics because the file didn't actually change content
// when the overlay was created, so it should not have triggered analysis.
expect(diagnostics, isNull);
}
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);
}
}