blob: 065018bd5926306d35b64a97565cbaf275e06576 [file] [log] [blame]
// 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;
}