| // Copyright (c) 2015, 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 'package:analysis_server/protocol/protocol.dart'; |
| import 'package:analysis_server/protocol/protocol_constants.dart'; |
| import 'package:analysis_server/protocol/protocol_generated.dart' |
| hide AnalysisOptions; |
| import 'package:analysis_server/src/utilities/mocks.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer_plugin/protocol/protocol_common.dart'; |
| import 'package:linter/src/rules.dart'; |
| import 'package:path/path.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import '../analysis_server_base.dart'; |
| |
| void main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(AnalysisOptionsFileNotificationTest); |
| defineReflectiveTests(PluginConflictsNotificationTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class AnalysisOptionsFileNotificationTest extends PubPackageAnalysisServerTest { |
| late File optionsFile; |
| Map<File, List<AnalysisError>> filesErrors = {}; |
| |
| final testSource = ''' |
| void f() { |
| var x = ''; |
| int y = x; // Not assignable. |
| print(y); |
| }'''; |
| |
| List<AnalysisError> get testFileErrors => filesErrors[testFile]!; |
| |
| void addOptionsFile(String contents) { |
| optionsFile = newAnalysisOptionsYamlFile(testPackageRootPath, contents); |
| } |
| |
| @override |
| void processNotification(Notification notification) { |
| if (notification.event == analysisNotificationErrors) { |
| var decoded = AnalysisErrorsParams.fromNotification( |
| notification, |
| clientUriConverter: server.uriConverter, |
| ); |
| filesErrors[getFile(decoded.file)] = decoded.errors; |
| } |
| } |
| |
| @override |
| Future<void> setUp() async { |
| registerLintRules(); |
| super.setUp(); |
| await setRoots(included: [workspaceRootPath], excluded: []); |
| } |
| |
| Future<void> test_error_filter() async { |
| addOptionsFile(''' |
| analyzer: |
| errors: |
| unused_local_variable: ignore |
| '''); |
| |
| addTestFile(''' |
| void f() { |
| String unused = ""; |
| } |
| '''); |
| |
| await waitForTasksFinished(); |
| |
| // Verify options file. |
| // TODO(brianwilkerson): Implement options file analysis in the new driver. |
| // expect(optionsFileErrors, isNotNull); |
| // expect(optionsFileErrors, isEmpty); |
| |
| // Verify test file. |
| expect(testFileErrors, isNotNull); |
| expect(testFileErrors, isEmpty); |
| } |
| |
| Future<void> test_error_filter_removed() async { |
| addOptionsFile(''' |
| analyzer: |
| errors: |
| unused_local_variable: ignore |
| '''); |
| |
| addTestFile(''' |
| void f() { |
| String unused = ""; |
| } |
| '''); |
| |
| await waitForTasksFinished(); |
| |
| // Verify options file. |
| // TODO(brianwilkerson): Implement options file analysis in the new driver. |
| // expect(optionsFileErrors, isNotNull); |
| // expect(optionsFileErrors, isEmpty); |
| |
| // Verify test file. |
| expect(testFileErrors, isNotNull); |
| expect(testFileErrors, isEmpty); |
| |
| addOptionsFile(''' |
| analyzer: |
| errors: |
| # unused_local_variable: ignore |
| '''); |
| |
| await pumpEventQueue(); |
| await waitForTasksFinished(); |
| |
| // Verify options file. |
| // TODO(brianwilkerson): Implement options file analysis in the new driver. |
| // expect(optionsFileErrors, isEmpty); |
| |
| // Verify test file. |
| expect(testFileErrors, hasLength(1)); |
| } |
| |
| Future<void> test_lint_options_changes() async { |
| addOptionsFile(''' |
| linter: |
| rules: |
| - camel_case_types |
| - constant_identifier_names |
| '''); |
| |
| addTestFile(testSource); |
| |
| await waitForTasksFinished(); |
| |
| verifyLintsEnabled(['camel_case_types', 'constant_identifier_names']); |
| |
| addOptionsFile(''' |
| linter: |
| rules: |
| - camel_case_types |
| '''); |
| |
| await pumpEventQueue(); |
| await waitForTasksFinished(); |
| |
| verifyLintsEnabled(['camel_case_types']); |
| } |
| |
| Future<void> test_lint_options_unsupported() async { |
| addOptionsFile(''' |
| linter: |
| rules: |
| - unsupported |
| '''); |
| |
| addTestFile(testSource); |
| |
| await waitForTasksFinished(); |
| |
| // TODO(brianwilkerson): Implement options file analysis in the new driver. |
| // expect(optionsFileErrors, hasLength(1)); |
| // expect(optionsFileErrors.first.severity, AnalysisErrorSeverity.WARNING); |
| // expect(optionsFileErrors.first.type, AnalysisErrorType.STATIC_WARNING); |
| } |
| |
| Future<void> test_options_file_added() async { |
| addTestFile(testSource); |
| |
| await waitForTasksFinished(); |
| |
| // Verify that lints are disabled. |
| expect(testFileAnalysisOptions.lint, false); |
| |
| // Add options file with a lint enabled. |
| addOptionsFile(''' |
| linter: |
| rules: |
| - camel_case_types |
| '''); |
| |
| await pumpEventQueue(); |
| await waitForTasksFinished(); |
| |
| verifyLintsEnabled(['camel_case_types']); |
| } |
| |
| Future<void> test_options_file_parse_error() async { |
| addOptionsFile(''' |
| ; #bang |
| '''); |
| |
| await waitForTasksFinished(); |
| |
| // TODO(brianwilkerson): Implement options file analysis in the new driver. |
| // expect(optionsFileErrors, hasLength(1)); |
| // expect(optionsFileErrors.first.severity, AnalysisErrorSeverity.ERROR); |
| // expect(optionsFileErrors.first.type, AnalysisErrorType.COMPILE_TIME_ERROR); |
| } |
| |
| Future<void> test_options_file_removed() async { |
| addOptionsFile(''' |
| linter: |
| rules: |
| - camel_case_types |
| '''); |
| |
| addTestFile(testSource); |
| |
| await waitForTasksFinished(); |
| |
| verifyLintsEnabled(['camel_case_types']); |
| |
| deleteFile(optionsFile.path); |
| |
| await pumpEventQueue(); |
| await waitForTasksFinished(); |
| |
| expect(testFileAnalysisOptions.lint, false); |
| } |
| |
| void verifyLintsEnabled(List<String> lints) { |
| var options = testFileAnalysisOptions; |
| expect(options.lint, true); |
| var rules = options.lintRules.map((rule) => rule.name); |
| expect(rules, unorderedEquals(lints)); |
| } |
| } |
| |
| @reflectiveTest |
| class PluginConflictsNotificationTest extends PubPackageAnalysisServerTest { |
| @override |
| late final TestPluginManager pluginManager = TestPluginManager( |
| resourceProvider, |
| ); |
| |
| Map<File, List<AnalysisError>?> filesErrors = {}; |
| |
| @override |
| void processNotification(Notification notification) { |
| if (notification.event == analysisNotificationErrors) { |
| var decoded = AnalysisErrorsParams.fromNotification( |
| notification, |
| clientUriConverter: server.uriConverter, |
| ); |
| filesErrors[getFile(decoded.file)] = decoded.errors; |
| } |
| } |
| |
| @override |
| void setUp() { |
| registerLintRules(); |
| super.setUp(); |
| } |
| |
| Future<void> test_plugin_conflict_different_sources() async { |
| var optionsFile = newAnalysisOptionsYamlFile(testPackageRootPath, ''' |
| plugins: |
| foo: ^1.0.0 |
| '''); |
| |
| var nestedOptionsFile = newAnalysisOptionsYamlFile( |
| join(testPackageRootPath, 'sub'), |
| ''' |
| plugins: |
| foo: |
| path: ../foo |
| ''', |
| ); |
| |
| await setRoots(included: [workspaceRootPath], excluded: []); |
| await waitForTasksFinished(); |
| await pumpEventQueue(); |
| |
| var rootErrors = filesErrors[optionsFile]!; |
| expect(rootErrors, hasLength(1)); |
| expect(rootErrors[0].code, 'incompatible_plugin_source'); |
| |
| // We only report one file in the conflict, not the second. |
| var nestedErrors = filesErrors[nestedOptionsFile]!; |
| |
| // TODO(srawlins): This is temporary. By the end of this arc of work, we |
| // won't be reporting this. https://github.com/dart-lang/sdk/issues/63627 |
| expect(nestedErrors, hasLength(1)); |
| expect(nestedErrors[0].code, 'plugins_in_inner_options'); |
| } |
| |
| Future<void> test_plugin_conflict_different_versions() async { |
| var optionsFile = newAnalysisOptionsYamlFile(testPackageRootPath, ''' |
| plugins: |
| foo: ^1.0.0 |
| '''); |
| |
| var nestedOptionsFile = newAnalysisOptionsYamlFile( |
| join(testPackageRootPath, 'sub'), |
| ''' |
| plugins: |
| foo: ^2.0.0 |
| ''', |
| ); |
| |
| await setRoots(included: [workspaceRootPath], excluded: []); |
| await waitForTasksFinished(); |
| await pumpEventQueue(); |
| |
| var rootErrors = filesErrors[optionsFile]!; |
| expect(rootErrors, hasLength(1)); |
| expect(rootErrors[0].code, 'incompatible_plugin_source'); |
| |
| // We only report one file in the conflict, not the second. |
| var nestedErrors = filesErrors[nestedOptionsFile]!; |
| // TODO(srawlins): This is temporary. By the end of this arc of work, we |
| // won't be reporting this. https://github.com/dart-lang/sdk/issues/63627 |
| expect(nestedErrors, hasLength(1)); |
| expect(nestedErrors[0].code, 'plugins_in_inner_options'); |
| } |
| |
| Future<void> test_plugin_conflict_resolved_on_update() async { |
| var optionsFile = newAnalysisOptionsYamlFile(testPackageRootPath, ''' |
| plugins: |
| foo: ^1.0.0 |
| '''); |
| |
| var nestedOptionsFile = newAnalysisOptionsYamlFile( |
| join(testPackageRootPath, 'sub'), |
| ''' |
| plugins: |
| foo: ^2.0.0 |
| ''', |
| ); |
| |
| await setRoots(included: [workspaceRootPath], excluded: []); |
| await waitForTasksFinished(); |
| await pumpEventQueue(); |
| |
| expect(filesErrors[optionsFile]!, hasLength(1)); |
| // TODO(srawlins): This is temporary. By the end of this arc of work, we |
| // won't be reporting this. https://github.com/dart-lang/sdk/issues/63627 |
| expect(filesErrors[nestedOptionsFile]!, hasLength(1)); |
| expect(filesErrors[nestedOptionsFile]![0].code, 'plugins_in_inner_options'); |
| |
| newAnalysisOptionsYamlFile(join(testPackageRootPath, 'sub'), ''' |
| plugins: |
| foo: ^1.0.0 |
| '''); |
| |
| await pumpEventQueue(); |
| await waitForTasksFinished(); |
| |
| expect(filesErrors[optionsFile]!, isEmpty); |
| // TODO(srawlins): This is temporary. By the end of this arc of work, we |
| // won't be reporting this. https://github.com/dart-lang/sdk/issues/63627 |
| expect(filesErrors[nestedOptionsFile]!, hasLength(1)); |
| expect(filesErrors[nestedOptionsFile]![0].code, 'plugins_in_inner_options'); |
| } |
| |
| Future<void> test_plugin_no_conflict_different_plugins() async { |
| var optionsFile = newAnalysisOptionsYamlFile(testPackageRootPath, ''' |
| plugins: |
| foo: ^1.0.0 |
| '''); |
| |
| var nestedOptionsFile = newAnalysisOptionsYamlFile( |
| join(testPackageRootPath, 'sub'), |
| ''' |
| plugins: |
| bar: ^1.0.0 |
| ''', |
| ); |
| |
| await setRoots(included: [workspaceRootPath], excluded: []); |
| await waitForTasksFinished(); |
| await pumpEventQueue(); |
| |
| var rootErrors = filesErrors[optionsFile]!; |
| expect(rootErrors, isEmpty); |
| |
| var nestedErrors = filesErrors[nestedOptionsFile]!; |
| // TODO(srawlins): This is temporary. By the end of this arc of work, we |
| // won't be reporting this. https://github.com/dart-lang/sdk/issues/63627 |
| expect(nestedErrors, hasLength(1)); |
| expect(nestedErrors[0].code, 'plugins_in_inner_options'); |
| } |
| |
| Future<void> test_plugin_no_conflict_identical_sources() async { |
| var optionsFile = newAnalysisOptionsYamlFile(testPackageRootPath, ''' |
| plugins: |
| foo: ^1.0.0 |
| '''); |
| |
| var nestedOptionsFile = newAnalysisOptionsYamlFile( |
| join(testPackageRootPath, 'sub'), |
| ''' |
| plugins: |
| foo: ^1.0.0 |
| ''', |
| ); |
| |
| await setRoots(included: [workspaceRootPath], excluded: []); |
| await waitForTasksFinished(); |
| await pumpEventQueue(); |
| |
| var rootErrors = filesErrors[optionsFile]!; |
| expect(rootErrors, isEmpty); |
| |
| var nestedErrors = filesErrors[nestedOptionsFile]!; |
| // TODO(srawlins): This is temporary. By the end of this arc of work, we |
| // won't be reporting this. https://github.com/dart-lang/sdk/issues/63627 |
| expect(nestedErrors, hasLength(1)); |
| expect(nestedErrors[0].code, 'plugins_in_inner_options'); |
| } |
| } |