| // 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. |
| var sdkRoot = newFolder('/sdk'); |
| createMockSdk( |
| resourceProvider: resourceProvider, |
| root: sdkRoot, |
| ); |
| |
| server = AnalysisServer( |
| serverChannel, |
| resourceProvider, |
| AnalysisServerOptions(), |
| DartSdkManager(sdkRoot.path), |
| 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'); |
| } |
| } |
| } |