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