mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
![Konstantin Shcheglov](/assets/img/avatar_default.png)
Bug: https://buganizer.corp.google.com/issues/195398835 Change-Id: I80a659a8ab6a3162703e6442970438af9b19be60 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209020 Reviewed-by: Samuel Rawlins <srawlins@google.com> Reviewed-by: Michal Terepeta <michalt@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
1825 lines
53 KiB
Dart
1825 lines
53 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/analysis_server.dart';
|
|
import 'package:analysis_server/src/domain_analysis.dart';
|
|
import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
|
|
import 'package:analysis_server/src/utilities/mocks.dart';
|
|
import 'package:analyzer/instrumentation/instrumentation.dart';
|
|
import 'package:analyzer/src/generated/sdk.dart';
|
|
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
|
|
import 'package:analyzer/src/test_utilities/package_config_file_builder.dart';
|
|
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import 'analysis_abstract.dart';
|
|
import 'mocks.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(AnalysisDomainBazelTest);
|
|
defineReflectiveTests(AnalysisDomainPubTest);
|
|
defineReflectiveTests(AnalysisDomainHandlerTest);
|
|
defineReflectiveTests(SetSubscriptionsTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class AnalysisDomainBazelTest extends _AnalysisDomainTest {
|
|
String get myPackageLibPath => '$myPackageRootPath/lib';
|
|
|
|
String get myPackageRootPath => '$workspaceRootPath/dart/my';
|
|
|
|
String get myPackageTestFilePath => '$myPackageLibPath/test.dart';
|
|
|
|
String get workspaceRootPath => '/workspace';
|
|
|
|
@override
|
|
void setUp() {
|
|
super.setUp();
|
|
newFile('$workspaceRootPath/WORKSPACE');
|
|
}
|
|
|
|
Future<void> test_fileSystem_changeFile_buildFile() async {
|
|
// This BUILD file does not enable null safety.
|
|
newBazelBuildFile(myPackageRootPath, '');
|
|
|
|
newFile(myPackageTestFilePath, content: '''
|
|
void f(int? a) {}
|
|
''');
|
|
|
|
setRoots(included: [myPackageRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// Cannot use `int?` without enabling null safety.
|
|
assertHasErrors(myPackageTestFilePath);
|
|
|
|
// Enable null safety.
|
|
newBazelBuildFile(myPackageRootPath, '''
|
|
dart_package(null_safety = True)
|
|
''');
|
|
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We have null safety enabled, so no errors.
|
|
assertNoErrors(myPackageTestFilePath);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class AnalysisDomainHandlerTest extends AbstractAnalysisTest {
|
|
Future<void> outOfRangeTest(SourceEdit edit) async {
|
|
var helper = AnalysisTestHelper();
|
|
helper.createSingleFileProject('library A;');
|
|
await helper.onAnalysisComplete;
|
|
helper.sendContentChange(AddContentOverlay('library B;'));
|
|
await helper.onAnalysisComplete;
|
|
var contentChange = ChangeContentOverlay([edit]);
|
|
var request = AnalysisUpdateContentParams({helper.testFile: contentChange})
|
|
.toRequest('0');
|
|
var response = helper.handler.handleRequest(request);
|
|
expect(response,
|
|
isResponseFailure('0', RequestErrorCode.INVALID_OVERLAY_CHANGE));
|
|
}
|
|
|
|
Future<void> test_setAnalysisRoots_excludedFolder() async {
|
|
newFile('/project/aaa/a.dart', content: '// a');
|
|
newFile('/project/bbb/b.dart', content: '// b');
|
|
var excludedPath = join(projectPath, 'bbb');
|
|
var response = testSetAnalysisRoots([projectPath], [excludedPath]);
|
|
expect(response, isResponseSuccess('0'));
|
|
}
|
|
|
|
Future<void> test_setAnalysisRoots_included_newFolder() async {
|
|
newPubspecYamlFile('/project', 'name: project');
|
|
var file = newFile('/project/bin/test.dart', content: 'main() {}').path;
|
|
var response = testSetAnalysisRoots([projectPath], []);
|
|
var serverRef = server;
|
|
expect(response, isResponseSuccess('0'));
|
|
// verify that unit is resolved eventually
|
|
await server.onAnalysisComplete;
|
|
var resolvedUnit = await serverRef.getResolvedUnit(file);
|
|
expect(resolvedUnit, isNotNull);
|
|
}
|
|
|
|
Future<void> test_setAnalysisRoots_included_nonexistentFolder() async {
|
|
var projectA = convertPath('/project_a');
|
|
var projectB = convertPath('/project_b');
|
|
var fileB = newFile('/project_b/b.dart', content: '// b').path;
|
|
var response = testSetAnalysisRoots([projectA, projectB], []);
|
|
var serverRef = server;
|
|
expect(response, isResponseSuccess('0'));
|
|
// Non-existence of /project_a should not prevent files in /project_b
|
|
// from being analyzed.
|
|
await server.onAnalysisComplete;
|
|
var resolvedUnit = await serverRef.getResolvedUnit(fileB);
|
|
expect(resolvedUnit, isNotNull);
|
|
}
|
|
|
|
Future<void> test_setAnalysisRoots_included_notAbsolute() async {
|
|
var response = testSetAnalysisRoots(['foo/bar'], []);
|
|
expect(response,
|
|
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT));
|
|
}
|
|
|
|
Future<void> test_setAnalysisRoots_included_notNormalized() async {
|
|
var response = testSetAnalysisRoots(['/foo/../bar'], []);
|
|
expect(response,
|
|
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT));
|
|
}
|
|
|
|
Future<void> test_setAnalysisRoots_notAbsolute() async {
|
|
var response = testSetAnalysisRoots([], ['foo/bar']);
|
|
expect(response,
|
|
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT));
|
|
}
|
|
|
|
Future<void> test_setAnalysisRoots_notNormalized() async {
|
|
var response = testSetAnalysisRoots([], ['/foo/../bar']);
|
|
expect(response,
|
|
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT));
|
|
}
|
|
|
|
void test_setPriorityFiles_invalid() {
|
|
var request = AnalysisSetPriorityFilesParams(
|
|
[convertPath('/project/lib.dart')],
|
|
).toRequest('0');
|
|
var response = handler.handleRequest(request);
|
|
expect(response, isResponseSuccess('0'));
|
|
}
|
|
|
|
void test_setPriorityFiles_valid() {
|
|
var p1 = convertPath('/p1');
|
|
var p2 = convertPath('/p2');
|
|
var aPath = convertPath('/p1/a.dart');
|
|
var bPath = convertPath('/p2/b.dart');
|
|
var cPath = convertPath('/p2/c.dart');
|
|
newFile(aPath, content: 'library a;');
|
|
newFile(bPath, content: 'library b;');
|
|
newFile(cPath, content: 'library c;');
|
|
|
|
var setRootsRequest =
|
|
AnalysisSetAnalysisRootsParams([p1, p2], []).toRequest('0');
|
|
var setRootsResponse = handler.handleRequest(setRootsRequest);
|
|
expect(setRootsResponse, isResponseSuccess('0'));
|
|
|
|
void setPriorityFiles(List<String> fileList) {
|
|
var request = AnalysisSetPriorityFilesParams(fileList).toRequest('0');
|
|
var response = handler.handleRequest(request);
|
|
expect(response, isResponseSuccess('0'));
|
|
// TODO(brianwilkerson) Enable the line below after getPriorityFiles
|
|
// has been implemented.
|
|
// expect(server.getPriorityFiles(), unorderedEquals(fileList));
|
|
}
|
|
|
|
setPriorityFiles([aPath, bPath]);
|
|
setPriorityFiles([bPath, cPath]);
|
|
setPriorityFiles([]);
|
|
}
|
|
|
|
Future<void> test_updateContent_badType() async {
|
|
var helper = AnalysisTestHelper();
|
|
helper.createSingleFileProject('// empty');
|
|
await helper.onAnalysisComplete;
|
|
var request = Request('0', ANALYSIS_REQUEST_UPDATE_CONTENT, {
|
|
ANALYSIS_REQUEST_UPDATE_CONTENT_FILES: {
|
|
helper.testFile: {
|
|
'type': 'foo',
|
|
}
|
|
}
|
|
});
|
|
var response = helper.handler.handleRequest(request);
|
|
expect(response, isResponseFailure('0'));
|
|
}
|
|
|
|
Future<void> test_updateContent_changeOnDisk_duringOverride() async {
|
|
var helper = AnalysisTestHelper();
|
|
helper.createSingleFileProject('library A;');
|
|
await helper.onAnalysisComplete;
|
|
// update code
|
|
helper.sendContentChange(AddContentOverlay('library B;'));
|
|
// There should be no errors
|
|
await helper.onAnalysisComplete;
|
|
expect(helper.getTestErrors(), hasLength(0));
|
|
// Change file on disk, adding a syntax error.
|
|
helper.resourceProvider.modifyFile(helper.testFile, 'library lib');
|
|
// There should still be no errors (file should not have been reread).
|
|
await helper.onAnalysisComplete;
|
|
expect(helper.getTestErrors(), hasLength(0));
|
|
// Send a content change with a null content param--file should be
|
|
// reread from disk.
|
|
helper.sendContentChange(RemoveContentOverlay());
|
|
// There should be errors now.
|
|
await helper.onAnalysisComplete;
|
|
expect(helper.getTestErrors(), hasLength(1));
|
|
}
|
|
|
|
Future<void> test_updateContent_changeOnDisk_normal() async {
|
|
var helper = AnalysisTestHelper();
|
|
helper.createSingleFileProject('library A;');
|
|
await helper.onAnalysisComplete;
|
|
// There should be no errors
|
|
expect(helper.getTestErrors(), hasLength(0));
|
|
// Change file on disk, adding a syntax error.
|
|
helper.resourceProvider.modifyFile(helper.testFile, 'library lib');
|
|
// There should be errors now.
|
|
await pumpEventQueue();
|
|
await helper.onAnalysisComplete;
|
|
expect(helper.getTestErrors(), hasLength(1));
|
|
}
|
|
|
|
Future<void> test_updateContent_fullContent() async {
|
|
var helper = AnalysisTestHelper();
|
|
helper.createSingleFileProject('// empty');
|
|
await helper.onAnalysisComplete;
|
|
// no errors initially
|
|
var errors = helper.getTestErrors();
|
|
expect(errors, isEmpty);
|
|
// update code
|
|
helper.sendContentChange(AddContentOverlay('library lib'));
|
|
// wait, there is an error
|
|
await helper.onAnalysisComplete;
|
|
errors = helper.getTestErrors();
|
|
expect(errors, hasLength(1));
|
|
}
|
|
|
|
Future<void> test_updateContent_incremental() async {
|
|
var helper = AnalysisTestHelper();
|
|
var initialContent = 'library A;';
|
|
helper.createSingleFileProject(initialContent);
|
|
await helper.onAnalysisComplete;
|
|
// no errors initially
|
|
var errors = helper.getTestErrors();
|
|
expect(errors, isEmpty);
|
|
// Add the file to the cache
|
|
helper.sendContentChange(AddContentOverlay(initialContent));
|
|
// update code
|
|
helper.sendContentChange(ChangeContentOverlay(
|
|
[SourceEdit('library '.length, 'A;'.length, 'lib')]));
|
|
// wait, there is an error
|
|
await helper.onAnalysisComplete;
|
|
errors = helper.getTestErrors();
|
|
expect(errors, hasLength(1));
|
|
}
|
|
|
|
Future<void> test_updateContent_outOfRange_beyondEnd() {
|
|
return outOfRangeTest(SourceEdit(6, 6, 'foo'));
|
|
}
|
|
|
|
Future<void> test_updateContent_outOfRange_negativeLength() {
|
|
return outOfRangeTest(SourceEdit(3, -1, 'foo'));
|
|
}
|
|
|
|
Future<void> test_updateContent_outOfRange_negativeOffset() {
|
|
return outOfRangeTest(SourceEdit(-1, 3, 'foo'));
|
|
}
|
|
|
|
void test_updateOptions_invalid() {
|
|
var request = Request('0', ANALYSIS_REQUEST_UPDATE_OPTIONS, {
|
|
ANALYSIS_REQUEST_UPDATE_OPTIONS_OPTIONS: {'not-an-option': true}
|
|
});
|
|
var response = handler.handleRequest(request);
|
|
// Invalid options should be silently ignored.
|
|
expect(response, isResponseSuccess('0'));
|
|
}
|
|
|
|
void test_updateOptions_null() {
|
|
// null is allowed as a synonym for {}.
|
|
var request = Request('0', ANALYSIS_REQUEST_UPDATE_OPTIONS,
|
|
{ANALYSIS_REQUEST_UPDATE_OPTIONS_OPTIONS: null});
|
|
var response = handler.handleRequest(request);
|
|
expect(response, isResponseSuccess('0'));
|
|
}
|
|
|
|
Response testSetAnalysisRoots(List<String> included, List<String> excluded) {
|
|
var request =
|
|
AnalysisSetAnalysisRootsParams(included, excluded).toRequest('0');
|
|
return handler.handleRequest(request)!;
|
|
}
|
|
|
|
Future<void> xtest_getReachableSources_invalidSource() async {
|
|
// TODO(brianwilkerson) Re-enable this test if we re-enable the
|
|
// analysis.getReachableSources request.
|
|
newFile('/project/a.dart', content: 'import "b.dart";');
|
|
server.setAnalysisRoots('0', ['/project/'], []);
|
|
|
|
await server.onAnalysisComplete;
|
|
|
|
var request = AnalysisGetReachableSourcesParams('/does/not/exist.dart')
|
|
.toRequest('0');
|
|
var response = handler.handleRequest(request)!;
|
|
var error = response.error!;
|
|
expect(error.code, RequestErrorCode.GET_REACHABLE_SOURCES_INVALID_FILE);
|
|
}
|
|
|
|
Future<void> xtest_getReachableSources_validSources() async {
|
|
// TODO(brianwilkerson) Re-enable this test if we re-enable the
|
|
// analysis.getReachableSources request.
|
|
var fileA = newFile('/project/a.dart', content: 'import "b.dart";').path;
|
|
newFile('/project/b.dart');
|
|
|
|
server.setAnalysisRoots('0', ['/project/'], []);
|
|
|
|
await server.onAnalysisComplete;
|
|
|
|
var request = AnalysisGetReachableSourcesParams(fileA).toRequest('0');
|
|
var response = handler.handleRequest(request)!;
|
|
|
|
var json = response.toJson()[Response.RESULT] as Map<String, dynamic>;
|
|
|
|
// Sanity checks.
|
|
expect(json['sources'], hasLength(6));
|
|
expect(json['sources']['file:///project/a.dart'],
|
|
unorderedEquals(['dart:core', 'file:///project/b.dart']));
|
|
expect(json['sources']['file:///project/b.dart'], ['dart:core']);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class AnalysisDomainPubTest extends _AnalysisDomainTest {
|
|
String get testFilePath => '$testPackageLibPath/test.dart';
|
|
|
|
String get testPackageLibPath => '$testPackageRootPath/lib';
|
|
|
|
String get testPackageRootPath => '$workspaceRootPath/test';
|
|
|
|
String get workspaceRootPath => '/home';
|
|
|
|
Future<void> test_fileSystem_addFile_analysisOptions() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var b_path = '$testPackageLibPath/b.dart';
|
|
|
|
_createFilesWithErrors([a_path, b_path]);
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// Both a.dart and b.dart are analyzed.
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path, b_path],
|
|
notAnalyzed: [],
|
|
);
|
|
|
|
// Write the options file that excludes b.dart
|
|
newAnalysisOptionsYamlFile(testPackageRootPath, content: r'''
|
|
analyzer:
|
|
exclude:
|
|
- lib/b.dart
|
|
''');
|
|
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// Errors for all files were flushed, and only a.dart is reported again.
|
|
_assertFlushedResults([a_path, b_path]);
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path],
|
|
notAnalyzed: [b_path],
|
|
);
|
|
}
|
|
|
|
Future<void> test_fileSystem_addFile_analysisOptions_analysis() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var options_path = '$testPackageRootPath/analysis_options.yaml';
|
|
|
|
newFile(a_path, content: 'error');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// a.dart was analyzed
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path],
|
|
notAnalyzed: [options_path],
|
|
);
|
|
|
|
// Add 'analysis_options.yaml' that has an error.
|
|
newFile(options_path, content: '''
|
|
analyzer:
|
|
error:
|
|
''');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// Both files were analyzed.
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path, options_path],
|
|
notAnalyzed: [],
|
|
);
|
|
}
|
|
|
|
Future<void> test_fileSystem_addFile_androidManifestXml() async {
|
|
var path = '$testPackageRootPath/AndroidManifest.xml';
|
|
|
|
newFile('$testPackageLibPath/a.dart', content: '');
|
|
|
|
newAnalysisOptionsYamlFile(testPackageRootPath, content: '''
|
|
analyzer:
|
|
optional-checks:
|
|
chrome-os-manifest-checks: true
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
|
|
newFile(path, content: '<manifest/>');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// No touch-screen.
|
|
assertHasErrors(path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_addFile_dart() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
|
|
// We have to create the folder, otherwise there is nothing to watch.
|
|
newFolder(testPackageLibPath);
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// We don't have a.dart yet.
|
|
assertNoErrorsNotification(a_path);
|
|
|
|
_createFilesWithErrors([a_path]);
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We created a.dart, so it should be analyzed.
|
|
assertHasErrors(a_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_addFile_dart_dotFolder() async {
|
|
var a_path = '$projectPath/lib/.foo/a.dart';
|
|
var b_path = '$projectPath/lib/b.dart';
|
|
|
|
newFile(b_path, content: r'''
|
|
import '.foo/a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
createProject();
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We don't have a.dart, so the import cannot be resolved.
|
|
assertHasErrors(b_path);
|
|
|
|
newFile(a_path, content: r'''
|
|
class A {}
|
|
''');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// 'a.dart' is in a dot-folder, so excluded from analysis.
|
|
assertNoErrorsNotification(a_path);
|
|
|
|
// We added a.dart with `A`, so no errors.
|
|
assertNoErrors(b_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_addFile_dart_excluded() async {
|
|
var a_path = '$projectPath/lib/a.dart';
|
|
var b_path = '$projectPath/lib/b.dart';
|
|
|
|
newAnalysisOptionsYamlFile(projectPath, content: r'''
|
|
analyzer:
|
|
exclude:
|
|
- "**/a.dart"
|
|
''');
|
|
|
|
newFile(b_path, content: r'''
|
|
import 'a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
createProject();
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We don't have a.dart, so the import cannot be resolved.
|
|
assertHasErrors(b_path);
|
|
|
|
newFile(a_path, content: r'''
|
|
class A {}
|
|
''');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We excluded 'a.dart' from analysis, no errors notification for it.
|
|
assertNoErrorsNotification(a_path);
|
|
|
|
// We added a.dart with `A`, so no errors.
|
|
assertNoErrors(b_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_addFile_dotPackagesFile() async {
|
|
var aaaLibPath = '/packages/aaa/lib';
|
|
var a_path = '$aaaLibPath/a.dart';
|
|
|
|
newFile(a_path, content: '''
|
|
class A {}
|
|
''');
|
|
|
|
newFile(testFilePath, content: '''
|
|
import 'package:aaa/a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// We cannot resolve `package:aaa/a.dart`
|
|
assertHasErrors(testFilePath);
|
|
|
|
// Write `.packages`, recreate analysis contexts.
|
|
newDotPackagesFile(testPackageRootPath, content: '''
|
|
aaa:${toUriStr(aaaLibPath)}
|
|
''');
|
|
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We have `A` in 'package:aaa/a.dart', so no errors.
|
|
assertNoErrors(testFilePath);
|
|
|
|
// errors are not reported for packages
|
|
assertNoErrorsNotification(a_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_addFile_fixDataYaml() async {
|
|
var path = '$testPackageLibPath/fix_data.yaml';
|
|
|
|
newFile('$testPackageLibPath/a.dart', content: '');
|
|
|
|
// Make sure that it is a package.
|
|
writePackageConfig(
|
|
'$testPackageRootPath/.dart_tool/package_config.json',
|
|
PackageConfigFileBuilder(),
|
|
);
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
|
|
// No `fix_data.yaml` to analyze yet.
|
|
assertNoErrorsNotification(path);
|
|
|
|
// Create it, will be analyzed.
|
|
newFile(path, content: '0: 1');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// And it has errors.
|
|
assertHasErrors(path);
|
|
|
|
// We don't recreate analysis contexts.
|
|
_assertFlushedResults([]);
|
|
}
|
|
|
|
Future<void> test_fileSystem_addFile_packageConfigJsonFile() async {
|
|
var aaaRootPath = '/packages/aaa';
|
|
var a_path = '$aaaRootPath/lib/a.dart';
|
|
|
|
newFile(a_path, content: '''
|
|
class A {}
|
|
''');
|
|
|
|
newFile(testFilePath, content: '''
|
|
import 'package:aaa/a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// We cannot resolve `package:aaa/a.dart`
|
|
assertHasErrors(testFilePath);
|
|
|
|
// Write `package_config.json`, recreate analysis contexts.
|
|
writePackageConfig(
|
|
'$testPackageRootPath/.dart_tool/package_config.json',
|
|
PackageConfigFileBuilder()..add(name: 'aaa', rootPath: aaaRootPath),
|
|
);
|
|
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We have `A` in 'package:aaa/a.dart', so no errors.
|
|
assertNoErrors(testFilePath);
|
|
|
|
// errors are not reported for packages
|
|
assertNoErrorsNotification(a_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_addFile_pubspec_analysis() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var pubspec_path = '$testPackageRootPath/pubspec.yaml';
|
|
|
|
newFile(a_path, content: 'error');
|
|
|
|
// Write an empty file to force a new analysis context.
|
|
// We look for `pubspec.yaml` files only in analysis context roots.
|
|
newAnalysisOptionsYamlFile(testPackageRootPath, content: '');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// a.dart was analyzed
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path],
|
|
notAnalyzed: [pubspec_path],
|
|
);
|
|
|
|
// Add a non-Dart file that we know how to analyze.
|
|
newFile(pubspec_path, content: '''
|
|
name: sample
|
|
dependencies: true
|
|
''');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// Both files were analyzed.
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path, pubspec_path],
|
|
notAnalyzed: [],
|
|
);
|
|
}
|
|
|
|
Future<void> test_fileSystem_addFile_unrelated() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var unrelated_path = '$testPackageRootPath/unrelated.txt';
|
|
|
|
newFile(a_path, content: 'error');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// a.dart was analyzed
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path],
|
|
notAnalyzed: [unrelated_path],
|
|
);
|
|
|
|
// Add an unrelated file, no analysis.
|
|
newFile(unrelated_path, content: 'anything');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// No analysis.
|
|
_assertFlushedResults([]);
|
|
_assertAnalyzedFiles(hasErrors: [], notAnalyzed: [a_path]);
|
|
}
|
|
|
|
Future<void> test_fileSystem_changeFile_analysisOptions() async {
|
|
var options_path = '$testPackageRootPath/analysis_options.yaml';
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var b_path = '$testPackageLibPath/b.dart';
|
|
var c_path = '$testPackageLibPath/c.dart';
|
|
|
|
_createFilesWithErrors([a_path, b_path, c_path]);
|
|
|
|
// Exclude b.dart from analysis.
|
|
newFile(options_path, content: r'''
|
|
analyzer:
|
|
exclude:
|
|
- lib/b.dart
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// Only a.dart is analyzed, because b.dart is excluded.
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path, c_path],
|
|
notAnalyzed: [b_path],
|
|
);
|
|
|
|
// Exclude c.dart from analysis.
|
|
newFile(options_path, content: r'''
|
|
analyzer:
|
|
exclude:
|
|
- lib/c.dart
|
|
''');
|
|
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// Errors for all files were flushed, a.dart and b.dart analyzed.
|
|
_assertFlushedResults([options_path, a_path, c_path]);
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path, b_path],
|
|
noErrors: [options_path],
|
|
notAnalyzed: [c_path],
|
|
);
|
|
}
|
|
|
|
Future<void> test_fileSystem_changeFile_androidManifestXml() async {
|
|
var path = '$testPackageRootPath/AndroidManifest.xml';
|
|
|
|
newFile('$testPackageLibPath/a.dart', content: '');
|
|
|
|
// Has an error - no touch screen.
|
|
newFile(path, content: '<manifest/>');
|
|
|
|
newAnalysisOptionsYamlFile(testPackageRootPath, content: '''
|
|
analyzer:
|
|
optional-checks:
|
|
chrome-os-manifest-checks: true
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
|
|
// Forget and check that we did.
|
|
forgetReceivedErrors();
|
|
assertNoErrorsNotification(path);
|
|
|
|
// Update the file, so analyze it.
|
|
newFile(path, content: '<manifest/>');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// An error was reported.
|
|
assertHasErrors(path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_changeFile_dart() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var b_path = '$testPackageLibPath/b.dart';
|
|
|
|
newFile(a_path, content: r'''
|
|
class A2 {}
|
|
''');
|
|
|
|
newFile(b_path, content: r'''
|
|
import 'a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
assertNoErrors(a_path);
|
|
assertHasErrors(b_path);
|
|
forgetReceivedErrors();
|
|
|
|
// Update a.dart so that b.dart has no error.
|
|
newFile(a_path, content: 'class A {}');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// The update of a.dart fixed the error in b.dart
|
|
assertNoErrors(a_path);
|
|
assertNoErrors(b_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_changeFile_dart_dotFolder() async {
|
|
var a_path = '$testPackageLibPath/.foo/a.dart';
|
|
var b_path = '$testPackageLibPath/b.dart';
|
|
|
|
newFile(a_path, content: r'''
|
|
class B {}
|
|
''');
|
|
|
|
newFile(b_path, content: r'''
|
|
import '.foo/a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// 'a.dart' is in a dot-folder, so excluded from analysis.
|
|
assertNoErrorsNotification(a_path);
|
|
|
|
// We have `B`, not `A`, in a.dart, so has errors.
|
|
assertHasErrors(b_path);
|
|
|
|
newFile(a_path, content: r'''
|
|
class A {}
|
|
''');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// 'a.dart' is in a dot-folder, so excluded from analysis.
|
|
assertNoErrorsNotification(a_path);
|
|
|
|
// We changed a.dart, to have `A`, so no errors.
|
|
assertNoErrors(b_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_changeFile_dart_excluded() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var b_path = '$testPackageLibPath/b.dart';
|
|
|
|
newAnalysisOptionsYamlFile(testPackageRootPath, content: r'''
|
|
analyzer:
|
|
exclude:
|
|
- "**/a.dart"
|
|
''');
|
|
|
|
newFile(a_path, content: r'''
|
|
class B {}
|
|
''');
|
|
|
|
newFile(b_path, content: r'''
|
|
import 'a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We excluded 'a.dart' from analysis, no errors notification for it.
|
|
assertNoErrorsNotification(a_path);
|
|
|
|
// We have `B`, not `A`, in a.dart, so has errors.
|
|
assertHasErrors(b_path);
|
|
|
|
newFile(a_path, content: r'''
|
|
class A {}
|
|
''');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We changed a.dart, to have `A`, so no errors.
|
|
assertNoErrors(b_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_changeFile_dotPackagesFile() async {
|
|
var aaaLibPath = '/packages/aaa/lib';
|
|
var a_path = '$aaaLibPath/a.dart';
|
|
|
|
newFile(a_path, content: '''
|
|
class A {}
|
|
''');
|
|
|
|
// Write `.packages` empty, without `package:aaa`.
|
|
newDotPackagesFile(testPackageRootPath, content: '');
|
|
|
|
newFile(testFilePath, content: '''
|
|
import 'package:aaa/a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// We cannot resolve `package:aaa/a.dart`
|
|
assertHasErrors(testFilePath);
|
|
|
|
// Write `.packages`, recreate analysis contexts.
|
|
newDotPackagesFile(testPackageRootPath, content: '''
|
|
aaa:${toUriStr(aaaLibPath)}
|
|
''');
|
|
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We have `A` in 'package:aaa/a.dart', so no errors.
|
|
assertNoErrors(testFilePath);
|
|
|
|
// errors are not reported for packages
|
|
assertNoErrorsNotification(a_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_changeFile_fixDataYaml() async {
|
|
var path = '$testPackageLibPath/fix_data.yaml';
|
|
|
|
newFile('$testPackageLibPath/a.dart', content: '');
|
|
|
|
// Make sure that it is a package.
|
|
writePackageConfig(
|
|
'$testPackageRootPath/.dart_tool/package_config.json',
|
|
PackageConfigFileBuilder(),
|
|
);
|
|
|
|
// This file has an error.
|
|
newFile(path, content: '0: 1');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
|
|
// The file was analyzed.
|
|
assertHasErrors(path);
|
|
|
|
// Replace with the context that does not have errors.
|
|
newFile(path, content: r'''
|
|
version: 1
|
|
transforms: []
|
|
''');
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// And it has errors.
|
|
assertNoErrors(path);
|
|
|
|
// We don't recreate analysis contexts.
|
|
_assertFlushedResults([]);
|
|
}
|
|
|
|
Future<void> test_fileSystem_changeFile_packageConfigJsonFile() async {
|
|
var aaaRootPath = '/packages/aaa';
|
|
var a_path = '$aaaRootPath/lib/a.dart';
|
|
|
|
newFile(a_path, content: '''
|
|
class A {}
|
|
''');
|
|
|
|
// Write the empty file, without `package:aaa`.
|
|
writePackageConfig(
|
|
'$testPackageRootPath/.dart_tool/package_config.json',
|
|
PackageConfigFileBuilder(),
|
|
);
|
|
|
|
newFile(testFilePath, content: '''
|
|
import 'package:aaa/a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// We cannot resolve `package:aaa/a.dart`
|
|
assertHasErrors(testFilePath);
|
|
|
|
// Write `package_config.json`, recreate analysis contexts.
|
|
writePackageConfig(
|
|
'$testPackageRootPath/.dart_tool/package_config.json',
|
|
PackageConfigFileBuilder()..add(name: 'aaa', rootPath: aaaRootPath),
|
|
);
|
|
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We have `A` in 'package:aaa/a.dart', so no errors.
|
|
assertNoErrors(testFilePath);
|
|
|
|
// errors are not reported for packages
|
|
assertNoErrorsNotification(a_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_deleteFile_analysisOptions() async {
|
|
var options_path = '$testPackageRootPath/analysis_options.yaml';
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var b_path = '$testPackageLibPath/b.dart';
|
|
|
|
_createFilesWithErrors([a_path, b_path]);
|
|
|
|
// Exclude b.dart from analysis.
|
|
newFile(options_path, content: r'''
|
|
analyzer:
|
|
exclude:
|
|
- lib/b.dart
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// Only a.dart is analyzed, because b.dart is excluded.
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path],
|
|
notAnalyzed: [b_path],
|
|
);
|
|
|
|
// Delete the options file.
|
|
deleteFile(options_path);
|
|
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// Errors for a.dart were flushed, a.dart and b.dart analyzed.
|
|
_assertFlushedResults([options_path, a_path]);
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path, b_path],
|
|
notAnalyzed: [options_path],
|
|
);
|
|
}
|
|
|
|
Future<void> test_fileSystem_deleteFile_androidManifestXml() async {
|
|
var path = '$testPackageRootPath/AndroidManifest.xml';
|
|
|
|
newFile('$testPackageLibPath/a.dart', content: '');
|
|
|
|
// Has an error - no touch screen.
|
|
newFile(path, content: '<manifest/>');
|
|
|
|
newAnalysisOptionsYamlFile(testPackageRootPath, content: '''
|
|
analyzer:
|
|
optional-checks:
|
|
chrome-os-manifest-checks: true
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
|
|
// An error was reported.
|
|
_assertAnalyzedFiles(hasErrors: [path], notAnalyzed: []);
|
|
|
|
// Delete the file.
|
|
deleteFile(path);
|
|
await pumpEventQueue();
|
|
|
|
// We received a flush notification.
|
|
_assertAnalyzedFiles(hasErrors: [], notAnalyzed: [path]);
|
|
}
|
|
|
|
Future<void> test_fileSystem_deleteFile_dart() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
|
|
_createFilesWithErrors([a_path]);
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// a.dart was analyzed
|
|
assertHasErrors(a_path);
|
|
|
|
deleteFile(a_path);
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We deleted a.dart, its errors should be flushed.
|
|
_assertFlushedResults([a_path]);
|
|
assertNoErrorsNotification(a_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_deleteFile_dart_excluded() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var b_path = '$testPackageLibPath/b.dart';
|
|
|
|
newAnalysisOptionsYamlFile(testPackageRootPath, content: r'''
|
|
analyzer:
|
|
exclude:
|
|
- "**/a.dart"
|
|
''');
|
|
|
|
newFile(a_path, content: r'''
|
|
class A {}
|
|
''');
|
|
|
|
newFile(b_path, content: r'''
|
|
import 'a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We excluded 'a.dart' from analysis, no errors notification for it.
|
|
assertNoErrorsNotification(a_path);
|
|
|
|
// We have `A` in a.dart, so no errors.
|
|
assertNoErrors(b_path);
|
|
|
|
deleteFile(a_path);
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We deleted a.dart, so `A` cannot be resolved.
|
|
assertHasErrors(b_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_deleteFile_dotPackagesFile() async {
|
|
var aaaLibPath = '/packages/aaa/lib';
|
|
var a_path = '$aaaLibPath/a.dart';
|
|
|
|
newFile(a_path, content: '''
|
|
class A {}
|
|
''');
|
|
|
|
newDotPackagesFile(testPackageRootPath, content: '''
|
|
aaa:${toUriStr(aaaLibPath)}
|
|
''');
|
|
|
|
newFile(testFilePath, content: '''
|
|
import 'package:aaa/a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// We have `A` in 'package:aaa/a.dart', so no errors.
|
|
assertNoErrors(testFilePath);
|
|
|
|
// Write `.packages`, recreate analysis contexts.
|
|
deleteFile('$testPackageRootPath/.packages');
|
|
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We cannot resolve `package:aaa/a.dart`
|
|
assertHasErrors(testFilePath);
|
|
|
|
// errors are not reported for packages
|
|
assertNoErrorsNotification(a_path);
|
|
}
|
|
|
|
Future<void> test_fileSystem_deleteFile_fixDataYaml() async {
|
|
var path = '$testPackageLibPath/fix_data.yaml';
|
|
|
|
newFile('$testPackageLibPath/a.dart', content: '');
|
|
|
|
// Make sure that it is a package.
|
|
writePackageConfig(
|
|
'$testPackageRootPath/.dart_tool/package_config.json',
|
|
PackageConfigFileBuilder(),
|
|
);
|
|
|
|
// This file has an error.
|
|
newFile(path, content: '0: 1');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
|
|
// The file was analyzed.
|
|
_assertAnalyzedFiles(hasErrors: [path], notAnalyzed: []);
|
|
|
|
// Delete the file.
|
|
deleteFile(path);
|
|
await pumpEventQueue();
|
|
|
|
// We received a flush notification.
|
|
_assertAnalyzedFiles(hasErrors: [], notAnalyzed: [path]);
|
|
}
|
|
|
|
Future<void> test_fileSystem_deleteFile_packageConfigJsonFile() async {
|
|
var aaaRootPath = '/packages/aaa';
|
|
var a_path = '$aaaRootPath/lib/a.dart';
|
|
|
|
newFile(a_path, content: '''
|
|
class A {}
|
|
''');
|
|
|
|
// Write the empty file, without `package:aaa`.
|
|
writePackageConfig(
|
|
'$testPackageRootPath/.dart_tool/package_config.json',
|
|
PackageConfigFileBuilder()..add(name: 'aaa', rootPath: aaaRootPath),
|
|
);
|
|
|
|
newFile(testFilePath, content: '''
|
|
import 'package:aaa/a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// We have `A` in 'package:aaa/a.dart', so no errors.
|
|
assertNoErrors(testFilePath);
|
|
|
|
// Delete `package_config.json`, recreate analysis contexts.
|
|
deleteFile(
|
|
'$testPackageRootPath/.dart_tool/package_config.json',
|
|
);
|
|
|
|
await pumpEventQueue();
|
|
await server.onAnalysisComplete;
|
|
|
|
// We cannot resolve 'package:aaa/a.dart', so errors.
|
|
assertHasErrors(testFilePath);
|
|
|
|
// errors are not reported for packages
|
|
assertNoErrorsNotification(a_path);
|
|
}
|
|
|
|
Future<void> test_setRoots_dotPackagesFile() async {
|
|
var aaaLibPath = '/packages/aaa/lib';
|
|
var a_path = '$aaaLibPath/a.dart';
|
|
|
|
newFile(a_path, content: '''
|
|
class A {}
|
|
''');
|
|
|
|
newDotPackagesFile(testPackageRootPath, content: '''
|
|
aaa:${toUriStr(aaaLibPath)}
|
|
''');
|
|
|
|
newFile(testFilePath, content: '''
|
|
import 'package:aaa/a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
// create project and wait for analysis
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// We have `A` in 'package:aaa/a.dart', so no errors.
|
|
assertNoErrors(testFilePath);
|
|
|
|
// errors are not reported for packages
|
|
assertNoErrorsNotification(a_path);
|
|
}
|
|
|
|
Future<void> test_setRoots_includedFile() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var b_path = '$testPackageLibPath/b.dart';
|
|
|
|
_createFilesWithErrors([a_path, b_path]);
|
|
|
|
setRoots(included: [a_path], excluded: []);
|
|
|
|
// Only a.dart is included, so b.dart is not analyzed.
|
|
await server.onAnalysisComplete;
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path],
|
|
notAnalyzed: [b_path],
|
|
);
|
|
}
|
|
|
|
Future<void> test_setRoots_includedFile_setRoots() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var b_path = '$testPackageLibPath/b.dart';
|
|
|
|
_createFilesWithErrors([a_path, b_path]);
|
|
|
|
// Include only single file.
|
|
setRoots(included: [a_path], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// So, only a.dart is analyzed, and b.dart is not.
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path],
|
|
notAnalyzed: [b_path],
|
|
);
|
|
|
|
// Include the folder that contains both a.dart and b.dart
|
|
setRoots(included: [testPackageRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// So, both a.dart and b.dart are analyzed.
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path, b_path],
|
|
notAnalyzed: [],
|
|
);
|
|
}
|
|
|
|
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,
|
|
]);
|
|
|
|
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.
|
|
_assertAnalyzedFiles(hasErrors: [
|
|
includedFile,
|
|
includedFolderFile1,
|
|
includedFolderFile2,
|
|
], notAnalyzed: [
|
|
notIncludedFile,
|
|
]);
|
|
}
|
|
|
|
Future<void> test_setRoots_includedFolder_analysisOptions_exclude() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var b_path = '$testPackageLibPath/b.dart';
|
|
|
|
newAnalysisOptionsYamlFile(testPackageRootPath, content: '''
|
|
analyzer:
|
|
exclude:
|
|
- "**/b.dart"
|
|
''');
|
|
|
|
_createFilesWithErrors([a_path, b_path]);
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
|
|
// b.dart is excluded using the options file.
|
|
await server.onAnalysisComplete;
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path],
|
|
notAnalyzed: [b_path],
|
|
);
|
|
}
|
|
|
|
@FailingTest(reason: 'Not implemented in ContextLocator')
|
|
Future<void> test_setRoots_includedFolder_excludedFile() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var excluded_path = '$testPackageRootPath/excluded/b.dart';
|
|
|
|
_createFilesWithErrors([a_path, excluded_path]);
|
|
|
|
setRoots(
|
|
included: [workspaceRootPath],
|
|
excluded: [excluded_path],
|
|
);
|
|
await server.onAnalysisComplete;
|
|
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path],
|
|
notAnalyzed: [excluded_path],
|
|
);
|
|
}
|
|
|
|
Future<void> test_setRoots_includedFolder_excludedFolder() async {
|
|
var a_path = '$testPackageLibPath/a.dart';
|
|
var excluded_path = '$testPackageRootPath/excluded/b.dart';
|
|
|
|
_createFilesWithErrors([a_path, excluded_path]);
|
|
|
|
setRoots(
|
|
included: [workspaceRootPath],
|
|
excluded: ['$testPackageRootPath/excluded'],
|
|
);
|
|
await server.onAnalysisComplete;
|
|
|
|
// a.dart is analyzed, but b.dart is in the excluded folder.
|
|
_assertAnalyzedFiles(
|
|
hasErrors: [a_path],
|
|
notAnalyzed: [excluded_path],
|
|
);
|
|
}
|
|
|
|
Future<void> test_setRoots_notDartFile_androidManifestXml() async {
|
|
var path = '$testPackageRootPath/AndroidManifest.xml';
|
|
|
|
newFile('$testPackageLibPath/a.dart', content: '');
|
|
|
|
newAnalysisOptionsYamlFile(testPackageRootPath, content: '''
|
|
analyzer:
|
|
optional-checks:
|
|
chrome-os-manifest-checks: true
|
|
''');
|
|
|
|
newFile(path, content: '<manifest/>');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
|
|
// No touch-screen.
|
|
assertHasErrors(path);
|
|
}
|
|
|
|
Future<void> test_setRoots_notDartFile_fixDataYaml() async {
|
|
var path = '$testPackageLibPath/fix_data.yaml';
|
|
|
|
// Make sure that it is a package.
|
|
writePackageConfig(
|
|
'$testPackageRootPath/.dart_tool/package_config.json',
|
|
PackageConfigFileBuilder(),
|
|
);
|
|
|
|
// So, `lib/fix_data.yaml` will be analyzed.
|
|
newFile(path, content: '0: 1');
|
|
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
|
|
assertHasErrors(path);
|
|
}
|
|
|
|
Future<void> test_setRoots_packageConfigJsonFile() async {
|
|
var aaaRootPath = '/packages/aaa';
|
|
var a_path = '$aaaRootPath/lib/a.dart';
|
|
|
|
newFile(a_path, content: '''
|
|
class A {}
|
|
''');
|
|
|
|
writePackageConfig(
|
|
'$testPackageRootPath/.dart_tool/package_config.json',
|
|
PackageConfigFileBuilder()..add(name: 'aaa', rootPath: aaaRootPath),
|
|
);
|
|
|
|
newFile(testFilePath, content: '''
|
|
import 'package:aaa/a.dart';
|
|
void f(A a) {}
|
|
''');
|
|
|
|
// create project and wait for analysis
|
|
setRoots(included: [workspaceRootPath], excluded: []);
|
|
await server.onAnalysisComplete;
|
|
|
|
// We have `A` in 'package:aaa/a.dart', so no errors.
|
|
assertNoErrors(testFilePath);
|
|
|
|
// errors are not reported for packages
|
|
assertNoErrorsNotification(a_path);
|
|
}
|
|
}
|
|
|
|
/// A helper to test 'analysis.*' requests.
|
|
class AnalysisTestHelper with ResourceProviderMixin {
|
|
late MockServerChannel serverChannel;
|
|
late AnalysisServer server;
|
|
late AnalysisDomainHandler handler;
|
|
|
|
Map<AnalysisService, List<String>> analysisSubscriptions = {};
|
|
|
|
Map<String, List<AnalysisError>> filesErrors = {};
|
|
Map<String, List<HighlightRegion>> filesHighlights = {};
|
|
Map<String, List<NavigationRegion>> filesNavigation = {};
|
|
|
|
late String projectPath;
|
|
late String testFile;
|
|
late String testCode;
|
|
|
|
AnalysisTestHelper() {
|
|
projectPath = convertPath('/project');
|
|
testFile = convertPath('/project/bin/test.dart');
|
|
serverChannel = MockServerChannel();
|
|
// Create an SDK in the mock file system.
|
|
MockSdk(resourceProvider: resourceProvider);
|
|
server = AnalysisServer(
|
|
serverChannel,
|
|
resourceProvider,
|
|
AnalysisServerOptions(),
|
|
DartSdkManager(convertPath('/sdk')),
|
|
CrashReportingAttachmentsBuilder.empty,
|
|
InstrumentationService.NULL_SERVICE);
|
|
handler = AnalysisDomainHandler(server);
|
|
// listen for notifications
|
|
var notificationStream = serverChannel.notificationController.stream;
|
|
notificationStream.listen((Notification notification) {
|
|
if (notification.event == ANALYSIS_NOTIFICATION_ERRORS) {
|
|
var decoded = AnalysisErrorsParams.fromNotification(notification);
|
|
filesErrors[decoded.file] = decoded.errors;
|
|
}
|
|
if (notification.event == ANALYSIS_NOTIFICATION_HIGHLIGHTS) {
|
|
var params = AnalysisHighlightsParams.fromNotification(notification);
|
|
filesHighlights[params.file] = params.regions;
|
|
}
|
|
if (notification.event == ANALYSIS_NOTIFICATION_NAVIGATION) {
|
|
var params = AnalysisNavigationParams.fromNotification(notification);
|
|
filesNavigation[params.file] = params.regions;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Returns a [Future] that completes when the server's analysis is complete.
|
|
Future get onAnalysisComplete {
|
|
return server.onAnalysisComplete;
|
|
}
|
|
|
|
void addAnalysisSubscription(AnalysisService service, String file) {
|
|
// add file to subscription
|
|
var files = analysisSubscriptions[service];
|
|
if (files == null) {
|
|
files = <String>[];
|
|
analysisSubscriptions[service] = files;
|
|
}
|
|
files.add(file);
|
|
// set subscriptions
|
|
var request =
|
|
AnalysisSetSubscriptionsParams(analysisSubscriptions).toRequest('0');
|
|
handleSuccessfulRequest(request);
|
|
}
|
|
|
|
void addAnalysisSubscriptionHighlights(String file) {
|
|
addAnalysisSubscription(AnalysisService.HIGHLIGHTS, file);
|
|
}
|
|
|
|
void addAnalysisSubscriptionNavigation(String file) {
|
|
addAnalysisSubscription(AnalysisService.NAVIGATION, file);
|
|
}
|
|
|
|
/// Creates an empty project `/project`.
|
|
void createEmptyProject() {
|
|
newFolder(projectPath);
|
|
var request =
|
|
AnalysisSetAnalysisRootsParams([projectPath], []).toRequest('0');
|
|
handleSuccessfulRequest(request);
|
|
}
|
|
|
|
/// Creates a project with a single Dart file `/project/bin/test.dart` with
|
|
/// the given [code].
|
|
void createSingleFileProject(code) {
|
|
testCode = _getCodeString(code);
|
|
newFolder(projectPath);
|
|
newFile(testFile, content: testCode);
|
|
var request =
|
|
AnalysisSetAnalysisRootsParams([projectPath], []).toRequest('0');
|
|
handleSuccessfulRequest(request);
|
|
}
|
|
|
|
/// Returns the offset of [search] in [testCode].
|
|
/// Fails if not found.
|
|
int findOffset(String search) {
|
|
var offset = testCode.indexOf(search);
|
|
expect(offset, isNot(-1));
|
|
return offset;
|
|
}
|
|
|
|
/// Returns [AnalysisError]s recorded for the given [file].
|
|
/// May be empty, but not `null`.
|
|
List<AnalysisError> getErrors(String file) {
|
|
var errors = filesErrors[file];
|
|
if (errors != null) {
|
|
return errors;
|
|
}
|
|
return <AnalysisError>[];
|
|
}
|
|
|
|
/// Returns highlights recorded for the given [file].
|
|
/// May be empty, but not `null`.
|
|
List<HighlightRegion> getHighlights(String file) {
|
|
var highlights = filesHighlights[file];
|
|
if (highlights != null) {
|
|
return highlights;
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/// Returns navigation regions recorded for the given [file].
|
|
/// May be empty, but not `null`.
|
|
List<NavigationRegion> getNavigation(String file) {
|
|
var navigation = filesNavigation[file];
|
|
if (navigation != null) {
|
|
return navigation;
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/// Returns [AnalysisError]s recorded for the [testFile].
|
|
/// May be empty, but not `null`.
|
|
List<AnalysisError> getTestErrors() {
|
|
return getErrors(testFile);
|
|
}
|
|
|
|
/// Returns highlights recorded for the given [testFile].
|
|
/// May be empty, but not `null`.
|
|
List<HighlightRegion> getTestHighlights() {
|
|
return getHighlights(testFile);
|
|
}
|
|
|
|
/// Returns navigation information recorded for the given [testFile].
|
|
/// May be empty, but not `null`.
|
|
List<NavigationRegion> getTestNavigation() {
|
|
return getNavigation(testFile);
|
|
}
|
|
|
|
/// Validates that the given [request] is handled successfully.
|
|
void handleSuccessfulRequest(Request request) {
|
|
var response = handler.handleRequest(request);
|
|
expect(response, isResponseSuccess('0'));
|
|
}
|
|
|
|
/// Send an `updateContent` request for [testFile].
|
|
void sendContentChange(dynamic contentChange) {
|
|
var request =
|
|
AnalysisUpdateContentParams({testFile: contentChange}).toRequest('0');
|
|
handleSuccessfulRequest(request);
|
|
}
|
|
|
|
/// Stops the associated server.
|
|
void stopServer() {
|
|
server.done();
|
|
}
|
|
|
|
static String _getCodeString(code) {
|
|
if (code is List<String>) {
|
|
code = code.join('\n');
|
|
}
|
|
return code as String;
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class SetSubscriptionsTest extends AbstractAnalysisTest {
|
|
Map<String, List<HighlightRegion>> filesHighlights = {};
|
|
|
|
final Completer<void> _resultsAvailable = Completer();
|
|
|
|
@override
|
|
void processNotification(Notification notification) {
|
|
if (notification.event == ANALYSIS_NOTIFICATION_HIGHLIGHTS) {
|
|
var params = AnalysisHighlightsParams.fromNotification(notification);
|
|
filesHighlights[params.file] = params.regions;
|
|
_resultsAvailable.complete();
|
|
}
|
|
}
|
|
|
|
Future<void> test_afterAnalysis() async {
|
|
addTestFile('int V = 42;');
|
|
createProject();
|
|
// wait for analysis, no results initially
|
|
await waitForTasksFinished();
|
|
expect(filesHighlights[testFile], isNull);
|
|
// subscribe
|
|
addAnalysisSubscription(AnalysisService.HIGHLIGHTS, testFile);
|
|
await _resultsAvailable.future;
|
|
// there are results
|
|
expect(filesHighlights[testFile], isNotEmpty);
|
|
}
|
|
|
|
Future<void> test_afterAnalysis_noSuchFile() async {
|
|
var file = convertPath('/no-such-file.dart');
|
|
addTestFile('// no matter');
|
|
createProject();
|
|
// wait for analysis, no results initially
|
|
await waitForTasksFinished();
|
|
expect(filesHighlights[testFile], isNull);
|
|
// subscribe
|
|
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', content: '''
|
|
library lib_a;
|
|
class A {}
|
|
''').path;
|
|
newDotPackagesFile('/project', content: 'pkgA:file:///packages/pkgA/lib');
|
|
//
|
|
addTestFile('''
|
|
import 'package:pkgA/libA.dart';
|
|
main() {
|
|
new A();
|
|
}
|
|
''');
|
|
createProject();
|
|
// wait for analysis, no results initially
|
|
await waitForTasksFinished();
|
|
expect(filesHighlights[pkgFile], isNull);
|
|
// subscribe
|
|
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', content: '''
|
|
library lib_a;
|
|
class A {}
|
|
''').path;
|
|
newFile('$pkgA/lib/libB.dart', content: '''
|
|
import 'package:pkgA/libA.dart';
|
|
main() {
|
|
new A();
|
|
}
|
|
''');
|
|
// add 'pkgA' and 'pkgB' as projects
|
|
newFolder(projectPath);
|
|
handleSuccessfulRequest(
|
|
AnalysisSetAnalysisRootsParams([pkgA, pkgB], []).toRequest('0'));
|
|
// wait for analysis, no results initially
|
|
await waitForTasksFinished();
|
|
expect(filesHighlights[pkgFileA], isNull);
|
|
// subscribe
|
|
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', content: '''
|
|
library lib_a;
|
|
class A {}
|
|
''').path;
|
|
newDotPackagesFile('/project', content: 'pkgA:/packages/pkgA/lib');
|
|
//
|
|
addTestFile('// no "pkgA" reference');
|
|
createProject();
|
|
// wait for analysis, no results initially
|
|
await waitForTasksFinished();
|
|
expect(filesHighlights[pkgFile], isNull);
|
|
// make it a priority file, so make analyzable
|
|
server.setPriorityFiles('0', [pkgFile]);
|
|
// subscribe
|
|
addAnalysisSubscription(AnalysisService.HIGHLIGHTS, pkgFile);
|
|
await _resultsAvailable.future;
|
|
// there are results
|
|
expect(filesHighlights[pkgFile], isNotEmpty);
|
|
}
|
|
|
|
Future<void> test_afterAnalysis_sdkFile() async {
|
|
var file = convertPath('/sdk/lib/core/core.dart');
|
|
addTestFile('// no matter');
|
|
createProject();
|
|
// wait for analysis, no results initially
|
|
await waitForTasksFinished();
|
|
expect(filesHighlights[file], isNull);
|
|
// subscribe
|
|
addAnalysisSubscription(AnalysisService.HIGHLIGHTS, file);
|
|
await _resultsAvailable.future;
|
|
// there are results
|
|
expect(filesHighlights[file], isNotEmpty);
|
|
}
|
|
|
|
Future<void> test_beforeAnalysis() async {
|
|
addTestFile('int V = 42;');
|
|
createProject();
|
|
// subscribe
|
|
addAnalysisSubscription(AnalysisService.HIGHLIGHTS, testFile);
|
|
// wait for analysis
|
|
await waitForTasksFinished();
|
|
expect(filesHighlights[testFile], isNotEmpty);
|
|
}
|
|
|
|
Future<void> test_sentToPlugins() async {
|
|
addTestFile('int V = 42;');
|
|
createProject();
|
|
// subscribe
|
|
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]);
|
|
}
|
|
}
|
|
|
|
class _AnalysisDomainTest extends AbstractAnalysisTest {
|
|
final Map<String, List<AnalysisError>> filesErrors = {};
|
|
|
|
/// The files for which `analysis.flushResults` was received.
|
|
final List<String> flushResults = [];
|
|
|
|
void assertHasErrors(String path) {
|
|
path = convertPath(path);
|
|
expect(filesErrors[path], isNotEmpty, reason: path);
|
|
}
|
|
|
|
void assertNoErrors(String path) {
|
|
path = convertPath(path);
|
|
expect(filesErrors[path], isEmpty, reason: path);
|
|
}
|
|
|
|
void assertNoErrorsNotification(String path) {
|
|
path = convertPath(path);
|
|
expect(filesErrors[path], isNull, reason: path);
|
|
}
|
|
|
|
void forgetReceivedErrors() {
|
|
filesErrors.clear();
|
|
}
|
|
|
|
@override
|
|
void processNotification(Notification notification) {
|
|
if (notification.event == ANALYSIS_NOTIFICATION_FLUSH_RESULTS) {
|
|
var decoded = AnalysisFlushResultsParams.fromNotification(notification);
|
|
flushResults.addAll(decoded.files);
|
|
decoded.files.forEach(filesErrors.remove);
|
|
}
|
|
if (notification.event == ANALYSIS_NOTIFICATION_ERRORS) {
|
|
var decoded = AnalysisErrorsParams.fromNotification(notification);
|
|
filesErrors[decoded.file] = decoded.errors;
|
|
}
|
|
}
|
|
|
|
void writePackageConfig(String path, PackageConfigFileBuilder config) {
|
|
newFile(path, content: config.toContent(toUriStr: toUriStr));
|
|
}
|
|
|
|
void _assertAnalyzedFiles({
|
|
required List<String> hasErrors,
|
|
List<String> noErrors = const [],
|
|
required List<String> notAnalyzed,
|
|
}) {
|
|
for (var path in hasErrors) {
|
|
assertHasErrors(path);
|
|
}
|
|
|
|
for (var path in noErrors) {
|
|
assertNoErrors(path);
|
|
}
|
|
|
|
for (var path in notAnalyzed) {
|
|
assertNoErrorsNotification(path);
|
|
}
|
|
|
|
filesErrors.clear();
|
|
}
|
|
|
|
void _assertFlushedResults(List<String> paths) {
|
|
var convertedPaths = paths.map(convertPath).toList();
|
|
expect(flushResults, unorderedEquals(convertedPaths));
|
|
flushResults.clear();
|
|
}
|
|
|
|
/// Create files with a content that has a compile time error.
|
|
/// So, when analyzed, these files will satisfy [assertHasErrors].
|
|
void _createFilesWithErrors(List<String> paths) {
|
|
for (var path in paths) {
|
|
newFile(path, content: 'error');
|
|
}
|
|
}
|
|
}
|