Files
sdk/pkg/analysis_server/test/domain_analysis_test.dart
Paul Berry afcfbbeba8 Migrate developer experience packages to new constructor decl syntax.
(Part of https://github.com/dart-lang/sdk/issues/63288)

This change migrates the packages owned by the developer experience
team to use the new constructor declaration syntax, described in
https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations.

This change was performed in an automated fashion, by (a) bumping the
packages' SDK constraints to `3.13.0-0`, (b) enabling the lints
`unnecessary_type_name_in_constructor` and
`unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint
failures using `dart fix`, and then (d) reformatting the affected
files.

To ease code review, I've reverted unrelated formatting changes.

Since this change requires bumping SDK constaints to `3.13.0-0`, it
was only performed on packages that are *not* published on
pub. (Packages that *are* published on pub should remain on lower
language versions until at least after the stable version of 3.13 is
released, so that we don't block users on the stable channel from
receiving updates to those packages.)

Change-Id: Ibb4daebafd239da58251e838ea6a3f336a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505046
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
2026-05-27 14:52:58 -07:00

2732 lines
74 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 'dart:async';
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:analysis_server/src/plugin/plugin_locator.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer/src/utilities/extensions/file_system.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
import 'package:analyzer_testing/package_config_file_builder.dart';
import 'package:analyzer_testing/utilities/utilities.dart';
import 'package:analyzer_utilities/testing/tree_string_sink.dart';
import 'package:collection/collection.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'analysis_server_base.dart';
import 'mocks.dart';
import 'services/completion/dart/text_expectations.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(AnalysisDomainBlazeTest);
defineReflectiveTests(AnalysisDomainPubTest);
defineReflectiveTests(SetAnalysisRootsTest);
defineReflectiveTests(SetSubscriptionsTest);
defineReflectiveTests(UpdateTextExpectations);
});
}
@reflectiveTest
class AnalysisDomainBlazeTest extends _AnalysisDomainTest {
String get myPackageLibPath => '$myPackageRootPath/lib';
String get myPackageRootPath => '$workspaceRootPath/dart/my';
String get myPackageTestFilePath => '$myPackageLibPath/test.dart';
@override
void setUp() {
super.setUp();
newFile('$workspaceRootPath/${file_paths.blazeWorkspaceMarker}', '');
}
Future<void> test_fileSystem_changeFile_buildFile_legacy() async {
// Make it a Blaze package.
newBazelBuildFile(myPackageRootPath, r'''
# foo
''');
newFile(myPackageTestFilePath, '''
void f(int? a) {}
''');
await setRoots(included: [myPackageRootPath], excluded: []);
await server.onAnalysisComplete;
// No errors after initial analysis.
assertNotificationsText(r'''
AnalysisErrors
file: /home/dart/my/lib/test.dart
errors: empty
''');
// Change BUILD file, nothing interesting.
newBazelBuildFile(myPackageRootPath, r'''
# bar
''');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// BUILD file change caused rebuilding analysis contexts.
assertNotificationsText(r'''
AnalysisFlush
/home/dart/my/lib/test.dart
AnalysisErrors
file: /home/dart/my/lib/test.dart
errors: empty
''');
}
}
@reflectiveTest
class AnalysisDomainPubTest extends _AnalysisDomainTest {
Future<void> test_fileSystem_addFile_analysisOptions() async {
deleteTestPackageAnalysisOptionsFile();
_createFilesWithErrors([
'$testPackageLibPath/a.dart',
'$testPackageLibPath/b.dart',
]);
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// Both a.dart and b.dart are analyzed.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
AnalysisErrors
file: /home/test/lib/b.dart
errors: notEmpty
''');
// Write the options file that excludes b.dart
newAnalysisOptionsYamlFile(testPackageRootPath, r'''
analyzer:
exclude:
- lib/b.dart
''');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// Errors for all files were flushed, b.dart is not reported.
assertNotificationsText(r'''
AnalysisFlush
/home/test/lib/a.dart
/home/test/lib/b.dart
/home/test/pubspec.yaml
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
}
Future<void> test_fileSystem_addFile_analysisOptions_analysis() async {
deleteTestPackageAnalysisOptionsFile();
_createFilesWithErrors(['$testPackageLibPath/a.dart']);
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// a.dart was analyzed
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
// Add 'analysis_options.yaml' that has an error.
newAnalysisOptionsYamlFile(testPackageRootPath, '''
analyzer:
error:
''');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// Both files were analyzed.
assertNotificationsText(r'''
AnalysisFlush
/home/test/lib/a.dart
/home/test/pubspec.yaml
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: notEmpty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
}
Future<void> test_fileSystem_addFile_androidManifestXml() async {
newAnalysisOptionsYamlFile(testPackageRootPath, '''
analyzer:
optional-checks:
chrome-os-manifest-checks: true
''');
newFile('$testPackageLibPath/a.dart', '');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
''');
newFile('$testPackageRootPath/AndroidManifest.xml', '<manifest/>');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// No touch-screen.
// TODO(scheglov): Why duplicate analysis?
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/AndroidManifest.xml
errors: notEmpty
AnalysisErrors
file: /home/test/AndroidManifest.xml
errors: notEmpty
''');
}
Future<void> test_fileSystem_addFile_dart() async {
// We have to create the folder, otherwise there is nothing to watch.
newFolder(testPackageLibPath);
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// We don't have a.dart yet.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
''');
_createFilesWithErrors(['$testPackageLibPath/a.dart']);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// We created a.dart, so it should be analyzed.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
}
Future<void> test_fileSystem_addFile_dart_dotFolder() async {
var aPath = '$testPackageLibPath/.foo/a.dart';
var bPath = '$testPackageLibPath/b.dart';
newFile(bPath, r'''
import '.foo/a.dart';
void f(A a) {}
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// We don't have a.dart, so the import cannot be resolved.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/b.dart
errors: notEmpty
''');
newFile(aPath, r'''
class A {}
''');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// 'a.dart' is in a dot-folder, so excluded from analysis.
// We added a.dart with `A`, so no errors.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/b.dart
errors: empty
''');
}
Future<void> test_fileSystem_addFile_dart_excluded() async {
var aPath = '$testPackageLibPath/a.dart';
var bPath = '$testPackageLibPath/b.dart';
newAnalysisOptionsYamlFile(testPackageRootPath, r'''
analyzer:
exclude:
- "**/a.dart"
''');
newFile(bPath, r'''
import 'a.dart';
void f(A a) {}
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// We don't have a.dart, so the import cannot be resolved.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/b.dart
errors: notEmpty
''');
newFile(aPath, r'''
class A {}
''');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// We excluded 'a.dart' from analysis, no errors notification for it.
// We added a.dart with `A`, so no errors.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/b.dart
errors: empty
''');
}
Future<void> test_fileSystem_addFile_fixDataFolderYaml() async {
var path = '$testPackageLibPath/fix_data/foo.yaml';
newFile('$testPackageLibPath/a.dart', '');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// No `fix_data.yaml` to analyze yet.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
''');
// Create it, will be analyzed.
newFile(path, '0: 1');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// And it has errors.
// We don't recreate analysis contexts.
// TODO(scheglov): Why duplicate analysis?
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/fix_data/foo.yaml
errors: notEmpty
AnalysisErrors
file: /home/test/lib/fix_data/foo.yaml
errors: notEmpty
''');
}
Future<void> test_fileSystem_addFile_fixDataYaml() async {
var path = '$testPackageLibPath/fix_data.yaml';
newFile('$testPackageLibPath/a.dart', '');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// No `fix_data.yaml` to analyze yet.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
''');
// Create it, will be analyzed.
newFile(path, '0: 1');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// And it has errors.
// We don't recreate analysis contexts.
// TODO(scheglov): Why duplicate analysis?
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/fix_data.yaml
errors: notEmpty
AnalysisErrors
file: /home/test/lib/fix_data.yaml
errors: notEmpty
''');
}
Future<void> test_fileSystem_addFile_packageConfigJsonFile() async {
var aaaRootPath = '/packages/aaa';
var aPath = '$aaaRootPath/lib/a.dart';
newFile(aPath, '''
class A {}
''');
newFile(testFilePath, '''
import 'package:aaa/a.dart';
void f(A a) {}
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// We cannot resolve `package:aaa/a.dart`
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/test.dart
errors: notEmpty
''');
// Write `package_config.json`, recreate analysis contexts.
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'aaa', rootFolder: getFolder(aaaRootPath)),
);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// We rebuilt analysis contexts.
// We have `A` in 'package:aaa/a.dart', so no errors.
// Errors are not reported for packages.
assertNotificationsText(r'''
AnalysisFlush
/home/test/analysis_options.yaml
/home/test/lib/test.dart
/home/test/pubspec.yaml
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/test.dart
errors: empty
''');
}
Future<void> test_fileSystem_addFile_pubspec_analysis() async {
var aPath = '$testPackageLibPath/a.dart';
var pubspecPath = '$testPackageRootPath/pubspec.yaml';
newFile(aPath, 'error');
// Write an empty file to force a new analysis context.
// We look for `pubspec.yaml` files only in analysis context roots.
newAnalysisOptionsYamlFile(testPackageRootPath, '');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// a.dart was analyzed
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
// Add a non-Dart file that we know how to analyze.
newFile(pubspecPath, '''
name: sample
dependencies: true
''');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// We rebuilt analysis contexts.
// All files were analyzed.
assertNotificationsText(r'''
AnalysisFlush
/home/test/analysis_options.yaml
/home/test/lib/a.dart
/home/test/pubspec.yaml
AnalysisErrors
file: /home/test/pubspec.yaml
errors: notEmpty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
}
Future<void> test_fileSystem_addFile_unrelated() async {
var aPath = '$testPackageLibPath/a.dart';
var unrelatedPath = '$testPackageRootPath/unrelated.txt';
newFile(aPath, 'error');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// a.dart was analyzed
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
// Add an unrelated file, no analysis.
newFile(unrelatedPath, 'anything');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// No analysis.
assertNotificationsText(r'''
''');
}
Future<void> test_fileSystem_changeFile_analysisOptions() async {
var optionsPath = '$testPackageRootPath/analysis_options.yaml';
var aPath = '$testPackageLibPath/a.dart';
var bPath = '$testPackageLibPath/b.dart';
var cPath = '$testPackageLibPath/c.dart';
_createFilesWithErrors([aPath, bPath, cPath]);
// Exclude 'b.dart' from analysis.
newFile(optionsPath, r'''
analyzer:
exclude:
- lib/b.dart
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// Only 'a.dart' and 'c.dart' are analyzed, because 'b.dart' is excluded.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
AnalysisErrors
file: /home/test/lib/c.dart
errors: notEmpty
''');
// Exclude 'c.dart' from analysis.
newFile(optionsPath, r'''
analyzer:
exclude:
- lib/c.dart
''');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// Analysis contexts were rebuilt.
// Only 'a.dart' and 'b.dart' analyzed.
assertNotificationsText(r'''
AnalysisFlush
/home/test/analysis_options.yaml
/home/test/lib/a.dart
/home/test/lib/c.dart
/home/test/pubspec.yaml
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
AnalysisErrors
file: /home/test/lib/b.dart
errors: notEmpty
''');
}
Future<void> test_fileSystem_changeFile_androidManifestXml() async {
var path = '$testPackageRootPath/AndroidManifest.xml';
newFile('$testPackageLibPath/a.dart', '');
// Has an error - no touch screen.
newFile(path, '<manifest/>');
newAnalysisOptionsYamlFile(testPackageRootPath, '''
analyzer:
optional-checks:
chrome-os-manifest-checks: true
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/AndroidManifest.xml
errors: notEmpty
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
''');
// Update the file, so analyze it.
newFile(path, '<manifest/>');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// An error was reported.
// TODO(scheglov): Why duplicate analysis?
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/AndroidManifest.xml
errors: notEmpty
AnalysisErrors
file: /home/test/AndroidManifest.xml
errors: notEmpty
''');
}
Future<void> test_fileSystem_changeFile_dart() async {
var aPath = '$testPackageLibPath/a.dart';
var bPath = '$testPackageLibPath/b.dart';
newFile(aPath, r'''
class A2 {}
''');
newFile(bPath, r'''
import 'a.dart';
void f(A a) {}
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
AnalysisErrors
file: /home/test/lib/b.dart
errors: notEmpty
''');
// Update a.dart so that b.dart has no error.
newFile(aPath, 'class A {}');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// The update of a.dart fixed the error in b.dart
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
AnalysisErrors
file: /home/test/lib/b.dart
errors: empty
''');
}
Future<void> test_fileSystem_changeFile_dart_dotFolder() async {
var aPath = '$testPackageLibPath/.foo/a.dart';
var bPath = '$testPackageLibPath/b.dart';
newFile(aPath, r'''
class B {}
''');
newFile(bPath, r'''
import '.foo/a.dart';
void f(A a) {}
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// 'a.dart' is in a dot-folder, so excluded from analysis.
// We have `B`, not `A`, in a.dart, so has errors.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/b.dart
errors: notEmpty
''');
newFile(aPath, r'''
class A {}
''');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// 'a.dart' is in a dot-folder, so excluded from analysis.
// We changed a.dart, to have `A`, so no errors.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/b.dart
errors: empty
''');
}
Future<void> test_fileSystem_changeFile_dart_excluded() async {
var aPath = '$testPackageLibPath/a.dart';
var bPath = '$testPackageLibPath/b.dart';
newAnalysisOptionsYamlFile(testPackageRootPath, r'''
analyzer:
exclude:
- "**/a.dart"
''');
newFile(aPath, r'''
class B {}
''');
newFile(bPath, r'''
import 'a.dart';
void f(A a) {}
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// We excluded 'a.dart' from analysis, no errors notification for it.
// We have `B`, not `A`, in a.dart, so has errors.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/b.dart
errors: notEmpty
''');
newFile(aPath, r'''
class A {}
''');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// We changed a.dart, to have `A`, so no errors.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/b.dart
errors: empty
''');
}
Future<void> test_fileSystem_changeFile_fixDataFolderYaml() async {
var path = '$testPackageLibPath/fix_data/foo.yaml';
newFile('$testPackageLibPath/a.dart', '');
// This file has an error.
newFile(path, '0: 1');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// The file was analyzed.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/fix_data/foo.yaml
errors: notEmpty
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
''');
// Replace with the content that does not have errors.
newFile(path, r'''
version: 1
transforms: []
''');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// No errors in `foo.yaml` anymore.
// We don't recreate analysis contexts.
// TODO(scheglov): Why duplicate analysis?
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/fix_data/foo.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/fix_data/foo.yaml
errors: empty
''');
}
Future<void> test_fileSystem_changeFile_fixDataYaml() async {
var path = '$testPackageLibPath/fix_data.yaml';
newFile('$testPackageLibPath/a.dart', '');
// This file has an error.
newFile(path, '0: 1');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// The file was analyzed.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/fix_data.yaml
errors: notEmpty
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
''');
// Replace with the content that does not have errors.
newFile(path, r'''
version: 1
transforms: []
''');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// No errors in `fix_data.yaml` anymore.
// We don't recreate analysis contexts.
// TODO(scheglov): Why duplicate analysis?
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/fix_data.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/fix_data.yaml
errors: empty
''');
}
Future<void> test_fileSystem_changeFile_hasOverlay_removeOverlay() async {
newFile(testFilePath, '');
// Add an overlay without errors.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
testFile.path: AddContentOverlay(''),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
await setRoots(included: [workspaceRootPath], excluded: []);
// The test file (overlay) is analyzed, no errors.
await _waitAnalysisComplete();
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/test.dart
errors: empty
''');
// Change the file, has errors.
newFile(testFilePath, 'error');
// But the overlay is still present, so the file is not analyzed.
await _waitAnalysisComplete();
assertNotificationsText(r'''
''');
// Remove the overlay, now the file will be read.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
testFile.path: RemoveContentOverlay(),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
// The file has errors.
await _waitAnalysisComplete();
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/test.dart
errors: notEmpty
''');
}
Future<void> test_fileSystem_changeFile_packageConfigJsonFile() async {
var aaaRootPath = '/packages/aaa';
var aPath = '$aaaRootPath/lib/a.dart';
newFile(aPath, '''
class A {}
''');
newFile(testFilePath, '''
import 'package:aaa/a.dart';
void f(A a) {}
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// The default `package_config.json` is without `package:aaa`.
// We cannot resolve `package:aaa/a.dart`
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/test.dart
errors: notEmpty
''');
// Write `package_config.json`, recreate analysis contexts.
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'aaa', rootFolder: getFolder(aaaRootPath)),
);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// We have `A` in 'package:aaa/a.dart', so no errors.
// Errors are not reported for packages.
assertNotificationsText(r'''
AnalysisFlush
/home/test/analysis_options.yaml
/home/test/lib/test.dart
/home/test/pubspec.yaml
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/test.dart
errors: empty
''');
}
Future<void> test_fileSystem_deleteFile_analysisOptions() async {
var optionsPath = '$testPackageRootPath/analysis_options.yaml';
var aPath = '$testPackageLibPath/a.dart';
var bPath = '$testPackageLibPath/b.dart';
_createFilesWithErrors([aPath, bPath]);
// Exclude b.dart from analysis.
newFile(optionsPath, r'''
analyzer:
exclude:
- lib/b.dart
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// Only a.dart is analyzed, because b.dart is excluded.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
// Delete the options file.
deleteFile(optionsPath);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// Errors for a.dart were flushed, a.dart and b.dart analyzed.
assertNotificationsText(r'''
AnalysisFlush
/home/test/analysis_options.yaml
/home/test/lib/a.dart
/home/test/pubspec.yaml
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
AnalysisErrors
file: /home/test/lib/b.dart
errors: notEmpty
''');
}
Future<void> test_fileSystem_deleteFile_androidManifestXml() async {
var path = '$testPackageRootPath/AndroidManifest.xml';
newFile('$testPackageLibPath/a.dart', '');
// Has an error - no touch screen.
newFile(path, '<manifest/>');
newAnalysisOptionsYamlFile(testPackageRootPath, '''
analyzer:
optional-checks:
chrome-os-manifest-checks: true
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// An error was reported.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/AndroidManifest.xml
errors: notEmpty
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
''');
// Delete the file.
deleteFile(path);
await pumpEventQueue(times: 5000);
// We received a flush notification.
assertNotificationsText(r'''
AnalysisFlush
/home/test/AndroidManifest.xml
''');
}
/// Tests that deleting and re-creating a file while an overlay is active
/// keeps the diagnotics when the overlay is then removed, then removes them
/// when the file is deleted.
///
/// https://github.com/dart-lang/sdk/issues/53475
Future<void> test_fileSystem_deleteFile_createFile_withOverlay_dart() async {
var aPath = convertPath('$testPackageLibPath/a.dart');
_createFilesWithErrors([aPath]);
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// Initial file has errors.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
// Set the overlay, with a different content.
// We get another notification with errors.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
aPath: AddContentOverlay('error2'),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
// Delete the file, we have the overlay, so no notifications.
deleteFile(aPath);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
assertNotificationsText(r'''
''');
// Re-creating the file, we have the overlay, so no notifications.
_createFilesWithErrors([aPath]);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
assertNotificationsText(r'''
''');
// Remove the overlay, the file has different content, so notification.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
aPath: RemoveContentOverlay(),
}).toRequest('1', clientUriConverter: server.uriConverter),
);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
// Delete the file, errors are now gone.
deleteFile(aPath);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisFlush
/home/test/lib/a.dart
''');
}
Future<void> test_fileSystem_deleteFile_dart() async {
var aPath = '$testPackageLibPath/a.dart';
_createFilesWithErrors([aPath]);
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// a.dart was analyzed
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
deleteFile(aPath);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// We deleted a.dart, its errors should be flushed.
assertNotificationsText(r'''
AnalysisFlush
/home/test/lib/a.dart
''');
}
Future<void> test_fileSystem_deleteFile_dart_excluded() async {
var aPath = '$testPackageLibPath/a.dart';
var bPath = '$testPackageLibPath/b.dart';
newAnalysisOptionsYamlFile(testPackageRootPath, r'''
analyzer:
exclude:
- "**/a.dart"
''');
newFile(aPath, r'''
class A {}
''');
newFile(bPath, r'''
import 'a.dart';
void f(A a) {}
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// We excluded 'a.dart' from analysis, no errors notification for it.
// We have `A` in a.dart, so no errors.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/b.dart
errors: empty
''');
deleteFile(aPath);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// We deleted a.dart, so `A` cannot be resolved.
// TODO(scheglov): Should we get flush for a.dart also?
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/b.dart
errors: notEmpty
''');
}
Future<void> test_fileSystem_deleteFile_fixDataYaml() async {
var path = '$testPackageLibPath/fix_data.yaml';
newFile('$testPackageLibPath/a.dart', '');
// This file has an error.
newFile(path, '0: 1');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// The file was analyzed.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/fix_data.yaml
errors: notEmpty
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
''');
// Delete the file.
deleteFile(path);
await pumpEventQueue(times: 5000);
// We received a flush notification.
assertNotificationsText(r'''
AnalysisFlush
/home/test/lib/fix_data.yaml
''');
}
Future<void> test_fileSystem_deleteFile_packageConfigJsonFile() async {
var aaaRootPath = '/packages/aaa';
var aPath = '$aaaRootPath/lib/a.dart';
newFile(aPath, '''
class A {}
''');
// Write the empty file, without `package:aaa`.
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'aaa', rootFolder: getFolder(aaaRootPath)),
);
newFile(testFilePath, '''
import 'package:aaa/a.dart';
void f(A a) {}
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// We have `A` in 'package:aaa/a.dart', so no errors.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/test.dart
errors: empty
''');
// Delete `package_config.json`, recreate analysis contexts.
deleteFile('$testPackageRootPath/.dart_tool/package_config.json');
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// We cannot resolve 'package:aaa/a.dart', so errors.
// Errors are not reported for packages.
assertNotificationsText(r'''
AnalysisFlush
/home/test/analysis_options.yaml
/home/test/lib/test.dart
/home/test/pubspec.yaml
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/test.dart
errors: notEmpty
''');
}
/// Tests that deleting a file does not clear diagnostics if there's still
/// an active overlay for the file.
///
/// https://github.com/dart-lang/sdk/issues/53475
Future<void> test_fileSystem_deleteFile_withOverlay_dart() async {
var aPath = convertPath('$testPackageLibPath/a.dart');
_createFilesWithErrors([aPath]);
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// Initial file has errors.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
// Set overlay with different content, with errors.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
aPath: AddContentOverlay('error2'),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
// Delete file, has the overlay, no notifications.
deleteFile(aPath);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
assertNotificationsText(r'''
''');
// After removing the overlay, errors are gone.
// TODO(scheglov): why not flush?
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
aPath: RemoveContentOverlay(),
}).toRequest('1', clientUriConverter: server.uriConverter),
);
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
''');
}
Future<void> test_setPriorityFiles() async {
var a = getFile('$workspaceRootPath/foo/lib/a.dart');
var b = getFile('$workspaceRootPath/foo/lib/b.dart');
var c = getFile('$workspaceRootPath/bar/lib/c.dart');
var d = getFile('$workspaceRootPath/bar/lib/d.dart');
a.writeAsStringSync('');
b.writeAsStringSync('');
c.writeAsStringSync('');
d.writeAsStringSync('');
await handleSuccessfulRequest(
AnalysisSetPriorityFilesParams([
a.path,
c.path,
]).toRequest('0', clientUriConverter: server.uriConverter),
);
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// a.dart and c.dart are priority files.
// So, they are analyzed before b.dart and d.dart
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/foo/lib/a.dart
errors: empty
AnalysisErrors
file: /home/bar/lib/c.dart
errors: empty
AnalysisErrors
file: /home/foo/lib/b.dart
errors: empty
AnalysisErrors
file: /home/bar/lib/d.dart
errors: empty
''');
}
Future<void> test_setPriorityFiles_notAbsolute() async {
var response = await handleRequest(
AnalysisSetPriorityFilesParams([
'a.dart',
]).toRequest('0', clientUriConverter: server.uriConverter),
);
expect(
response,
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
);
}
Future<void> test_setPriorityFiles_withoutRoots() async {
await handleSuccessfulRequest(
AnalysisSetPriorityFilesParams([
convertPath('$testPackageLibPath/a.dart'),
]).toRequest('0', clientUriConverter: server.uriConverter),
);
}
Future<void> test_setRoots_excluded_notAbsolute() async {
var response = await handleRequest(
AnalysisSetAnalysisRootsParams(
[workspaceRootPath],
['foo'],
packageRoots: {},
).toRequest('0', clientUriConverter: server.uriConverter),
);
expect(
response,
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
);
}
Future<void> test_setRoots_excluded_notNormalized() async {
var response = await handleRequest(
AnalysisSetAnalysisRootsParams(
[workspaceRootPath],
[convertPath('/foo/../bar')],
packageRoots: {},
).toRequest('0', clientUriConverter: server.uriConverter),
);
expect(
response,
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
);
}
Future<void> test_setRoots_included_notAbsolute() async {
var response = await handleRequest(
AnalysisSetAnalysisRootsParams(
['foo'],
[],
packageRoots: {},
).toRequest('0', clientUriConverter: server.uriConverter),
);
expect(
response,
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
);
}
Future<void> test_setRoots_included_notNormalized() async {
var response = await handleRequest(
AnalysisSetAnalysisRootsParams(
[convertPath('/foo/../bar')],
[],
packageRoots: {},
).toRequest('0', clientUriConverter: server.uriConverter),
);
expect(
response,
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
);
}
Future<void> test_setRoots_includedFile() async {
var aPath = '$testPackageLibPath/a.dart';
var bPath = '$testPackageLibPath/b.dart';
_createFilesWithErrors([aPath, bPath]);
await setRoots(included: [aPath], excluded: []);
// Only a.dart is included, so b.dart is not analyzed.
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
}
Future<void> test_setRoots_includedFile_setRoots() async {
var aPath = '$testPackageLibPath/a.dart';
var bPath = '$testPackageLibPath/b.dart';
_createFilesWithErrors([aPath, bPath]);
// Include only single file.
await setRoots(included: [aPath], excluded: []);
await server.onAnalysisComplete;
// So, only a.dart is analyzed, and b.dart is not.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
// Include the folder that contains both a.dart and b.dart
await setRoots(included: [testPackageRootPath], excluded: []);
await server.onAnalysisComplete;
// So, both a.dart and b.dart are analyzed.
assertNotificationsText(r'''
AnalysisFlush
/home/test/lib/a.dart
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
AnalysisErrors
file: /home/test/lib/b.dart
errors: notEmpty
''');
}
Future<void> test_setRoots_includedFileFolder() async {
var includedFile = '$testPackageLibPath/a.dart';
var includedFolder = '$testPackageLibPath/foo';
var includedFolderFile1 = '$includedFolder/1.dart';
var includedFolderFile2 = '$includedFolder/2.dart';
var notIncludedFile = '$testPackageLibPath/b.dart';
_createFilesWithErrors([
includedFile,
includedFolderFile1,
includedFolderFile2,
notIncludedFile,
]);
await setRoots(included: [includedFile, includedFolder], excluded: []);
await server.onAnalysisComplete;
// We can combine a file, and a folder as included paths.
// And the file that is not in there is not analyzed.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/foo/1.dart
errors: notEmpty
AnalysisErrors
file: /home/test/lib/foo/2.dart
errors: notEmpty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
}
Future<void> test_setRoots_includedFolder_analysisOptions_exclude() async {
var aPath = '$testPackageLibPath/a.dart';
var bPath = '$testPackageLibPath/b.dart';
newAnalysisOptionsYamlFile(testPackageRootPath, '''
analyzer:
exclude:
- "**/b.dart"
''');
_createFilesWithErrors([aPath, bPath]);
await setRoots(included: [workspaceRootPath], excluded: []);
// b.dart is excluded using the options file.
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
}
@FailingTest(reason: 'Not implemented in ContextLocator')
Future<void> test_setRoots_includedFolder_excludedFile() async {
var aPath = '$testPackageLibPath/a.dart';
var excluded_path = '$testPackageRootPath/excluded/b.dart';
_createFilesWithErrors([aPath, excluded_path]);
await setRoots(included: [workspaceRootPath], excluded: [excluded_path]);
await server.onAnalysisComplete;
fail('Not implemented');
}
Future<void> test_setRoots_includedFolder_excludedFolder() async {
var aPath = '$testPackageLibPath/a.dart';
var excluded_path = '$testPackageRootPath/excluded/b.dart';
_createFilesWithErrors([aPath, excluded_path]);
await setRoots(
included: [workspaceRootPath],
excluded: ['$testPackageRootPath/excluded'],
);
await server.onAnalysisComplete;
// a.dart is analyzed, but b.dart is in the excluded folder.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
}
Future<void> test_setRoots_includedFolder_notExisting() async {
var existingFolder_path = '$testPackageLibPath/exiting';
var notExistingFolder_path = '$testPackageLibPath/notExisting';
var existingFile_path = '$existingFolder_path/1.dart';
_createFilesWithErrors([existingFile_path]);
await setRoots(
included: [existingFolder_path, notExistingFolder_path],
excluded: [],
);
await server.onAnalysisComplete;
// The not existing root does not prevent analysis of the existing one.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/exiting/1.dart
errors: notEmpty
''');
}
Future<void> test_setRoots_notDartFile_analysisOptions_excluded() async {
deleteTestPackageAnalysisOptionsFile();
var aPath = '$testPackageLibPath/a.dart';
var optionsPath = '$testPackageRootPath/analysis_options.yaml';
newFile(aPath, 'error');
// 'analysis_options.yaml' that has an error and excludes itself.
newFile(optionsPath, '''
analyzer:
exclude:
- analysis_options.yaml
error:
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
}
Future<void> test_setRoots_notDartFile_androidManifestXml() async {
var path = '$testPackageRootPath/AndroidManifest.xml';
newFile('$testPackageLibPath/a.dart', '');
newAnalysisOptionsYamlFile(testPackageRootPath, '''
analyzer:
optional-checks:
chrome-os-manifest-checks: true
''');
newFile(path, '<manifest/>');
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
// No touch-screen.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/AndroidManifest.xml
errors: notEmpty
AnalysisErrors
file: /home/test/lib/a.dart
errors: empty
''');
}
Future<void> test_setRoots_notDartFile_fixDataYaml() async {
var path = '$testPackageLibPath/fix_data.yaml';
// `lib/fix_data.yaml` will be analyzed.
newFile(path, '0: 1');
await setRoots(included: [workspaceRootPath], excluded: []);
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/fix_data.yaml
errors: notEmpty
''');
}
Future<void> test_setRoots_notDartFile_fixDataYaml_empty() async {
var path = '$testPackageLibPath/fix_data.yaml';
newFile(path, '');
await setRoots(included: [workspaceRootPath], excluded: []);
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/fix_data.yaml
errors: empty
''');
}
Future<void> test_setRoots_notDartFile_fixDataYaml_onlyComments() async {
var path = '$testPackageLibPath/fix_data.yaml';
newFile(path, '# one\n#two');
await setRoots(included: [workspaceRootPath], excluded: []);
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/fix_data.yaml
errors: empty
''');
}
Future<void> test_setRoots_notDartFile_pubspec_excluded() async {
deleteTestPackageAnalysisOptionsFile();
var aPath = '$testPackageLibPath/a.dart';
var optionsPath = '$testPackageRootPath/analysis_options.yaml';
newFile(aPath, 'error');
writeTestPackagePubspecYamlFile('''
name:
- error
''');
// 'analysis_options.yaml' that excludes pubspec.yaml.
newFile(optionsPath, '''
analyzer:
exclude:
- pubspec.yaml
''');
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// No pubspec.yaml
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/a.dart
errors: notEmpty
''');
}
Future<void> test_setRoots_packageConfigJsonFile() async {
var aaaRootPath = '/packages/aaa';
var aPath = '$aaaRootPath/lib/a.dart';
newFile(aPath, '''
class A {}
''');
writeTestPackageConfig(
config: PackageConfigFileBuilder()
..add(name: 'aaa', rootFolder: getFolder(aaaRootPath)),
);
newFile(testFilePath, '''
import 'package:aaa/a.dart';
void f(A a) {}
''');
// create project and wait for analysis
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
// We have `A` in 'package:aaa/a.dart', so no errors.
// Errors are not reported for packages.
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/test.dart
errors: empty
''');
}
Future<void> test_updateContent_addOverlay() async {
newFile(testFilePath, 'error');
await setRoots(included: [workspaceRootPath], excluded: []);
// The file in the file system has errors.
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/test.dart
errors: notEmpty
''');
// Add an overlay without errors.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
testFile.path: AddContentOverlay(''),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
// A new errors notification was received, no errors.
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/test.dart
errors: empty
''');
}
Future<void> test_updateContent_changeOverlay() async {
newFile(testFilePath, '');
// Add the content with an error.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
testFile.path: AddContentOverlay('var v = 0'),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
await setRoots(included: [workspaceRootPath], excluded: []);
// The overlay has an error.
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/pubspec.yaml
errors: empty
AnalysisErrors
file: /home/test/analysis_options.yaml
errors: empty
AnalysisErrors
file: /home/test/lib/test.dart
errors: notEmpty
''');
// Add the missing `;`.
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
testFile.path: ChangeContentOverlay([SourceEdit(9, 0, ';')]),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
// A new errors notification was received, no errors.
await server.onAnalysisComplete;
assertNotificationsText(r'''
AnalysisErrors
file: /home/test/lib/test.dart
errors: empty
''');
}
Future<void> test_updateContent_notAbsolute() async {
var response = await handleRequest(
AnalysisUpdateContentParams({
'a.dart': AddContentOverlay(''),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
expect(response, isResponseFailure('0'));
}
Future<void> test_updateContent_outOfRange_beyondEnd() {
return _updateContent_outOfRange('012', SourceEdit(0, 5, 'foo'));
}
Future<void> test_updateContent_outOfRange_negativeLength() {
return _updateContent_outOfRange('', SourceEdit(3, -1, 'foo'));
}
Future<void> test_updateContent_outOfRange_negativeOffset() {
return _updateContent_outOfRange('', SourceEdit(-1, 3, 'foo'));
}
Future<void> _updateContent_outOfRange(
String initialContent,
SourceEdit edit,
) async {
newFile(testFilePath, initialContent);
await setRoots(included: [workspaceRootPath], excluded: []);
await server.onAnalysisComplete;
await handleSuccessfulRequest(
AnalysisUpdateContentParams({
testFile.path: AddContentOverlay(initialContent),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
var response = await handleRequest(
AnalysisUpdateContentParams({
testFile.path: ChangeContentOverlay([edit]),
}).toRequest('0', clientUriConverter: server.uriConverter),
);
expect(
response,
isResponseFailure('0', RequestErrorCode.INVALID_OVERLAY_CHANGE),
);
}
/// Pump the event queue, so that watch events are processed.
/// Wait for analysis to complete.
/// Repeat a few times, eventually there will be no work to do.
Future<void> _waitAnalysisComplete() async {
for (var i = 0; i < 128; i++) {
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
}
}
}
@reflectiveTest
class SetAnalysisRootsTest extends PubPackageAnalysisServerTest {
/// Verifies the set of context roots created by the server and which were
/// provided to each plugin.
void expectPluginMapping(Map<String, List<String>> expected) {
var pluginMapping = pluginManager.contextRootPlugins.map(
(root, plugins) => MapEntry(
pathContext.basename(root.root.path),
plugins.map(
(pluginPath) => pathContext.basename(
pathContext.dirname(pathContext.dirname(pluginPath)),
),
),
),
);
// Additionally, add any context roots from the server that were not
// provided to plugins so the tests can also explicitly verify roots were
// created that didn't show up in the plugins list.
for (var serverContext in server.contextManager.analysisContexts) {
var name = pathContext.basename(serverContext.contextRoot.root.path);
pluginMapping.putIfAbsent(name, () => []);
}
expect(pluginMapping, expected);
}
@override
void setUp() {
super.setUp();
// These tests don't use the "test" package folder but have their own named
// package folders. Delete the "test" folder so it doesn't show up as a
// context root (else it would need listing in each test expectation).
deleteFolder(testPackageRootPath);
}
/// Tests a package with a nested folder that has an additional
/// `analysis_options.yaml` that does not enable the plugin. An additional
/// context should be created for the non-plugin folder and it should be
/// excluded from the parents plugin root.
Future<void>
test_sentToPlugins_inNestedPackages_withNestedAnalysisOptions_enabledPlugin_disabledPlugin() async {
var plugin1 = (name: 'plugin1', path: _createPlugin('plugin1'));
// package1 has plugin2 enabled.
_createTestPackage(
'package1',
withPackageConfig: false,
plugins: [plugin1],
);
// nestedFolder1 has no plugins enabled.
newAnalysisOptionsYamlFile(
join(workspaceRootPath, 'package1', 'nestedFolder1'),
analysisOptionsContent(experiments: experiments),
);
// Write the single package config at the root that can resolve both
// plugins.
newPackageConfigJsonFileFromBuilder(
workspaceRootPath,
PackageConfigFileBuilder()
..add(name: 'plugin1', rootFolder: getFolder(plugin1.path)),
);
// Set the analysis roots to the folder ('/home') that contains both
// packages but not the plugins (which are in '/plugins').
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
expectPluginMapping({
'home': [],
'package1': ['plugin1'],
'nestedFolder1': [],
});
}
/// Tests a package with a nested folder that has an additional
/// `analysis_options.yaml` that enables a different plugin. An additional
/// root should be created.
Future<void>
test_sentToPlugins_inNestedPackages_withNestedAnalysisOptions_enabledPlugin_enabledDifferentPlugin() async {
var plugin1 = (name: 'plugin1', path: _createPlugin('plugin1'));
var plugin2 = (name: 'plugin2', path: _createPlugin('plugin2'));
// package1 has plugin2 enabled.
_createTestPackage(
'package1',
withPackageConfig: false,
plugins: [plugin1],
);
// nestedFolder1 has plugin2 enabled.
newAnalysisOptionsYamlFile(
join(workspaceRootPath, 'package1', 'nestedFolder1'),
analysisOptionsContent(
experiments: experiments,
legacyPlugins: [plugin2.name],
),
);
// Write the single package config at the root that can resolve both
// plugins.
newPackageConfigJsonFileFromBuilder(
workspaceRootPath,
PackageConfigFileBuilder()
..add(name: 'plugin1', rootFolder: getFolder(plugin1.path))
..add(name: 'plugin2', rootFolder: getFolder(plugin2.path)),
);
// Set the analysis roots to the folder ('/home') that contains both
// packages but not the plugins (which are in '/plugins').
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
expectPluginMapping({
'home': [],
'package1': ['plugin1'],
'nestedFolder1': ['plugin2'],
});
}
/// Tests a package with a nested folder that has an additional
/// `analysis_options.yaml` that enables the same plugin explicitly. No
/// additional context needs to be created.
Future<void>
test_sentToPlugins_inNestedPackages_withNestedAnalysisOptions_enabledPlugin_enabledPluginExplicit() async {
var plugin1 = (name: 'plugin1', path: _createPlugin('plugin1'));
// package1 has plugin2 enabled.
_createTestPackage(
'package1',
withPackageConfig: false,
plugins: [plugin1],
);
// nestedFolder1 also has plugin1 enabled.
newAnalysisOptionsYamlFile(
join(workspaceRootPath, 'package1', 'nestedFolder1'),
analysisOptionsContent(
experiments: experiments,
legacyPlugins: [plugin1.name],
),
);
// Write the single package config at the root that can resolve both
// plugins.
newPackageConfigJsonFileFromBuilder(
workspaceRootPath,
PackageConfigFileBuilder()
..add(name: 'plugin1', rootFolder: getFolder(plugin1.path)),
);
// Set the analysis roots to the folder ('/home') that contains both
// packages but not the plugins (which are in '/plugins').
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
expectPluginMapping({
'home': [],
'package1': ['plugin1'],
// nestedFolder1 is included as part of package1.
});
}
/// Tests a package with a nested folder that has an additional
/// `analysis_options.yaml` that enables the same plugin by including the
/// parents `analysis_options.yaml`. No additional context needs to be
/// created.
Future<void>
test_sentToPlugins_inNestedPackages_withNestedAnalysisOptions_enabledPlugin_enabledPluginInclude() async {
var plugin1 = (name: 'plugin1', path: _createPlugin('plugin1'));
// package1 has plugin2 enabled.
_createTestPackage(
'package1',
withPackageConfig: false,
plugins: [plugin1],
);
// nestedFolder1 also has plugin1 enabled because it includes the parent
// `analysis_options.yaml`.
newAnalysisOptionsYamlFile(
join(workspaceRootPath, 'package1', 'nestedFolder1'),
analysisOptionsContent(includes: ['../analysis_options.yaml']),
);
// Write the single package config at the root that can resolve both
// plugins.
newPackageConfigJsonFileFromBuilder(
workspaceRootPath,
PackageConfigFileBuilder()
..add(name: 'plugin1', rootFolder: getFolder(plugin1.path)),
);
// Set the analysis roots to the folder ('/home') that contains both
// packages but not the plugins (which are in '/plugins').
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
expectPluginMapping({
'home': [],
'package1': ['plugin1'],
// nestedFolder1 is included as part of package1.
});
}
/// Test that the correct context roots are passed to plugins when they are
/// enabled only for projects nested within the workspace (which do not have
/// their own package configs but use the one from the root).
///
/// In this example, we have three nested projects that use a mix of plugins:
///
/// - root/ (no plugins enabled)
/// - package1/ (plugin1, (plugin2 - disabled due to limit))
/// - package2/ (plugin2, (plugin1 - disabled due to limit))
/// - package3/ (plugin1)
Future<void>
test_sentToPlugins_inNestedPackages_withoutPackageConfigs() async {
var plugin1 = (name: 'plugin1', path: _createPlugin('plugin1'));
var plugin2 = (name: 'plugin2', path: _createPlugin('plugin2'));
// Only the first plugin for each will be enabled due to the 1-plugin-limit.
_createTestPackage(
'package1',
withPackageConfig: false,
plugins: [plugin1, plugin2],
);
_createTestPackage(
'package2',
withPackageConfig: false,
plugins: [plugin2, plugin1],
);
_createTestPackage(
'package3',
withPackageConfig: false,
plugins: [plugin1],
);
// Write the single package config at the root that can resolve both
// plugins.
newPackageConfigJsonFileFromBuilder(
workspaceRootPath,
PackageConfigFileBuilder()
..add(name: 'plugin1', rootFolder: getFolder(plugin1.path))
..add(name: 'plugin2', rootFolder: getFolder(plugin2.path)),
);
// Set the analysis roots to the folder ('/home') that contains both
// packages but not the plugins (which are in '/plugins').
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
expectPluginMapping({
'home': [],
'package1': ['plugin1'],
'package2': ['plugin2'],
'package3': ['plugin1'],
});
}
/// Test that the correct context roots are passed to plugins when they are
/// enabled only for projects nested within the workspace (which have their
/// own package configs).
///
/// In this example, we have three nested projects that use a mix of plugins:
///
/// - root/ (no plugins enabled)
/// - package1/ (plugin1, (plugin2 - disabled due to limit))
/// - package2/ (plugin2, (plugin1 - disabled due to limit))
/// - package3/ (plugin1)
Future<void> test_sentToPlugins_inNestedPackages_withPackageConfigs() async {
var plugin1 = (name: 'plugin1', path: _createPlugin('plugin1'));
var plugin2 = (name: 'plugin2', path: _createPlugin('plugin2'));
// Only the first plugin for each will be enabled due to the 1-plugin-limit.
_createTestPackage('package1', plugins: [plugin1, plugin2]);
_createTestPackage('package2', plugins: [plugin2, plugin1]);
_createTestPackage('package3', plugins: [plugin1]);
// Ensure the root directory can resolve both plugin packages even though
// we don't enabled them for the root. This will catch if we incorrectly try
// to enable them because we read a child analysis_options.
newPackageConfigJsonFileFromBuilder(
workspaceRootPath,
PackageConfigFileBuilder()
..add(name: 'plugin1', rootFolder: getFolder(plugin1.path))
..add(name: 'plugin2', rootFolder: getFolder(plugin2.path)),
);
// Set the analysis roots to the folder ('/home') that contains both
// packages but not the plugins (which are in '/plugins').
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
expectPluginMapping({
'home': [],
'package1': ['plugin1'],
'package2': ['plugin2'],
'package3': ['plugin1'],
});
}
/// Test that the correct context roots are passed to plugins when they are
/// enabled for projects that contain subfolders that do not contain analysis
/// options and so the existing plugins apply.
///
/// In this example, a project has plugins and it has subfolders that inherit
/// those options (and thus do not produce a new root).
///
/// - root/ (no plugins enabled)
/// - package1/ (plugin1)
/// - package1/lib/ (no explicit options, plugin1 implied)
Future<void> test_sentToPlugins_inNestedPackages_withSubFolders() async {
var plugin1 = (name: 'plugin1', path: _createPlugin('plugin1'));
// Only the first plugin for each will be enabled due to the 1-plugin-limit.
var package1Path = _createTestPackage(
'package1',
withPackageConfig: false,
plugins: [plugin1],
);
newFolder(join(package1Path, 'lib'));
// Write the single package config at the root that can resolve the plugin.
newPackageConfigJsonFileFromBuilder(
workspaceRootPath,
PackageConfigFileBuilder()
..add(name: 'plugin1', rootFolder: getFolder(plugin1.path)),
);
// Set the analysis roots to the folder ('/home') that contains the
// package but not the plugin (which is in '/plugins').
await setRoots(included: [workspaceRootPath], excluded: []);
await waitForTasksFinished();
expectPluginMapping({
'home': [],
'package1': ['plugin1'],
});
}
/// Creates a plugin package named [name] and returns the path to the root
/// of the package.
String _createPlugin(String name) {
var pluginDirectory = convertPath(join('/plugins', name));
newPubspecYamlFile(pluginDirectory, 'name: $name');
newFile(convertPath(join(pluginDirectory, 'lib', '$name.dart')), '');
newFolder(
join(
pluginDirectory,
PluginLocator.toolsFolderName,
PluginLocator.defaultPluginFolderName,
'$name.dart',
),
);
return pluginDirectory;
}
/// Creates a package in the test workspace named [name] that uses [plugins].
String _createTestPackage(
String name, {
bool withPackageConfig = true,
List<({String name, String path})> plugins = const [],
}) {
var packagePath = join(workspaceRootPath, name);
newPubspecYamlFile(packagePath, 'name: $name');
newAnalysisOptionsYamlFile(
packagePath,
analysisOptionsContent(
experiments: experiments,
legacyPlugins: plugins.map((plugin) => plugin.name).toList(),
),
);
if (withPackageConfig) {
var packageConfig = PackageConfigFileBuilder()
..add(name: name, rootFolder: getFolder(packagePath));
for (var plugin in plugins) {
packageConfig.add(
name: plugin.name,
rootFolder: getFolder(plugin.path),
);
}
newPackageConfigJsonFileFromBuilder(packagePath, packageConfig);
}
return packagePath;
}
}
@reflectiveTest
class SetSubscriptionsTest extends PubPackageAnalysisServerTest {
Map<File, List<HighlightRegion>> filesHighlights = {};
final Completer<void> _resultsAvailable = Completer();
@override
void processNotification(Notification notification) {
super.processNotification(notification);
if (notification.event == analysisNotificationHighlights) {
var params = AnalysisHighlightsParams.fromNotification(
notification,
clientUriConverter: server.uriConverter,
);
filesHighlights[getFile(params.file)] = params.regions;
_resultsAvailable.complete();
}
}
@override
Future<void> setUp() async {
super.setUp();
await setRoots(included: [workspaceRootPath], excluded: []);
}
Future<void> test_afterAnalysis() async {
addTestFile('int V = 42;');
// wait for analysis, no results initially
await waitForTasksFinished();
expect(filesHighlights[testFile], isNull);
// subscribe
await addAnalysisSubscription(AnalysisService.HIGHLIGHTS, testFile);
await _resultsAvailable.future;
// there are results
expect(filesHighlights[testFile], isNotEmpty);
}
Future<void> test_afterAnalysis_noSuchFile() async {
var file = getFile('/no-such-file.dart');
addTestFile('// no matter');
// wait for analysis, no results initially
await waitForTasksFinished();
expect(filesHighlights[testFile], isNull);
// subscribe
await addAnalysisSubscription(AnalysisService.HIGHLIGHTS, file);
await _resultsAvailable.future;
// there are results
expect(filesHighlights[file], isEmpty);
}
Future<void> test_afterAnalysis_packageFile_external() async {
var pkgFile = newFile('/packages/pkgA/lib/libA.dart', '''
library lib_a;
class A {}
''');
writePackageConfig(
convertPath('/project'),
config: (PackageConfigFileBuilder()
..add(name: 'pkgA', rootFolder: getFolder('/packages/pkgA'))),
);
//
addTestFile('''
import 'package:pkgA/libA.dart';
void f() {
new A();
}
''');
// wait for analysis, no results initially
await waitForTasksFinished();
expect(filesHighlights[pkgFile], isNull);
// subscribe
await addAnalysisSubscription(AnalysisService.HIGHLIGHTS, pkgFile);
await _resultsAvailable.future;
// there are results
expect(filesHighlights[pkgFile], isNotEmpty);
}
Future<void> test_afterAnalysis_packageFile_inRoot() async {
var pkgA = convertPath('/pkgA');
var pkgB = convertPath('/pkgA');
var pkgFileA = newFile('$pkgA/lib/libA.dart', '''
library lib_a;
class A {}
''');
newFile('$pkgA/lib/libB.dart', '''
import 'package:pkgA/libA.dart';
void f() {
new A();
}
''');
// add 'pkgA' and 'pkgB' as projects
await setRoots(included: [pkgA, pkgB], excluded: []);
// wait for analysis, no results initially
await waitForTasksFinished();
expect(filesHighlights[pkgFileA], isNull);
// subscribe
await addAnalysisSubscription(AnalysisService.HIGHLIGHTS, pkgFileA);
await _resultsAvailable.future;
// there are results
expect(filesHighlights[pkgFileA], isNotEmpty);
}
Future<void> test_afterAnalysis_packageFile_notUsed() async {
var pkgFile = newFile('/packages/pkgA/lib/libA.dart', '''
library lib_a;
class A {}
''');
writePackageConfig(
convertPath('/project'),
config: (PackageConfigFileBuilder()
..add(name: 'pkgA', rootFolder: getFolder('/packages/pkgA'))),
);
//
addTestFile('// no "pkgA" reference');
// wait for analysis, no results initially
await waitForTasksFinished();
expect(filesHighlights[pkgFile], isNull);
// make it a priority file, so make analyzable
server.setPriorityFiles('0', [pkgFile.path]);
// subscribe
await addAnalysisSubscription(AnalysisService.HIGHLIGHTS, pkgFile);
await _resultsAvailable.future;
// there are results
expect(filesHighlights[pkgFile], isNotEmpty);
}
Future<void> test_afterAnalysis_sdkFile() async {
var file = getFile('/sdk/lib/core/core.dart');
addTestFile('// no matter');
// wait for analysis, no results initially
await waitForTasksFinished();
expect(filesHighlights[file], isNull);
// subscribe
await addAnalysisSubscription(AnalysisService.HIGHLIGHTS, file);
await _resultsAvailable.future;
// there are results
expect(filesHighlights[file], isNotEmpty);
}
Future<void> test_beforeAnalysis() async {
addTestFile('int V = 42;');
// subscribe
await addAnalysisSubscription(AnalysisService.HIGHLIGHTS, testFile);
// wait for analysis
await waitForTasksFinished();
expect(filesHighlights[testFile], isNotEmpty);
}
Future<void> test_sentToPlugins() async {
addTestFile('int V = 42;');
// subscribe
await addAnalysisSubscription(AnalysisService.HIGHLIGHTS, testFile);
// wait for analysis
await waitForTasksFinished();
var params = pluginManager.analysisSetSubscriptionsParams!;
var subscriptions = params.subscriptions;
expect(subscriptions, hasLength(1));
var files = subscriptions[plugin.AnalysisService.HIGHLIGHTS];
expect(files, [testFile.path]);
}
}
class _AnalysisDomainTest extends PubPackageAnalysisServerTest {
final configuration = _NotificationPrinterConfiguration();
final List<(String, Object)> notifications = [];
void assertNotificationsText(String expected) {
var buffer = StringBuffer();
var sink = TreeStringSink(sink: buffer, indent: '');
_NotificationPrinter(
configuration: configuration,
resourceProvider: resourceProvider,
sink: sink,
).writeNotifications(notifications);
notifications.clear();
var actual = buffer.toString();
if (actual != expected) {
print('-------- Actual --------');
print('$actual------------------------');
TextExpectationsCollector.add(actual);
}
expect(actual, expected);
}
@override
void processNotification(Notification notification) {
if (notification.event == analysisNotificationFlushResults) {
var decoded = AnalysisFlushResultsParams.fromNotification(
notification,
clientUriConverter: server.uriConverter,
);
notifications.add((notification.event, decoded));
}
if (notification.event == analysisNotificationErrors) {
var decoded = AnalysisErrorsParams.fromNotification(
notification,
clientUriConverter: server.uriConverter,
);
notifications.add((notification.event, decoded));
}
}
/// Create files with a content that has a compile time error.
void _createFilesWithErrors(List<String> paths) {
for (var path in paths) {
newFile(path, 'error');
}
}
}
class _NotificationPrinter {
final _NotificationPrinterConfiguration configuration;
final ResourceProvider resourceProvider;
final TreeStringSink sink;
new({
required this.configuration,
required this.resourceProvider,
required this.sink,
});
void writeNotifications(List<(String, Object)> notifications) {
for (var entry in notifications) {
_writeNotification(entry.$1, entry.$2);
}
}
void _writelnFile(String path, {String? name}) {
sink.writeIndentedLine(() {
if (name != null) {
sink.write('$name: ');
}
var file = resourceProvider.getFile(path);
sink.write(file.posixPath);
});
}
void _writeNotification(String event, Object notification) {
switch (notification) {
case AnalysisFlushResultsParams():
var files = notification.files.sorted();
sink.writeElements('AnalysisFlush', files, _writelnFile);
case AnalysisErrorsParams():
sink.writelnWithIndent('AnalysisErrors');
sink.withIndent(() {
_writelnFile(name: 'file', notification.file);
if (configuration.withAnalysisErrorDetails) {
throw UnimplementedError();
} else if (notification.errors.isNotEmpty) {
sink.writelnWithIndent('errors: notEmpty');
} else {
sink.writelnWithIndent('errors: empty');
}
});
default:
throw UnimplementedError('${notification.runtimeType}');
}
}
}
class _NotificationPrinterConfiguration {
bool withAnalysisErrorDetails = false;
}