| // 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 'dart:mirrors'; |
| |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/src/analysis_options/analysis_options_provider.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/file_system/file_system.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/lint/linter.dart'; |
| import 'package:analyzer/src/lint/registry.dart'; |
| import 'package:analyzer/src/task/options.dart'; |
| import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import '../../generated/test_support.dart'; |
| |
| main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(ErrorCodeValuesTest); |
| defineReflectiveTests(OptionsFileValidatorTest); |
| defineReflectiveTests(OptionsProviderTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class ErrorCodeValuesTest { |
| test_errorCodes() { |
| // Now that we're using unique names for comparison, the only reason to |
| // split the codes by class is to find all of the classes that need to be |
| // checked against `errorCodeValues`. |
| var errorTypeMap = <Type, List<ErrorCode>>{}; |
| for (ErrorCode code in errorCodeValues) { |
| Type type = code.runtimeType; |
| errorTypeMap.putIfAbsent(type, () => <ErrorCode>[]).add(code); |
| } |
| |
| StringBuffer missingCodes = StringBuffer(); |
| errorTypeMap.forEach((Type errorType, List<ErrorCode> codes) { |
| var listedNames = codes.map((ErrorCode code) => code.uniqueName).toSet(); |
| |
| var declaredNames = reflectClass(errorType) |
| .declarations |
| .values |
| .map((DeclarationMirror declarationMirror) { |
| String name = declarationMirror.simpleName.toString(); |
| // TODO(danrubel): find a better way to extract the text from the symbol |
| assert(name.startsWith('Symbol("') && name.endsWith('")')); |
| return '$errorType.${name.substring(8, name.length - 2)}'; |
| }).where((String name) { |
| return name == name.toUpperCase(); |
| }).toList(); |
| |
| // Assert that all declared names are in errorCodeValues |
| |
| for (String declaredName in declaredNames) { |
| if (!listedNames.contains(declaredName)) { |
| missingCodes.writeln(); |
| missingCodes.write(' $declaredName'); |
| } |
| } |
| }); |
| if (missingCodes.isNotEmpty) { |
| fail('Missing error codes:$missingCodes'); |
| } |
| } |
| } |
| |
| @reflectiveTest |
| class OptionsFileValidatorTest { |
| final OptionsFileValidator validator = OptionsFileValidator( |
| TestSource(), |
| sourceIsOptionsForContextRoot: true, |
| ); |
| final AnalysisOptionsProvider optionsProvider = AnalysisOptionsProvider(); |
| |
| test_analyzer_cannotIgnore_badValue() { |
| validate(''' |
| analyzer: |
| cannot-ignore: |
| - not_an_error_code |
| ''', [AnalysisOptionsWarningCode.UNRECOGNIZED_ERROR_CODE]); |
| } |
| |
| test_analyzer_cannotIgnore_goodValue() { |
| validate(''' |
| analyzer: |
| cannot-ignore: |
| - invalid_annotation |
| ''', []); |
| } |
| |
| test_analyzer_cannotIgnore_lintRule() { |
| Registry.ruleRegistry.registerLintRule(TestRule()); |
| validate(''' |
| analyzer: |
| cannot-ignore: |
| - fantastic_test_rule |
| ''', []); |
| } |
| |
| test_analyzer_cannotIgnore_notAList() { |
| validate(''' |
| analyzer: |
| cannot-ignore: |
| one_error_code: true |
| ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]); |
| } |
| |
| test_analyzer_cannotIgnore_severity() { |
| validate(''' |
| analyzer: |
| cannot-ignore: |
| - error |
| ''', []); |
| } |
| |
| test_analyzer_cannotIgnore_valueNotAString() { |
| validate(''' |
| analyzer: |
| cannot-ignore: |
| one_error_code: |
| foo: bar |
| ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]); |
| } |
| |
| test_analyzer_enableExperiment_badValue() { |
| validate(''' |
| analyzer: |
| enable-experiment: |
| - not-an-experiment |
| ''', [AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITHOUT_VALUES]); |
| } |
| |
| test_analyzer_enableExperiment_notAList() { |
| validate(''' |
| analyzer: |
| enable-experiment: |
| experiment: true |
| ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]); |
| } |
| |
| test_analyzer_error_code_supported() { |
| validate(''' |
| analyzer: |
| errors: |
| unused_local_variable: ignore |
| invalid_assignment: warning |
| assignment_of_do_not_store: error |
| dead_code: info |
| ''', []); |
| } |
| |
| test_analyzer_error_code_supported_bad_value() { |
| var errors = validate(''' |
| analyzer: |
| errors: |
| unused_local_variable: ftw |
| ''', [AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES]); |
| expect(errors.single.problemMessage.messageText(includeUrl: false), |
| contains("The option 'ftw'")); |
| } |
| |
| test_analyzer_error_code_supported_bad_value_null() { |
| var errors = validate(''' |
| analyzer: |
| errors: |
| unused_local_variable: null |
| ''', [AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES]); |
| expect(errors.single.problemMessage.messageText(includeUrl: false), |
| contains("The option 'null'")); |
| } |
| |
| test_analyzer_error_code_unsupported() { |
| var errors = validate(''' |
| analyzer: |
| errors: |
| not_supported: ignore |
| ''', [AnalysisOptionsWarningCode.UNRECOGNIZED_ERROR_CODE]); |
| expect(errors.single.problemMessage.messageText(includeUrl: false), |
| contains("'not_supported' isn't a recognized error code")); |
| } |
| |
| test_analyzer_error_code_unsupported_null() { |
| var errors = validate(''' |
| analyzer: |
| errors: |
| null: ignore |
| ''', [AnalysisOptionsWarningCode.UNRECOGNIZED_ERROR_CODE]); |
| expect(errors.single.problemMessage.messageText(includeUrl: false), |
| contains("'null' isn't a recognized error code")); |
| } |
| |
| test_analyzer_errors_notAMap() { |
| validate(''' |
| analyzer: |
| errors: |
| - invalid_annotation |
| - unused_import |
| ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]); |
| } |
| |
| test_analyzer_errors_valueNotAScalar() { |
| validate(''' |
| analyzer: |
| errors: |
| invalid_annotation: ignore |
| unused_import: [1, 2, 3] |
| ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]); |
| } |
| |
| test_analyzer_language_bad_format_list() { |
| validate(''' |
| analyzer: |
| language: |
| - notAnOption: true |
| ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]); |
| } |
| |
| test_analyzer_language_bad_format_scalar() { |
| validate(''' |
| analyzer: |
| language: true |
| ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]); |
| } |
| |
| test_analyzer_language_supports_empty() { |
| validate(''' |
| analyzer: |
| language: |
| ''', []); |
| } |
| |
| test_analyzer_language_unsupported_key() { |
| validate(''' |
| analyzer: |
| language: |
| unsupported: true |
| ''', [AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES]); |
| } |
| |
| test_analyzer_lint_codes_recognized() { |
| Registry.ruleRegistry.registerLintRule(TestRule()); |
| validate(''' |
| analyzer: |
| errors: |
| fantastic_test_rule: ignore |
| ''', []); |
| } |
| |
| test_analyzer_supported_exclude() { |
| validate(''' |
| analyzer: |
| exclude: |
| - test/_data/p4/lib/lib1.dart |
| ''', []); |
| } |
| |
| test_analyzer_unsupported_option() { |
| validate(''' |
| analyzer: |
| not_supported: true |
| ''', [AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES]); |
| } |
| |
| test_chromeos_manifest_checks() { |
| validate(''' |
| analyzer: |
| optional-checks: |
| chrome-os-manifest-checks |
| ''', []); |
| } |
| |
| test_chromeos_manifest_checks_invalid() { |
| validate(''' |
| analyzer: |
| optional-checks: |
| chromeos-manifest |
| ''', [AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUES]); |
| } |
| |
| test_chromeos_manifest_checks_notAMap() { |
| validate(''' |
| analyzer: |
| optional-checks: |
| - chrome-os-manifest-checks |
| ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]); |
| } |
| |
| test_codeStyle_format_false() { |
| validate(''' |
| code-style: |
| format: false |
| ''', []); |
| } |
| |
| test_codeStyle_format_invalid() { |
| validate(''' |
| code-style: |
| format: 80 |
| ''', [AnalysisOptionsWarningCode.UNSUPPORTED_VALUE]); |
| } |
| |
| test_codeStyle_format_true() { |
| validate(''' |
| code-style: |
| format: true |
| ''', []); |
| } |
| |
| test_codeStyle_unsupported_list() { |
| validate(''' |
| code-style: |
| - format |
| ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]); |
| } |
| |
| test_codeStyle_unsupported_scalar() { |
| validate(''' |
| code-style: format |
| ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]); |
| } |
| |
| test_codeStyle_unsupportedOption() { |
| validate(''' |
| code-style: |
| not_supported: true |
| ''', [AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITHOUT_VALUES]); |
| } |
| |
| test_formatter_invalid_key() { |
| validate(''' |
| formatter: |
| wrong: 123 |
| ''', [ |
| AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITHOUT_VALUES, |
| ]); |
| } |
| |
| test_formatter_invalid_keys() { |
| validate(''' |
| formatter: |
| wrong: 123 |
| wrong2: 123 |
| ''', [ |
| AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITHOUT_VALUES, |
| AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITHOUT_VALUES, |
| ]); |
| } |
| |
| test_formatter_pageWidth_invalid_decimal() { |
| validate(''' |
| formatter: |
| page_width: 123.45 |
| ''', [ |
| AnalysisOptionsWarningCode.INVALID_OPTION, |
| ]); |
| } |
| |
| test_formatter_pageWidth_invalid_negativeInteger() { |
| validate(''' |
| formatter: |
| page_width: -123 |
| ''', [ |
| AnalysisOptionsWarningCode.INVALID_OPTION, |
| ]); |
| } |
| |
| test_formatter_pageWidth_invalid_string() { |
| validate(''' |
| formatter: |
| page_width: "123" |
| ''', [AnalysisOptionsWarningCode.INVALID_OPTION]); |
| } |
| |
| test_formatter_pageWidth_invalid_zero() { |
| validate(''' |
| formatter: |
| page_width: 0 |
| ''', [ |
| AnalysisOptionsWarningCode.INVALID_OPTION, |
| ]); |
| } |
| |
| test_formatter_pageWidth_valid_integer() { |
| validate(''' |
| formatter: |
| page_width: 123 |
| ''', []); |
| } |
| |
| test_formatter_valid_empty() { |
| validate(''' |
| formatter: |
| ''', []); |
| } |
| |
| test_linter_supported_rules() { |
| Registry.ruleRegistry.registerLintRule(TestRule()); |
| validate(''' |
| linter: |
| rules: |
| - fantastic_test_rule |
| ''', []); |
| } |
| |
| test_linter_unsupported_option() { |
| validate(''' |
| linter: |
| unsupported: true |
| ''', [AnalysisOptionsWarningCode.UNSUPPORTED_OPTION_WITH_LEGAL_VALUE]); |
| } |
| |
| test_plugins_each_invalid_mapKey() { |
| validate(''' |
| plugins: |
| one: |
| ppath: foo/bar |
| ''', []); |
| } |
| |
| test_plugins_each_valid_mapKey() { |
| validate(''' |
| plugins: |
| one: |
| path: foo/bar |
| ''', []); |
| } |
| |
| test_plugins_each_valid_scalar() { |
| validate(''' |
| plugins: |
| one: ^1.2.3 |
| ''', []); |
| } |
| |
| test_plugins_invalid_scalar() { |
| validate(''' |
| plugins: 7 |
| ''', [AnalysisOptionsWarningCode.INVALID_SECTION_FORMAT]); |
| } |
| |
| test_plugins_valid_empty() { |
| validate(''' |
| plugins: |
| ''', []); |
| } |
| |
| List<AnalysisError> validate(String source, List<ErrorCode> expected) { |
| var options = optionsProvider.getOptionsFromString(source); |
| var errors = validator.validate(options); |
| expect(errors.map((AnalysisError e) => e.errorCode), |
| unorderedEquals(expected)); |
| return errors; |
| } |
| } |
| |
| @reflectiveTest |
| class OptionsProviderTest with ResourceProviderMixin { |
| late final SourceFactory sourceFactory; |
| |
| late final AnalysisOptionsProvider provider; |
| |
| String get optionsFilePath => '/analysis_options.yaml'; |
| |
| void assertErrorsInList( |
| List<AnalysisError> errors, |
| List<ExpectedError> expectedErrors, |
| ) { |
| GatheringErrorListener errorListener = GatheringErrorListener(); |
| errorListener.addAll(errors); |
| errorListener.assertErrors(expectedErrors); |
| } |
| |
| void assertErrorsInOptionsFile( |
| String code, List<ExpectedError> expectedErrors) { |
| newFile(optionsFilePath, code); |
| var errors = analyzeAnalysisOptions( |
| sourceFactory.forUri2(toUri(optionsFilePath))!, |
| code, |
| sourceFactory, |
| '/', |
| null /*sdkVersionConstraint*/, |
| ); |
| |
| assertErrorsInList(errors, expectedErrors); |
| } |
| |
| ExpectedError error( |
| ErrorCode code, |
| int offset, |
| int length, { |
| Pattern? correctionContains, |
| String? text, |
| List<Pattern> messageContains = const [], |
| List<ExpectedContextMessage> contextMessages = |
| const <ExpectedContextMessage>[], |
| }) => |
| ExpectedError( |
| code, |
| offset, |
| length, |
| correctionContains: correctionContains, |
| message: text, |
| messageContains: messageContains, |
| expectedContextMessages: contextMessages, |
| ); |
| |
| void setUp() { |
| sourceFactory = SourceFactory([ResourceUriResolver(resourceProvider)]); |
| provider = AnalysisOptionsProvider(sourceFactory); |
| } |
| |
| test_multiplePlugins_firstIsDirectlyIncluded_secondIsDirect_listForm() { |
| newFile(convertPath('/other_options.yaml'), ''' |
| analyzer: |
| plugins: |
| - plugin_one |
| '''); |
| assertErrorsInOptionsFile(r''' |
| include: other_options.yaml |
| analyzer: |
| plugins: |
| - plugin_two |
| ''', [ |
| error(AnalysisOptionsWarningCode.MULTIPLE_PLUGINS, 55, 10), |
| ]); |
| } |
| |
| test_multiplePlugins_firstIsDirectlyIncluded_secondIsDirect_mapForm() { |
| newFile('/other_options.yaml', ''' |
| analyzer: |
| plugins: |
| - plugin_one |
| '''); |
| assertErrorsInOptionsFile(r''' |
| include: other_options.yaml |
| analyzer: |
| plugins: |
| plugin_two: |
| foo: bar |
| ''', [ |
| error(AnalysisOptionsWarningCode.MULTIPLE_PLUGINS, 53, 10), |
| ]); |
| } |
| |
| test_multiplePlugins_firstIsDirectlyIncluded_secondIsDirect_scalarForm() { |
| newFile('/other_options.yaml', ''' |
| analyzer: |
| plugins: |
| - plugin_one |
| '''); |
| assertErrorsInOptionsFile(r''' |
| include: other_options.yaml |
| analyzer: |
| plugins: plugin_two |
| ''', [ |
| error(AnalysisOptionsWarningCode.MULTIPLE_PLUGINS, 49, 10), |
| ]); |
| } |
| |
| test_multiplePlugins_firstIsIndirectlyIncluded_secondIsDirect() { |
| newFile('/more_options.yaml', ''' |
| analyzer: |
| plugins: |
| - plugin_one |
| '''); |
| newFile('/other_options.yaml', ''' |
| include: more_options.yaml |
| '''); |
| assertErrorsInOptionsFile(r''' |
| include: other_options.yaml |
| analyzer: |
| plugins: |
| - plugin_two |
| ''', [ |
| error(AnalysisOptionsWarningCode.MULTIPLE_PLUGINS, 55, 10), |
| ]); |
| } |
| |
| test_multiplePlugins_firstIsIndirectlyIncluded_secondIsDirectlyIncluded() { |
| newFile('/more_options.yaml', ''' |
| analyzer: |
| plugins: |
| - plugin_one |
| '''); |
| newFile('/other_options.yaml', ''' |
| include: more_options.yaml |
| analyzer: |
| plugins: |
| - plugin_two |
| '''); |
| assertErrorsInOptionsFile(r''' |
| include: other_options.yaml |
| ''', [ |
| error(AnalysisOptionsWarningCode.INCLUDED_FILE_WARNING, 9, 18), |
| ]); |
| } |
| |
| test_multiplePlugins_multipleDirect_listForm() { |
| assertErrorsInOptionsFile(r''' |
| analyzer: |
| plugins: |
| - plugin_one |
| - plugin_two |
| - plugin_three |
| ''', [ |
| error(AnalysisOptionsWarningCode.MULTIPLE_PLUGINS, 44, 10), |
| error(AnalysisOptionsWarningCode.MULTIPLE_PLUGINS, 61, 12), |
| ]); |
| } |
| |
| test_multiplePlugins_multipleDirect_listForm_nonString() { |
| assertErrorsInOptionsFile(r''' |
| analyzer: |
| plugins: |
| - 7 |
| - plugin_one |
| ''', []); |
| } |
| |
| test_multiplePlugins_multipleDirect_listForm_sameName() { |
| assertErrorsInOptionsFile(r''' |
| analyzer: |
| plugins: |
| - plugin_one |
| - plugin_one |
| ''', []); |
| } |
| |
| test_multiplePlugins_multipleDirect_mapForm() { |
| assertErrorsInOptionsFile(r''' |
| analyzer: |
| plugins: |
| plugin_one: yes |
| plugin_two: sure |
| ''', [ |
| error(AnalysisOptionsWarningCode.MULTIPLE_PLUGINS, 45, 10), |
| ]); |
| } |
| |
| test_multiplePlugins_multipleDirect_mapForm_sameName() { |
| assertErrorsInOptionsFile(r''' |
| analyzer: |
| plugins: |
| plugin_one: yes |
| plugin_one: sure |
| ''', [ |
| error(AnalysisOptionsErrorCode.PARSE_ERROR, 45, 10), |
| ]); |
| } |
| |
| List<AnalysisError> validate(String code, List<ErrorCode> expected) { |
| newFile(optionsFilePath, code); |
| var errors = analyzeAnalysisOptions( |
| sourceFactory.forUri('file://$optionsFilePath')!, |
| code, |
| sourceFactory, |
| '/', |
| null /*sdkVersionConstraint*/, |
| ); |
| expect( |
| errors.map((AnalysisError e) => e.errorCode), |
| unorderedEquals(expected), |
| ); |
| return errors; |
| } |
| } |
| |
| class TestRule extends LintRule { |
| static const LintCode code = LintCode( |
| 'fantastic_test_rule', 'Fantastic test rule.', |
| correctionMessage: 'Try fantastic test rule.'); |
| |
| TestRule() |
| : super( |
| name: 'fantastic_test_rule', |
| description: '', |
| ); |
| |
| TestRule.withName(String name) |
| : super( |
| name: name, |
| description: '', |
| ); |
| |
| @override |
| LintCode get lintCode => code; |
| } |