diff --git a/pkg/analyzer/test/src/dart/resolution/node_text_expectations.dart b/pkg/analyzer/test/src/dart/resolution/node_text_expectations.dart index dba255dc5e0..0d280f52ecb 100644 --- a/pkg/analyzer/test/src/dart/resolution/node_text_expectations.dart +++ b/pkg/analyzer/test/src/dart/resolution/node_text_expectations.dart @@ -176,6 +176,36 @@ class NodeTextExpectationsCollector { methodName: 'parseTestCodeWithDiagnostics', argument: _ArgumentIndex(0), ), + _AssertMethod( + className: 'AbstractAnalysisOptionsTest', + methodName: 'assertDiagnosticsInCode', + argument: _ArgumentIndex(0), + ), + _AssertMethod( + className: 'AbstractAnalysisOptionsTest', + methodName: 'assertDiagnosticsInFiles', + argument: _ArgumentMapEntryValue(mapArgument: _ArgumentIndex(0)), + ), + _AssertMethod( + className: 'OptionsFileValidatorTest', + methodName: 'validate', + argument: _ArgumentIndex(0), + ), + _AssertMethod( + className: 'OptionsProviderTest', + methodName: 'assertDiagnosticsInOptionsFile', + argument: _ArgumentIndex(1), + ), + _AssertMethod( + className: 'OptionsRuleValidatorTestMixin', + methodName: 'assertDiagnostics', + argument: _ArgumentIndex(0), + ), + _AssertMethod( + className: 'OptionsRuleValidatorTestMixin', + methodName: 'assertRuleDiagnosticsInFiles', + argument: _ArgumentMapEntryValue(mapArgument: _ArgumentIndex(0)), + ), _AssertMethod( className: 'ResolutionTest', methodName: 'assertDartObjectText', diff --git a/pkg/analyzer/test/src/diagnostics/analysis_options/analysis_options_test_support.dart b/pkg/analyzer/test/src/diagnostics/analysis_options/analysis_options_test_support.dart index f640e158087..eab91a49bdc 100644 --- a/pkg/analyzer/test/src/diagnostics/analysis_options/analysis_options_test_support.dart +++ b/pkg/analyzer/test/src/diagnostics/analysis_options/analysis_options_test_support.dart @@ -2,7 +2,7 @@ // 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:_fe_analyzer_shared/src/base/errors.dart'; +import 'package:analyzer/diagnostic/diagnostic.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/source/file_source.dart'; import 'package:analyzer/src/analysis_options/options_file_validator.dart'; @@ -12,59 +12,46 @@ import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/source/package_map_resolver.dart'; import 'package:analyzer/src/test_utilities/lint_registration_mixin.dart'; import 'package:analyzer_testing/resource_provider_mixin.dart'; -import 'package:analyzer_testing/src/analysis_rule/pub_package_resolution.dart'; +import 'package:analyzer_testing/src/expected_diagnostics.dart'; import 'package:meta/meta.dart'; import 'package:pub_semver/pub_semver.dart'; +import 'package:test/test.dart'; -import '../../../generated/test_support.dart'; +import '../../../util/diff.dart'; +import '../../dart/resolution/node_text_expectations.dart'; abstract class AbstractAnalysisOptionsTest - with ResourceProviderMixin, LintRegistrationMixin { + with + ResourceProviderMixin, + LintRegistrationMixin, + AnalysisOptionsDiagnosticExpectationMixin { late SourceFactory sourceFactory; Map? dependencies; - late File analysisOptionsFile = newFile(analysisOptionsPath, ''); - late String analysisOptionsPath = convertPath('/analysis_options.yaml'); + late File analysisOptionsFile = getFile('/analysis_options.yaml'); VersionConstraint? get sdkVersionConstraint => null; - Future assertErrorsInCode( - String code, - List expectedDiagnostics, - ) async { - analysisOptionsFile.writeAsStringSync(code); + Future assertDiagnosticsInCode(String code) async { + await assertDiagnosticsInFiles({analysisOptionsFile: code}); + } + + Future assertDiagnosticsInFiles(Map codeByFile) async { + var cleanCodeByFile = writeFilesWithoutDiagnosticExpectations(codeByFile); + var diagnostics = AnalysisOptionsAnalyzer( initialSource: FileSource(analysisOptionsFile), sourceFactory: sourceFactory, contextRoot: '/', sdkVersionConstraint: sdkVersionConstraint, resourceProvider: resourceProvider, - ).walkIncludes(content: code); - var diagnosticListener = GatheringDiagnosticListener(); - diagnosticListener.addAll(diagnostics); - diagnosticListener.assertErrors(expectedDiagnostics); + ).walkIncludes(content: cleanCodeByFile[analysisOptionsFile]!); + + assertDiagnosticMarkersInFiles( + codeByFile: codeByFile, + diagnostics: diagnostics, + ); } - Future assertNoErrorsInCode(String code) async => - await assertErrorsInCode(code, const []); - - ExpectedError error( - DiagnosticCode code, - int offset, - int length, { - Pattern? correctionContains, - String? text, - List messageContains = const [], - List contextMessages = - const [], - }) => ExpectedError( - code, - offset, - length, - correctionContains: correctionContains, - messageContainsAll: messageContains, - contextMessages: contextMessages, - ); - void setUp() { var resolvers = [ ResourceUriResolver(resourceProvider), @@ -82,3 +69,79 @@ abstract class AbstractAnalysisOptionsTest unregisterLintRules(); } } + +/// Shared inline diagnostic expectation checks for analysis-options tests. +/// +/// These helpers only compare diagnostics with inline markers. Test-specific +/// helpers remain responsible for choosing which analyzer or validator entry +/// point produces the diagnostics. +mixin AnalysisOptionsDiagnosticExpectationMixin { + void assertDiagnosticMarkersInFiles({ + required Map codeByFile, + required List diagnostics, + }) { + var cleanCodeByFile = { + for (var entry in codeByFile.entries) + entry.key: removeDiagnosticExpectations(entry.value), + }; + var actualCodeByFile = updateExpectedDiagnosticsForFiles( + contentByFile: cleanCodeByFile, + actualDiagnosticsByFile: _diagnosticsByFile( + files: codeByFile.keys, + diagnostics: diagnostics, + ), + ); + + var hasMismatch = false; + var index = 0; + for (var entry in codeByFile.entries) { + var actual = actualCodeByFile[entry.key]!; + if (actual != entry.value) { + NodeTextExpectationsCollector.add(actual, intraInvocationId: '$index'); + print('-------- ${entry.key.path} --------'); + printPrettyDiff(entry.value, actual); + hasMismatch = true; + } + index++; + } + + if (hasMismatch) { + fail('See the difference above.'); + } + } + + Map writeFilesWithoutDiagnosticExpectations( + Map codeByFile, + ) { + var cleanCodeByFile = { + for (var entry in codeByFile.entries) + entry.key: removeDiagnosticExpectations(entry.value), + }; + for (var entry in cleanCodeByFile.entries) { + entry.key.writeAsStringSync(entry.value); + } + return cleanCodeByFile; + } + + Map> _diagnosticsByFile({ + required Iterable files, + required List diagnostics, + }) { + var fileByPath = {for (var file in files) file.path: file}; + var diagnosticsByFile = {for (var file in files) file: []}; + + for (var diagnostic in diagnostics) { + var filePath = diagnostic.problemMessage.filePath; + var file = fileByPath[filePath]; + if (file == null) { + fail( + 'Cannot generate diagnostic expectations for $filePath: ' + 'no content was provided.', + ); + } + diagnosticsByFile[file]!.add(diagnostic); + } + + return diagnosticsByFile; + } +} diff --git a/pkg/analyzer/test/src/diagnostics/analysis_options/include_file_not_found_test.dart b/pkg/analyzer/test/src/diagnostics/analysis_options/include_file_not_found_test.dart index 34fa58a10cf..4fa166dc209 100644 --- a/pkg/analyzer/test/src/diagnostics/analysis_options/include_file_not_found_test.dart +++ b/pkg/analyzer/test/src/diagnostics/analysis_options/include_file_not_found_test.dart @@ -2,7 +2,6 @@ // 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:analyzer/src/diagnostic/diagnostic.dart' as diag; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'analysis_options_test_support.dart'; @@ -16,159 +15,107 @@ main() { @reflectiveTest class IncludeFileNotFoundTest extends AbstractAnalysisOptionsTest { Future test_notFound_existent_doubleQuoted() async { - await assertErrorsInCode( - ''' + await assertDiagnosticsInCode(''' include: "./analysis_options.yaml" -''', - [error(diag.recursiveIncludeFile, 9, 25)], - ); +// ^^^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.recursiveIncludeFile] The URI './analysis_options.yaml' included in '/analysis_options.yaml' includes '/analysis_options.yaml', creating a circular reference. +'''); } Future test_notFound_existent_list_first() async { - newFile('/included1.yaml', ''); - await assertErrorsInCode( - ''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' include: - ./analysis_options.yaml +// ^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.recursiveIncludeFile] The URI './analysis_options.yaml' included in '/analysis_options.yaml' includes '/analysis_options.yaml', creating a circular reference. - included1.yaml ''', - [error(diag.recursiveIncludeFile, 13, 23)], - ); + getFile('/included1.yaml'): '', + }); } Future test_notFound_existent_list_second() async { - newFile('/included1.yaml', ''); - await assertErrorsInCode( - ''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' include: - included1.yaml - ./analysis_options.yaml +// ^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.recursiveIncludeFile] The URI './analysis_options.yaml' included in '/analysis_options.yaml' includes '/analysis_options.yaml', creating a circular reference. ''', - [error(diag.recursiveIncludeFile, 32, 23)], - ); + getFile('/included1.yaml'): '', + }); } Future test_notFound_existent_notQuoted() async { - await assertErrorsInCode( - ''' + await assertDiagnosticsInCode(''' include: ./analysis_options.yaml -''', - [error(diag.recursiveIncludeFile, 9, 23)], - ); +// ^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.recursiveIncludeFile] The URI './analysis_options.yaml' included in '/analysis_options.yaml' includes '/analysis_options.yaml', creating a circular reference. +'''); } Future test_notFound_existent_singleQuoted() async { - await assertErrorsInCode( - ''' + await assertDiagnosticsInCode(''' include: './analysis_options.yaml' -''', - [error(diag.recursiveIncludeFile, 9, 25)], - ); +// ^^^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.recursiveIncludeFile] The URI './analysis_options.yaml' included in '/analysis_options.yaml' includes '/analysis_options.yaml', creating a circular reference. +'''); } Future test_notFound_nonexistent_doubleQuoted() async { - await assertErrorsInCode( - ''' + await assertDiagnosticsInCode(''' # We don't depend on pedantic, but we should consider adding it. include: "package:pedantic/analysis_options.yaml" -''', - [ - error( - diag.includeFileNotFound, - 74, - 40, - text: - "The URI 'package:pedantic/analysis_options.yaml' included in " - "'${convertPath('/analysis_options.yaml')}' can't be found " - "when analyzing '/'.", - ), - ], - ); +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.includeFileNotFound] The URI 'package:pedantic/analysis_options.yaml' included in '/analysis_options.yaml' can't be found when analyzing '/'. +'''); } Future test_notFound_nonexistent_list_first() async { - newFile('/included1.yaml', ''); - await assertErrorsInCode( - ''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' # We don't depend on pedantic, but we should consider adding it. include: - package:pedantic/analysis_options.yaml +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.includeFileNotFound] The URI 'package:pedantic/analysis_options.yaml' included in '/analysis_options.yaml' can't be found when analyzing '/'. - included1.yaml ''', - [ - error( - diag.includeFileNotFound, - 78, - 38, - text: - "The URI 'package:pedantic/analysis_options.yaml' included in " - "'${convertPath('/analysis_options.yaml')}' can't be found " - "when analyzing '/'.", - ), - ], - ); + getFile('/included1.yaml'): '', + }); } Future test_notFound_nonexistent_list_second() async { - newFile('/included1.yaml', ''); - await assertErrorsInCode( - ''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' # We don't depend on pedantic, but we should consider adding it. include: - included1.yaml - package:pedantic/analysis_options.yaml +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.includeFileNotFound] The URI 'package:pedantic/analysis_options.yaml' included in '/analysis_options.yaml' can't be found when analyzing '/'. ''', - [ - error( - diag.includeFileNotFound, - 97, - 38, - text: - "The URI 'package:pedantic/analysis_options.yaml' included in " - "'${convertPath('/analysis_options.yaml')}' can't be found " - "when analyzing '/'.", - ), - ], - ); + getFile('/included1.yaml'): '', + }); } Future test_notFound_nonexistent_notQuoted() async { - await assertErrorsInCode( - ''' + await assertDiagnosticsInCode(''' # We don't depend on pedantic, but we should consider adding it. include: package:pedantic/analysis_options.yaml -''', - [ - error( - diag.includeFileNotFound, - 74, - 38, - text: - "The URI 'package:pedantic/analysis_options.yaml' included in " - "'${convertPath('/analysis_options.yaml')}' can't be found " - "when analyzing '/'.", - ), - ], - ); +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.includeFileNotFound] The URI 'package:pedantic/analysis_options.yaml' included in '/analysis_options.yaml' can't be found when analyzing '/'. +'''); } Future test_notFound_nonexistent_singleQuoted() async { - await assertErrorsInCode( - ''' + await assertDiagnosticsInCode(''' # We don't depend on pedantic, but we should consider adding it. include: 'package:pedantic/analysis_options.yaml' -''', - [ - error( - diag.includeFileNotFound, - 74, - 40, - text: - "The URI 'package:pedantic/analysis_options.yaml' included in " - "'${convertPath('/analysis_options.yaml')}' can't be found " - "when analyzing '/'.", - ), - ], - ); +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.includeFileNotFound] The URI 'package:pedantic/analysis_options.yaml' included in '/analysis_options.yaml' can't be found when analyzing '/'. +'''); } } diff --git a/pkg/analyzer/test/src/diagnostics/analysis_options/include_file_warning_test.dart b/pkg/analyzer/test/src/diagnostics/analysis_options/include_file_warning_test.dart index 510bc06bf88..e913c06fed6 100644 --- a/pkg/analyzer/test/src/diagnostics/analysis_options/include_file_warning_test.dart +++ b/pkg/analyzer/test/src/diagnostics/analysis_options/include_file_warning_test.dart @@ -2,7 +2,6 @@ // 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:analyzer/src/diagnostic/diagnostic.dart' as diag; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'analysis_options_test_support.dart'; @@ -16,25 +15,16 @@ main() { @reflectiveTest class IncludeFileWarningTest extends AbstractAnalysisOptionsTest { Future test_fileWarning() async { - newFile('/a.yaml', ''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' +include: a.yaml +// ^^^^^^ +// [diag.includedFileWarning] Warning in the included options file /a.yaml(12..20): The option 'something' isn't supported by 'analyzer'. +''', + getFile('/a.yaml'): ''' analyzer: something: bad -'''); - await assertErrorsInCode( - ''' -include: a.yaml ''', - [ - error( - diag.includedFileWarning, - 9, - 6, - messageContains: [ - 'Warning in the included options file ${convertPath('/a.yaml')}', - ": The option 'something' isn't supported by 'analyzer'.", - ], - ), - ], - ); + }); } } diff --git a/pkg/analyzer/test/src/diagnostics/analysis_options/recursive_include_file_test.dart b/pkg/analyzer/test/src/diagnostics/analysis_options/recursive_include_file_test.dart index d9a918986ab..254d56be4fe 100644 --- a/pkg/analyzer/test/src/diagnostics/analysis_options/recursive_include_file_test.dart +++ b/pkg/analyzer/test/src/diagnostics/analysis_options/recursive_include_file_test.dart @@ -2,7 +2,6 @@ // 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:analyzer/src/diagnostic/diagnostic.dart' as diag; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'analysis_options_test_support.dart'; @@ -16,203 +15,135 @@ main() { @reflectiveTest class RecursiveIncludeFileTest extends AbstractAnalysisOptionsTest { Future test_itself() async { - await assertErrorsInCode( - ''' + await assertDiagnosticsInCode(''' include: analysis_options.yaml -''', - [ - error( - diag.recursiveIncludeFile, - 9, - 21, - text: - "The URI 'analysis_options.yaml' included in " - "'${convertPath('/analysis_options.yaml')}' includes " - "'${convertPath('/analysis_options.yaml')}', " - "creating a circular reference.", - ), - ], - ); +// ^^^^^^^^^^^^^^^^^^^^^ +// [diag.recursiveIncludeFile] The URI 'analysis_options.yaml' included in '/analysis_options.yaml' includes '/analysis_options.yaml', creating a circular reference. +'''); } Future test_itself_inList() async { - await assertErrorsInCode( - ''' + await assertDiagnosticsInCode(''' include: - analysis_options.yaml -''', - [ - error( - diag.recursiveIncludeFile, - 13, - 21, - text: - "The URI 'analysis_options.yaml' included in " - "'${convertPath('/analysis_options.yaml')}' includes " - "'${convertPath('/analysis_options.yaml')}', " - "creating a circular reference.", - ), - ], - ); +// ^^^^^^^^^^^^^^^^^^^^^ +// [diag.recursiveIncludeFile] The URI 'analysis_options.yaml' included in '/analysis_options.yaml' includes '/analysis_options.yaml', creating a circular reference. +'''); } Future test_notRecursive() async { - newFile('/a.yaml', ''' -include: b.yaml -'''); - newFile('/b.yaml', ''); - await assertNoErrorsInCode(''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' include: - a.yaml - b.yaml -'''); +''', + getFile('/a.yaml'): ''' +include: b.yaml +''', + getFile('/b.yaml'): '', + }); } Future test_notRecursive_included() async { - newFile('/a.yaml', ''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' +include: c.yaml +''', + getFile('/a.yaml'): ''' include: b.yaml -'''); - newFile('/b.yaml', ''); - newFile('/c.yaml', ''' +''', + getFile('/b.yaml'): '', + getFile('/c.yaml'): ''' include: - a.yaml - b.yaml -'''); - await assertNoErrorsInCode(''' -include: c.yaml -'''); +''', + }); } Future test_recursive() async { - newFile('/a.yaml', ''' -include: b.yaml -'''); - newFile('/b.yaml', ''' -include: analysis_options.yaml -'''); - await assertErrorsInCode( - ''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' include: a.yaml +// ^^^^^^ +// [diag.recursiveIncludeFile] The URI 'analysis_options.yaml' included in '/b.yaml' includes '/b.yaml', creating a circular reference. ''', - [ - error( - diag.recursiveIncludeFile, - 9, - 6, - text: - "The URI 'analysis_options.yaml' included in " - "'${convertPath('/b.yaml')}' includes " - "'${convertPath('/b.yaml')}', " - "creating a circular reference.", - ), - ], - ); + getFile('/a.yaml'): ''' +include: b.yaml +''', + getFile('/b.yaml'): ''' +include: analysis_options.yaml +''', + }); } Future test_recursive_itself() async { - newFile('/a.yaml', ''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' include: a.yaml -'''); - await assertErrorsInCode( - ''' +// ^^^^^^ +// [diag.includedFileWarning] Warning in the included options file /a.yaml(9..14): The file includes itself recursively. +''', + getFile('/a.yaml'): ''' include: a.yaml ''', - [ - error( - diag.includedFileWarning, - 9, - 6, - messageContains: [ - "Warning in the included options file ${convertPath('/a.yaml')}", - ": The file includes itself recursively.", - ], - ), - ], - ); + }); } Future test_recursive_listAtTop() async { - newFile('/a.yaml', ''' -include: b.yaml -'''); - newFile('/b.yaml', ''' -include: analysis_options.yaml -'''); - newFile('/empty.yaml', ''' -'''); - await assertErrorsInCode( - ''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' include: - empty.yaml - a.yaml +// ^^^^^^ +// [diag.recursiveIncludeFile] The URI 'analysis_options.yaml' included in '/b.yaml' includes '/b.yaml', creating a circular reference. ''', - [ - error( - diag.recursiveIncludeFile, - 28, - 6, - text: - "The URI 'analysis_options.yaml' included in " - "'${convertPath('/b.yaml')}' includes " - "'${convertPath('/b.yaml')}', " - "creating a circular reference.", - ), - ], - ); + getFile('/a.yaml'): ''' +include: b.yaml +''', + getFile('/b.yaml'): ''' +include: analysis_options.yaml +''', + getFile('/empty.yaml'): ''' +''', + }); } Future test_recursive_listIncluded() async { - newFile('/a.yaml', ''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' +include: a.yaml +// ^^^^^^ +// [diag.recursiveIncludeFile] The URI 'analysis_options.yaml' included in '/b.yaml' includes '/b.yaml', creating a circular reference. +''', + getFile('/a.yaml'): ''' include: - empty.yaml - b.yaml -'''); - newFile('/b.yaml', ''' -include: analysis_options.yaml -'''); - newFile('/empty.yaml', ''' -'''); - await assertErrorsInCode( - ''' -include: a.yaml ''', - [ - error( - diag.recursiveIncludeFile, - 9, - 6, - text: - "The URI 'analysis_options.yaml' included in " - "'${convertPath('/b.yaml')}' includes " - "'${convertPath('/b.yaml')}', " - "creating a circular reference.", - ), - ], - ); + getFile('/b.yaml'): ''' +include: analysis_options.yaml +''', + getFile('/empty.yaml'): ''' +''', + }); } Future test_recursive_notInBeginning() async { - newFile('/a.yaml', ''' -include: b.yaml -'''); - newFile('/b.yaml', ''' + await assertDiagnosticsInFiles({ + analysisOptionsFile: ''' include: a.yaml -'''); - await assertErrorsInCode( - ''' +// ^^^^^^ +// [diag.includedFileWarning] Warning in the included options file /a.yaml(9..14): The file includes itself recursively. +''', + getFile('/a.yaml'): ''' +include: b.yaml +''', + getFile('/b.yaml'): ''' include: a.yaml ''', - [ - error( - diag.includedFileWarning, - 9, - 6, - messageContains: [ - "Warning in the included options file ${convertPath('/a.yaml')}", - ": The file includes itself recursively.", - ], - ), - ], - ); + }); } } diff --git a/pkg/analyzer/test/src/diagnostics/analysis_options/test_all.dart b/pkg/analyzer/test/src/diagnostics/analysis_options/test_all.dart index ebc0ec8e7f3..7bafcfcc1fd 100644 --- a/pkg/analyzer/test/src/diagnostics/analysis_options/test_all.dart +++ b/pkg/analyzer/test/src/diagnostics/analysis_options/test_all.dart @@ -4,6 +4,7 @@ import 'package:test_reflective_loader/test_reflective_loader.dart'; +import '../../dart/resolution/node_text_expectations.dart'; import 'include_file_not_found_test.dart' as include_file_not_found; import 'include_file_warning_test.dart' as include_file_warning; import 'recursive_include_file_test.dart' as recursive_include_file; @@ -13,5 +14,6 @@ main() { include_file_not_found.main(); include_file_warning.main(); recursive_include_file.main(); + defineReflectiveTests(UpdateNodeTextExpectations); }); } diff --git a/pkg/analyzer/test/src/options/options_file_validator_test.dart b/pkg/analyzer/test/src/options/options_file_validator_test.dart index 2843e8c67d0..8ef01c1793b 100644 --- a/pkg/analyzer/test/src/options/options_file_validator_test.dart +++ b/pkg/analyzer/test/src/options/options_file_validator_test.dart @@ -8,26 +8,28 @@ import 'package:analyzer/analysis_rule/analysis_rule.dart'; import 'package:analyzer/diagnostic/diagnostic.dart'; import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/listener.dart'; +import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/source/file_source.dart'; import 'package:analyzer/src/analysis_options/analysis_options_provider.dart'; import 'package:analyzer/src/analysis_options/options_file_validator.dart'; import 'package:analyzer/src/context/source.dart'; -import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag; import 'package:analyzer/src/file_system/file_system.dart'; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/test_utilities/lint_registration_mixin.dart'; import 'package:analyzer_testing/resource_provider_mixin.dart'; -import 'package:analyzer_testing/src/analysis_rule/pub_package_resolution.dart'; +import 'package:analyzer_testing/src/expected_diagnostics.dart'; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; -import '../../generated/test_support.dart'; +import '../dart/resolution/node_text_expectations.dart'; +import '../diagnostics/analysis_options/analysis_options_test_support.dart'; main() { defineReflectiveSuite(() { defineReflectiveTests(ErrorCodeValuesTest); defineReflectiveTests(OptionsFileValidatorTest); defineReflectiveTests(OptionsProviderTest); + defineReflectiveTests(UpdateNodeTextExpectations); }); } @@ -78,7 +80,10 @@ class ErrorCodeValuesTest { @reflectiveTest class OptionsFileValidatorTest - with LintRegistrationMixin, ResourceProviderMixin { + with + LintRegistrationMixin, + ResourceProviderMixin, + AnalysisOptionsDiagnosticExpectationMixin { late final OptionsFileValidator validator = OptionsFileValidator( FileSource(newFile('/analysis_options.yaml', '')), isPrimarySource: true, @@ -95,14 +100,13 @@ class OptionsFileValidatorTest } test_analyzer_cannotIgnore_badValue() { - validate( - ''' + validate(''' analyzer: cannot-ignore: - not_an_error_code -''', - [diag.unrecognizedErrorCode], - ); +// ^^^^^^^^^^^^^^^^^ +// [diag.unrecognizedErrorCode] 'not_an_error_code' isn't a recognized diagnostic code. +'''); } test_analyzer_cannotIgnore_goodValue() { @@ -110,7 +114,7 @@ analyzer: analyzer: cannot-ignore: - invalid_annotation -''', []); +'''); } test_analyzer_cannotIgnore_lintRule() { @@ -119,18 +123,16 @@ analyzer: analyzer: cannot-ignore: - fantastic_test_rule -''', []); +'''); } test_analyzer_cannotIgnore_notAList() { - validate( - ''' + validate(''' analyzer: cannot-ignore: one_error_code: true -''', - [diag.invalidSectionFormat], - ); +// [diag.invalidSectionFormat][column 5][length 21] Invalid format for the 'cannot-ignore' section. +'''); } test_analyzer_cannotIgnore_severity() { @@ -138,58 +140,51 @@ analyzer: analyzer: cannot-ignore: - error -''', []); +'''); } test_analyzer_cannotIgnore_valueNotAString() { - validate( - ''' + validate(''' analyzer: cannot-ignore: one_error_code: +// [diag.invalidSectionFormat][column 5][length 31] Invalid format for the 'cannot-ignore' section. foo: bar -''', - [diag.invalidSectionFormat], - ); +'''); } test_analyzer_empty() { registerLintRule(TestRule()); validate(''' analyzer: -''', []); +'''); } test_analyzer_enableExperiment_badValue() { - validate( - ''' + validate(''' analyzer: enable-experiment: - not-an-experiment - ''', - [diag.unsupportedOptionWithoutValues], - ); +// ^^^^^^^^^^^^^^^^^ +// [diag.unsupportedOptionWithoutValues] The option 'not-an-experiment' isn't supported by 'enable-experiment'. + '''); } test_analyzer_enableExperiment_mapValue() { - validate( - ''' + validate(''' analyzer: enable-experiment: experiment: true - ''', - [diag.invalidSectionFormat], - ); +// [diag.invalidSectionFormat][column 5][length 21] Invalid format for the 'enable-experiment' section. + '''); } test_analyzer_enableExperiment_scalarValue() { - validate( - ''' + validate(''' analyzer: enable-experiment: 7 - ''', - [diag.invalidSectionFormat], - ); +// [diag.invalidSectionFormat][column 22][length 6] Invalid format for the 'enable-experiment' section. + '''); } test_analyzer_error_code_supported() { @@ -200,18 +195,17 @@ analyzer: invalid_assignment: warning assignment_of_do_not_store: error dead_code: info -''', []); +'''); } test_analyzer_error_code_supported_bad_value() { - var diagnostics = validate( - ''' + var diagnostics = validate(''' analyzer: errors: unused_local_variable: ftw - ''', - [diag.unsupportedOptionWithLegalValues], - ); +// ^^^ +// [diag.unsupportedOptionWithLegalValues] The option 'ftw' isn't supported by 'errors'. + '''); expect( diagnostics.single.problemMessage.messageText(includeUrl: false), contains("The option 'ftw'"), @@ -219,14 +213,13 @@ analyzer: } test_analyzer_error_code_supported_bad_value_null() { - var diagnostics = validate( - ''' + var diagnostics = validate(''' analyzer: errors: unused_local_variable: null - ''', - [diag.unsupportedOptionWithLegalValues], - ); +// ^^^^ +// [diag.unsupportedOptionWithLegalValues] The option 'null' isn't supported by 'errors'. + '''); expect( diagnostics.single.problemMessage.messageText(includeUrl: false), contains("The option 'null'"), @@ -234,14 +227,13 @@ analyzer: } test_analyzer_error_code_unsupported() { - var diagnostics = validate( - ''' + var diagnostics = validate(''' analyzer: errors: not_supported: ignore - ''', - [diag.unrecognizedErrorCode], - ); +// ^^^^^^^^^^^^^ +// [diag.unrecognizedErrorCode] 'not_supported' isn't a recognized diagnostic code. + '''); expect( diagnostics.single.problemMessage.messageText(includeUrl: false), contains("'not_supported' isn't a recognized diagnostic code"), @@ -249,14 +241,13 @@ analyzer: } test_analyzer_error_code_unsupported_null() { - var diagnostics = validate( - ''' + var diagnostics = validate(''' analyzer: errors: null: ignore - ''', - [diag.unrecognizedErrorCode], - ); +// ^^^^ +// [diag.unrecognizedErrorCode] 'null' isn't a recognized diagnostic code. + '''); expect( diagnostics.single.problemMessage.messageText(includeUrl: false), contains("'null' isn't a recognized diagnostic code"), @@ -264,60 +255,54 @@ analyzer: } test_analyzer_errors_notAMap() { - validate( - ''' + validate(''' analyzer: errors: - invalid_annotation +// [diag.invalidSectionFormat][column 5][length 45] Invalid format for the 'enable-experiment' section. - unused_import - ''', - [diag.invalidSectionFormat], - ); + '''); } test_analyzer_errors_valueNotAScalar() { - validate( - ''' + validate(''' analyzer: errors: invalid_annotation: ignore unused_import: [1, 2, 3] - ''', - [diag.invalidSectionFormat], - ); +// ^^^^^^^^^ +// [diag.invalidSectionFormat] Invalid format for the 'enable-experiment' section. + '''); } test_analyzer_language_bad_format_list() { - validate( - ''' + validate(''' analyzer: language: - notAnOption: true -''', - [diag.invalidSectionFormat], - ); +// [diag.invalidSectionFormat][column 5][length 20] Invalid format for the 'language' section. +'''); } test_analyzer_language_bad_format_scalar() { - validate( - ''' + validate(''' analyzer: language: true -''', - [diag.invalidSectionFormat], - ); +// ^^^^ +// [diag.invalidSectionFormat] Invalid format for the 'language' section. +'''); } - @FailingTest(reason: 'Enable when we deprecate strict-raw-types') + // TODO(srawlins): Enable when we deprecate strict-raw-types. + @SkippedTest(reason: 'Enable when we deprecate strict-raw-types') test_analyzer_language_strictRawTypes_deprecated() { - validate( - ''' + validate(''' analyzer: language: strict-raw-types: true -''', - [diag.analysisOptionDeprecated], - ); +// ^^^^^^^^^^^^^^^^ +// [diag.analysisOptionDeprecated] The option 'strict-raw-types' is no longer supported. +'''); } test_analyzer_language_strictRawTypes_notDeprecatedIfFalse() { @@ -325,25 +310,24 @@ analyzer: analyzer: language: strict-raw-types: false -''', []); +'''); } test_analyzer_language_supports_empty() { validate(''' analyzer: language: -''', []); +'''); } test_analyzer_language_unsupported_key() { - validate( - ''' + validate(''' analyzer: language: unsupported: true -''', - [diag.unsupportedOptionWithLegalValues], - ); +// ^^^^^^^^^^^ +// [diag.unsupportedOptionWithLegalValues] The option 'unsupported' isn't supported by 'language'. +'''); } test_analyzer_lint_codes_recognized() { @@ -352,16 +336,14 @@ analyzer: analyzer: errors: fantastic_test_rule: ignore -''', []); +'''); } test_analyzer_scalarValue() { - validate( - ''' + validate(''' analyzer: 7 - ''', - [diag.invalidSectionFormat], - ); +// [diag.invalidSectionFormat][column 11][length 6] Invalid format for the 'cannot-ignore' section. + '''); } test_analyzer_supported_exclude() { @@ -369,17 +351,16 @@ analyzer: 7 analyzer: exclude: - test/_data/p4/lib/lib1.dart -''', []); +'''); } test_analyzer_unsupported_option() { - validate( - ''' + validate(''' analyzer: not_supported: true -''', - [diag.unsupportedOptionWithLegalValues], - ); +//^^^^^^^^^^^^^ +// [diag.unsupportedOptionWithLegalValues] The option 'not_supported' isn't supported by 'analyzer'. +'''); } test_chromeos_manifest_checks() { @@ -387,234 +368,214 @@ analyzer: analyzer: optional-checks: chrome-os-manifest-checks -''', []); +'''); } test_chromeos_manifest_checks_invalid() { - validate( - ''' + validate(''' analyzer: optional-checks: chromeos-manifest -''', - [diag.unsupportedOptionWithLegalValues], - ); +// ^^^^^^^^^^^^^^^^^ +// [diag.unsupportedOptionWithLegalValues] The option 'chromeos-manifest' isn't supported by ''chrome-os-manifest-checks' or 'propagate-linter-exceptions''. +'''); } test_chromeos_manifest_checks_notAMap() { - validate( - ''' + validate(''' analyzer: optional-checks: - chrome-os-manifest-checks -''', - [diag.invalidSectionFormat], - ); +// [diag.invalidSectionFormat][column 5][length 28] Invalid format for the 'enable-experiment' section. +'''); } test_codeStyle_format_bool_false() { validate(''' code-style: format: false -''', []); +'''); } test_codeStyle_format_bool_true() { validate(''' code-style: format: true -''', []); +'''); } test_codeStyle_format_invalid() { - validate( - ''' + validate(''' code-style: format: 80 -''', - [diag.unsupportedValue], - ); +// ^^ +// [diag.unsupportedValue] The value '80' isn't supported by 'format'. +'''); } test_codeStyle_format_string_false() { validate(''' code-style: format: "false" -''', []); +'''); } test_codeStyle_format_string_true() { validate(''' code-style: format: "true" -''', []); +'''); } test_codeStyle_format_string_true_mixedCase() { validate(''' code-style: format: "True" -''', []); +'''); } test_codeStyle_format_string_true_upperCase() { validate(''' code-style: format: "TRUE" -''', []); +'''); } test_codeStyle_nonMap() { - validate( - ''' + validate(''' code-style: 7 -''', - [diag.invalidSectionFormat], - ); +// ^ +// [diag.invalidSectionFormat] Invalid format for the 'code-style' section. +'''); } test_codeStyle_unsupported_list() { - validate( - ''' + validate(''' code-style: - format -''', - [diag.invalidSectionFormat], - ); +// [diag.invalidSectionFormat][column 3][length 9] Invalid format for the 'code-style' section. +'''); } test_codeStyle_unsupported_scalar() { - validate( - ''' + validate(''' code-style: format -''', - [diag.invalidSectionFormat], - ); +// ^^^^^^ +// [diag.invalidSectionFormat] Invalid format for the 'code-style' section. +'''); } test_codeStyle_unsupportedOption() { - validate( - ''' + validate(''' code-style: not_supported: true -''', - [diag.unsupportedOptionWithoutValues], - ); +//^^^^^^^^^^^^^ +// [diag.unsupportedOptionWithoutValues] The option 'not_supported' isn't supported by 'code-style'. +'''); } test_formatter_invalid_key() { - validate( - ''' + validate(''' formatter: wrong: 123 -''', - [diag.unsupportedOptionWithoutValues], - ); +//^^^^^ +// [diag.unsupportedOptionWithoutValues] The option 'wrong' isn't supported by 'formatter'. +'''); } test_formatter_invalid_keys() { - validate( - ''' + validate(''' formatter: wrong: 123 +//^^^^^ +// [diag.unsupportedOptionWithoutValues] The option 'wrong' isn't supported by 'formatter'. wrong2: 123 -''', - [ - diag.unsupportedOptionWithoutValues, - diag.unsupportedOptionWithoutValues, - ], - ); +//^^^^^^ +// [diag.unsupportedOptionWithoutValues] The option 'wrong2' isn't supported by 'formatter'. +'''); } test_formatter_pageWidth_invalid_decimal() { - validate( - ''' + validate(''' formatter: page_width: 123.45 -''', - [diag.invalidOption], - ); +// ^^^^^^ +// [diag.invalidOption] Invalid option specified for 'page_width': "page_width" must be a positive integer. +'''); } test_formatter_pageWidth_invalid_negativeInteger() { - validate( - ''' + validate(''' formatter: page_width: -123 -''', - [diag.invalidOption], - ); +// ^^^^ +// [diag.invalidOption] Invalid option specified for 'page_width': "page_width" must be a positive integer. +'''); } test_formatter_pageWidth_invalid_string() { - validate( - ''' + validate(''' formatter: page_width: "123" -''', - [diag.invalidOption], - ); +// ^^^^^ +// [diag.invalidOption] Invalid option specified for 'page_width': "page_width" must be a positive integer. +'''); } test_formatter_pageWidth_invalid_zero() { - validate( - ''' + validate(''' formatter: page_width: 0 -''', - [diag.invalidOption], - ); +// ^ +// [diag.invalidOption] Invalid option specified for 'page_width': "page_width" must be a positive integer. +'''); } test_formatter_pageWidth_valid_integer() { validate(''' formatter: page_width: 123 -''', []); +'''); } test_formatter_trailingCommas_invalid_map() { - validate( - ''' + validate(''' formatter: trailing_commas: a: b -''', - [diag.invalidOption], - ); +// [diag.invalidOption][column 5][length 5] Invalid option specified for 'trailing_commas': "trailing_commas" must be "automate" or "preserve". +'''); } test_formatter_trailingCommas_invalid_numeric() { - validate( - ''' + validate(''' formatter: trailing_commas: 1 -''', - [diag.invalidOption], - ); +// ^ +// [diag.invalidOption] Invalid option specified for 'trailing_commas': "trailing_commas" must be "automate" or "preserve". +'''); } test_formatter_trailingCommas_invalid_string() { - validate( - ''' + validate(''' formatter: trailing_commas: foo -''', - [diag.invalidOption], - ); +// ^^^ +// [diag.invalidOption] Invalid option specified for 'trailing_commas': "trailing_commas" must be "automate" or "preserve". +'''); } test_formatter_trailingCommas_valid() { validate(''' formatter: trailing_commas: automate -''', []); +'''); } test_formatter_valid_empty() { validate(''' formatter: -''', []); +'''); } test_linter_supported_rules() { @@ -623,17 +584,16 @@ formatter: linter: rules: - fantastic_test_rule - ''', []); + '''); } test_linter_unsupported_option() { - validate( - ''' + validate(''' linter: unsupported: true - ''', - [diag.unsupportedOptionWithLegalValue], - ); +//^^^^^^^^^^^ +// [diag.unsupportedOptionWithLegalValue] The option 'unsupported' isn't supported by 'linter'. + '''); } test_plugins_dependencyOverrides() { @@ -642,42 +602,39 @@ plugins: dependency_overrides: one: git: https://github.com/dart-lang/linter.git -''', []); +'''); } test_plugins_dependencyOverrides_invalid_mapKey() { - validate( - ''' + validate(''' plugins: dependency_overrides: one: ppath: foo/bar -''', - [diag.unsupportedOptionWithLegalValues], - ); +// ^^^^^ +// [diag.unsupportedOptionWithLegalValues] The option 'ppath' isn't supported by 'plugins/dependency_overrides/one'. +'''); } test_plugins_diagnostics_invalid() { - validate( - ''' + validate(''' plugins: one: diagnostics: code: abc -''', - [diag.unsupportedOptionWithLegalValues], - ); +// ^^^ +// [diag.unsupportedOptionWithLegalValues] The option 'abc' isn't supported by 'plugins/one/diagnostics'. +'''); } test_plugins_diagnostics_notAMap() { - validate( - ''' + validate(''' plugins: one: diagnostics: 7 -''', - [diag.invalidSectionFormat], - ); +// ^ +// [diag.invalidSectionFormat] Invalid format for the 'plugins/one/diagnostics' section. +'''); } test_plugins_diagnostics_supported_severity() { @@ -689,7 +646,7 @@ plugins: code2: warning code3: error code4: info -''', []); +'''); } test_plugins_diagnostics_supported_trueOrFalse() { @@ -699,18 +656,17 @@ plugins: diagnostics: code1: true code2: false -''', []); +'''); } test_plugins_each_invalid_mapKey() { - validate( - ''' + validate(''' plugins: one: ppath: foo/bar -''', - [diag.unsupportedOptionWithLegalValues], - ); +// ^^^^^ +// [diag.unsupportedOptionWithLegalValues] The option 'ppath' isn't supported by 'plugins/one'. +'''); } test_plugins_each_valid_mapKey() { @@ -718,40 +674,38 @@ plugins: plugins: one: path: foo/bar -''', []); +'''); } test_plugins_each_valid_scalar() { validate(''' plugins: one: ^1.2.3 -''', []); +'''); } test_plugins_git_invalid_key() { - validate( - ''' + validate(''' plugins: one: git: url: https://github.com/dart-lang/linter.git invalid: main -''', - [diag.unsupportedOptionWithLegalValues], - ); +// ^^^^^^^ +// [diag.unsupportedOptionWithLegalValues] The option 'invalid' isn't supported by 'plugins/one/git'. +'''); } test_plugins_git_invalid_value() { - validate( - ''' + validate(''' plugins: one: git: url: https://github.com/dart-lang/linter.git ref: 7 -''', - [diag.invalidSectionFormat], - ); +// ^ +// [diag.invalidSectionFormat] Invalid format for the 'plugins/one/git/ref' section. +'''); } test_plugins_git_map() { @@ -763,7 +717,7 @@ plugins: ref: main path: pkg/linter tag_pattern: 'v*' -''', []); +'''); } test_plugins_git_scalar() { @@ -771,22 +725,21 @@ plugins: plugins: one: git: https://github.com/dart-lang/linter.git -''', []); +'''); } test_plugins_invalid_scalar() { - validate( - ''' + validate(''' plugins: 7 -''', - [diag.invalidSectionFormat], - ); +// ^ +// [diag.invalidSectionFormat] Invalid format for the 'plugins' section. +'''); } test_plugins_valid_empty() { validate(''' plugins: -''', []); +'''); } test_propagate_linter_exceptions() { @@ -794,7 +747,7 @@ plugins: analyzer: optional-checks: propagate-linter-exceptions -''', []); +'''); } test_propagate_linter_exceptions_mapKey() { @@ -802,14 +755,15 @@ analyzer: analyzer: optional-checks: propagate-linter-exceptions: true -''', []); +'''); } - List validate(String source, List expected) { - var optionsFile = newFile('/analysis_options.yaml', source); + List validate(String source) { + var cleanSource = removeDiagnosticExpectations(source); + var optionsFile = newFile('/analysis_options.yaml', cleanSource); var sourceUrl = optionsFile.toUri(); var options = optionsProvider.getOptionsFromString( - source, + cleanSource, sourceUrl: sourceUrl, ); var diagnosticListener = RecordingDiagnosticListener(); @@ -818,71 +772,25 @@ analyzer: DiagnosticReporter(diagnosticListener, FileSource(optionsFile)), ); var diagnostics = diagnosticListener.diagnostics; - expect( - diagnostics.map((Diagnostic e) => e.diagnosticCode), - unorderedEquals(expected), + assertDiagnosticMarkersInFiles( + codeByFile: {optionsFile: source}, + diagnostics: diagnostics, ); return diagnostics; } } @reflectiveTest -class OptionsProviderTest with ResourceProviderMixin { +class OptionsProviderTest + with ResourceProviderMixin, AnalysisOptionsDiagnosticExpectationMixin { late final SourceFactory sourceFactory; late final AnalysisOptionsProvider provider; - String get optionsFilePath => '/analysis_options.yaml'; + File get analysisOptionsFile => getFile('/analysis_options.yaml'); - void assertErrorsInList( - List diagnostics, - List expectedErrors, - ) { - GatheringDiagnosticListener diagnosticListener = - GatheringDiagnosticListener(); - diagnosticListener.addAll(diagnostics); - diagnosticListener.assertErrors(expectedErrors); - } - - void assertErrorsInOptionsFile( - String code, - List expectedErrors, - ) { - newFile(optionsFilePath, code); - var diagnostics = AnalysisOptionsAnalyzer( - initialSource: sourceFactory.forUri2(toUri(optionsFilePath))!, - sourceFactory: sourceFactory, - contextRoot: convertPath('/'), - sdkVersionConstraint: null, - resourceProvider: resourceProvider, - ).walkIncludes(content: code); - - assertErrorsInList(diagnostics, expectedErrors); - } - - ExpectedError error( - DiagnosticCode code, - int offset, - int length, { - Pattern? correctionContains, - // TODO(FMorschel): refactor the uses of this to prefer `messageContains` - String? text, - List messageContains = const [], - List contextMessages = - const [], - }) { - assert( - text == null || messageContains.isEmpty, - 'Only use one of text or messageContains', - ); - return ExpectedError( - code, - offset, - length, - correctionContains: correctionContains, - messageContainsAll: text != null ? [text] : messageContains, - contextMessages: contextMessages, - ); + void assertDiagnosticsInOptionsFile(File file, String code) { + _assertDiagnosticsInOptionsFiles(file, {file: code}); } void setUp() { @@ -894,26 +802,14 @@ class OptionsProviderTest with ResourceProviderMixin { // Test that the appropriate error is issued if `analysis_options.yaml` // tries to include another options file which in turn includes // `analysis_options.yaml`. - var other = newFile('/other_options.yaml', r''' + newFile('/other_options.yaml', r''' include: analysis_options.yaml '''); - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' include: other_options.yaml -''', - [ - error( - diag.recursiveIncludeFile, - 9, - 18, - messageContains: [ - "The URI 'analysis_options.yaml' included in " - "'${other.path}' includes '${other.path}', " - "creating a circular reference.", - ], - ), - ], - ); +// ^^^^^^^^^^^^^^^^^^ +// [diag.recursiveIncludeFile] The URI 'analysis_options.yaml' included in '/other_options.yaml' includes '/other_options.yaml', creating a circular reference. +'''); } test_circularInclude_nontrivial_nested() { @@ -922,55 +818,30 @@ include: other_options.yaml // Note: comments ensure that the `include` directives in each file are at // different file offsets, so that we can validate that the reported source // ranges are correct. - var other1 = newFile('/other_options1.yaml', r''' + newFile('/other_options1.yaml', r''' include: other_options2.yaml '''); newFile('/other_options2.yaml', r''' # comment include: other_options1.yaml '''); - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' # comment # comment include: other_options1.yaml -''', - [ - error( - diag.includedFileWarning, - 29, - 19, - messageContains: [ - "Warning in the included options file " - "${other1.path}(9..27): The file includes itself " - "recursively.", - ], - ), - ], - ); +// ^^^^^^^^^^^^^^^^^^^ +// [diag.includedFileWarning] Warning in the included options file /other_options1.yaml(9..27): The file includes itself recursively. +'''); } test_circularInclude_trivial_direct() { // Test that the appropriate error is issued if `analysis_options.yaml` // tries to include itself. - var convertedPath = convertPath(optionsFilePath); - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' include: analysis_options.yaml -''', - [ - error( - diag.recursiveIncludeFile, - 9, - 21, - messageContains: [ - "The URI 'analysis_options.yaml' included in " - "'$convertedPath' includes '$convertedPath', " - "creating a circular reference.", - ], - ), - ], - ); +// ^^^^^^^^^^^^^^^^^^^^^ +// [diag.recursiveIncludeFile] The URI 'analysis_options.yaml' included in '/analysis_options.yaml' includes '/analysis_options.yaml', creating a circular reference. +'''); } test_circularInclude_trivial_nested() { @@ -979,37 +850,25 @@ include: analysis_options.yaml // Note: comments ensure that the `include` directives in each file are at // different file offsets, so that we can validate that the reported source // ranges are correct. - var other = newFile('/other_options.yaml', r''' + newFile('/other_options.yaml', r''' include: other_options.yaml '''); - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' # comment include: other_options.yaml -''', - [ - error( - diag.includedFileWarning, - 19, - 18, - messageContains: [ - "Warning in the included options file ${other.path}(9..26): " - "The file includes itself recursively.", - ], - ), - ], - ); +// ^^^^^^^^^^^^^^^^^^ +// [diag.includedFileWarning] Warning in the included options file /other_options.yaml(9..26): The file includes itself recursively. +'''); } test_invalidYaml_direct() { - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' formatter: page_width: 80 page_width: 90 -''', - [error(diag.parseError, 30, 10)], - ); +//^^^^^^^^^^ +// [diag.parseError] Duplicate mapping key. +'''); } test_invalidYaml_nested() { @@ -1018,12 +877,11 @@ formatter: page_width: 80 page_width: 90 '''); - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' include: other_options.yaml -''', - [error(diag.includedFileParseError, 9, 18)], - ); +// ^^^^^^^^^^^^^^^^^^ +// [diag.includedFileParseError] Duplicate mapping key. in /other_options.yaml(30..40) +'''); } test_multiplePlugins_firstIsDirectlyIncluded_secondIsDirect_listForm() { @@ -1032,15 +890,14 @@ analyzer: plugins: - plugin_one '''); - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' include: other_options.yaml analyzer: plugins: - plugin_two -''', - [error(diag.multiplePlugins, 55, 10)], - ); +// ^^^^^^^^^^ +// [diag.multiplePlugins] Multiple plugins can't be enabled. +'''); } test_multiplePlugins_firstIsDirectlyIncluded_secondIsDirect_mapForm() { @@ -1049,16 +906,15 @@ analyzer: plugins: - plugin_one '''); - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' include: other_options.yaml analyzer: plugins: plugin_two: +// ^^^^^^^^^^ +// [diag.multiplePlugins] Multiple plugins can't be enabled. foo: bar -''', - [error(diag.multiplePlugins, 53, 10)], - ); +'''); } test_multiplePlugins_firstIsDirectlyIncluded_secondIsDirect_scalarForm() { @@ -1067,14 +923,13 @@ analyzer: plugins: - plugin_one '''); - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' include: other_options.yaml analyzer: plugins: plugin_two -''', - [error(diag.multiplePlugins, 49, 10)], - ); +// ^^^^^^^^^^ +// [diag.multiplePlugins] Multiple plugins can't be enabled. +'''); } test_multiplePlugins_firstIsIndirectlyIncluded_secondIsDirect() { @@ -1086,15 +941,14 @@ analyzer: newFile('/other_options.yaml', ''' include: more_options.yaml '''); - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' include: other_options.yaml analyzer: plugins: - plugin_two -''', - [error(diag.multiplePlugins, 55, 10)], - ); +// ^^^^^^^^^^ +// [diag.multiplePlugins] Multiple plugins can't be enabled. +'''); } test_multiplePlugins_firstIsIndirectlyIncluded_secondIsDirectlyIncluded() { @@ -1109,140 +963,110 @@ analyzer: plugins: - plugin_two '''); - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' include: other_options.yaml -''', - [error(diag.includedFileWarning, 9, 18)], - ); +// ^^^^^^^^^^^^^^^^^^ +// [diag.includedFileWarning] Warning in the included options file /other_options.yaml(54..63): Multiple plugins can't be enabled. +'''); } test_multiplePlugins_multipleDirect_listForm() { - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' analyzer: plugins: - plugin_one - plugin_two +// ^^^^^^^^^^ +// [diag.multiplePlugins] Multiple plugins can't be enabled. - plugin_three -''', - [ - error(diag.multiplePlugins, 44, 10), - error(diag.multiplePlugins, 61, 12), - ], - ); +// ^^^^^^^^^^^^ +// [diag.multiplePlugins] Multiple plugins can't be enabled. +'''); } test_multiplePlugins_multipleDirect_listForm_nonString() { - assertErrorsInOptionsFile(r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' analyzer: plugins: - 7 - plugin_one -''', []); +'''); } test_multiplePlugins_multipleDirect_listForm_sameName() { - assertErrorsInOptionsFile(r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' analyzer: plugins: - plugin_one - plugin_one -''', []); +'''); } test_multiplePlugins_multipleDirect_mapForm() { - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' analyzer: plugins: plugin_one: yes plugin_two: sure -''', - [error(diag.multiplePlugins, 45, 10)], - ); +// ^^^^^^^^^^ +// [diag.multiplePlugins] Multiple plugins can't be enabled. +'''); } test_multiplePlugins_multipleDirect_mapForm_sameName() { - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' analyzer: plugins: plugin_one: yes plugin_one: sure -''', - [error(diag.parseError, 45, 10)], - ); +// ^^^^^^^^^^ +// [diag.parseError] Duplicate mapping key. +'''); } test_nonExistentInclude_direct() { - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' include: other_options.yaml -''', - [error(diag.includeFileNotFound, 9, 18)], - ); +// ^^^^^^^^^^^^^^^^^^ +// [diag.includeFileNotFound] The URI 'other_options.yaml' included in '/analysis_options.yaml' can't be found when analyzing '/'. +'''); } test_nonExistentInclude_nested() { newFile('/other_options1.yaml', r''' include: other_options2.yaml '''); - assertErrorsInOptionsFile( - r''' + assertDiagnosticsInOptionsFile(analysisOptionsFile, r''' include: other_options1.yaml -''', - [error(diag.includeFileNotFound, 9, 19)], - ); +// ^^^^^^^^^^^^^^^^^^^ +// [diag.includeFileNotFound] The URI 'other_options2.yaml' included in '/other_options1.yaml' can't be found when analyzing '/'. +'''); } test_pluginsInInnerOptions() { - var code = ''' -plugins: - one: ^1.0.0 -'''; - var filePath = '/inner/analysis_options.yaml'; - newFile(filePath, code); newFile('/pubspec.yaml', ''' name: test version: 0.0.1 '''); - var diagnostics = AnalysisOptionsAnalyzer( - initialSource: sourceFactory.forUri2(toUri(filePath))!, - sourceFactory: sourceFactory, - contextRoot: convertPath('/'), - sdkVersionConstraint: null, - resourceProvider: resourceProvider, - ).walkIncludes(content: code); - - assertErrorsInList(diagnostics, [ - error(diag.pluginsInInnerOptions, 11, 12), - ]); + assertDiagnosticsInOptionsFile(getFile('/inner/analysis_options.yaml'), ''' +plugins: + one: ^1.0.0 +// [diag.pluginsInInnerOptions][column 3][length 12] Plugins can only be specified in the root of a pub workspace or the root of a package that isn't in a workspace. +'''); } test_pluginsInInnerOptions_included() { - newFile(optionsFilePath, ''' + newFile('/analysis_options.yaml', ''' plugins: one: ^1.0.0 '''); - var code = ''' -include: ../analysis_options.yaml -'''; - var filePath = '/inner/analysis_options.yaml'; - newFile(filePath, code); newFile('/pubspec.yaml', ''' name: test version: 0.0.1 '''); - var diagnostics = AnalysisOptionsAnalyzer( - initialSource: sourceFactory.forUri2(toUri(filePath))!, - sourceFactory: sourceFactory, - contextRoot: convertPath('/'), - sdkVersionConstraint: null, - resourceProvider: resourceProvider, - ).walkIncludes(content: code); - - assertErrorsInList(diagnostics, []); + assertDiagnosticsInOptionsFile(getFile('/inner/analysis_options.yaml'), ''' +include: ../analysis_options.yaml +'''); } test_pluginsInInnerOptions_included_notAtContextRoot() { @@ -1252,39 +1076,32 @@ version: 0.0.1 plugins: one: ^1.0.0 '''); - var code = ''' -include: ../inner2/analysis_options.yaml -'''; - newFile(inner1Path, code); newFile('/pubspec.yaml', ''' name: test version: 0.0.1 '''); - var diagnostics = AnalysisOptionsAnalyzer( - initialSource: sourceFactory.forUri2(toUri(inner1Path))!, - sourceFactory: sourceFactory, - contextRoot: convertPath('/'), - sdkVersionConstraint: null, - resourceProvider: resourceProvider, - ).walkIncludes(content: code); - - assertErrorsInList(diagnostics, []); + assertDiagnosticsInOptionsFile(getFile(inner1Path), ''' +include: ../inner2/analysis_options.yaml +'''); } - List validate(String code, List expected) { - newFile(optionsFilePath, code); + void _assertDiagnosticsInOptionsFiles( + File initialFile, + Map codeByFile, + ) { + var cleanCodeByFile = writeFilesWithoutDiagnosticExpectations(codeByFile); var diagnostics = AnalysisOptionsAnalyzer( - initialSource: sourceFactory.forUri2(toUri(optionsFilePath))!, + initialSource: sourceFactory.forUri2(initialFile.toUri())!, sourceFactory: sourceFactory, contextRoot: convertPath('/'), sdkVersionConstraint: null, resourceProvider: resourceProvider, - ).walkIncludes(content: code); - expect( - diagnostics.map((Diagnostic e) => e.diagnosticCode), - unorderedEquals(expected), + ).walkIncludes(content: cleanCodeByFile[initialFile]!); + + assertDiagnosticMarkersInFiles( + codeByFile: codeByFile, + diagnostics: diagnostics, ); - return diagnostics; } } diff --git a/pkg/analyzer/test/src/options/options_rule_validator_test.dart b/pkg/analyzer/test/src/options/options_rule_validator_test.dart index ea9bad73ea5..3da4c37c9f3 100644 --- a/pkg/analyzer/test/src/options/options_rule_validator_test.dart +++ b/pkg/analyzer/test/src/options/options_rule_validator_test.dart @@ -6,17 +6,15 @@ import 'package:analyzer/analysis_rule/analysis_rule.dart'; import 'package:analyzer/analysis_rule/rule_state.dart'; import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/listener.dart'; +import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/source/file_source.dart'; import 'package:analyzer/src/analysis_options/analysis_options_provider.dart'; -import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag; import 'package:analyzer/src/lint/options_rule_validator.dart'; -import 'package:analyzer/src/test_utilities/test_code_format.dart'; -import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'package:yaml/yaml.dart'; -import '../../generated/test_support.dart'; +import '../dart/resolution/node_text_expectations.dart'; import '../diagnostics/analysis_options/analysis_options_test_support.dart'; main() { @@ -24,6 +22,7 @@ main() { defineReflectiveTests(OptionsRuleValidatorIncludedFileTest); defineReflectiveTests(OptionsRuleValidatorTest); defineReflectiveTests(OptionsRuleValidatorValueTest); + defineReflectiveTests(UpdateNodeTextExpectations); }); } @@ -67,7 +66,7 @@ linter: rules: rule_pos: true '''); - assertNoErrors(''' + assertDiagnostics(''' include: - included1.yaml - included2.yaml @@ -81,48 +80,35 @@ linter: - deprecated_lint '''); - assertNoErrors(''' + assertDiagnostics(''' include: included.yaml '''); } Future test_incompatible_multiple_include() async { - var included1Code = TestCode.parse(''' + await assertDiagnosticsInFiles({ + getFile('/included1.yaml'): ''' linter: rules: - [!rule_neg!]: true -'''); - var included1 = newFile('/included1.yaml', included1Code.code); - var included2Code = TestCode.parse(''' + rule_neg: true +// ^^^^^^^^ +// [context 2] The rule 'rule_neg' is enabled here. +''', + getFile('/included2.yaml'): ''' linter: rules: - [!rule_pos!]: true -'''); - var included2 = newFile('/included2.yaml', included2Code.code); - var testCode = TestCode.parse(''' + rule_pos: true +// ^^^^^^^^ +// [context 1] The rule 'rule_pos' is enabled here. +''', + analysisOptionsFile: ''' include: - included1.yaml - - [!included2.yaml!] -'''); - await assertErrorsInCode(testCode.code, [ - error( - diag.incompatibleLintIncluded, - testCode.range.sourceRange.offset, - testCode.range.sourceRange.length, - contextMessages: [ - contextMessage( - included2, - included2Code.range.sourceRange.offset, - included2Code.range.sourceRange.length, - ), - contextMessage( - included1, - included1Code.range.sourceRange.offset, - included1Code.range.sourceRange.length, - ), - ], - ), - ]); + - included2.yaml +// ^^^^^^^^^^^^^^ +// [diag.incompatibleLintIncluded][context 1][context 2] The rule 'included2.yaml' is incompatible with 'rule_pos' and 'rule_neg', which is included from 2 files. +''', + }); } Future test_incompatible_multiple_include_disabled() async { @@ -136,7 +122,7 @@ linter: rules: rule_pos: true '''); - await assertNoErrorsInCode(''' + await assertDiagnosticsInCode(''' include: - included1.yaml - included2.yaml @@ -148,91 +134,88 @@ linter: } Future test_incompatible_multiple_include_list() async { - var included1Code = TestCode.parse(''' -linter: - rules: - - [!rule_neg!] -'''); - var included1 = newFile('/included1.yaml', included1Code.code); - var included2Code = TestCode.parse(''' -linter: - rules: - - [!rule_pos!] -'''); - var included2 = newFile('/included2.yaml', included2Code.code); - var testCode = TestCode.parse(''' -include: - - included1.yaml - - [!included2.yaml!] -'''); - await assertErrorsInCode(testCode.code, [ - error( - diag.incompatibleLintIncluded, - testCode.range.sourceRange.offset, - testCode.range.sourceRange.length, - contextMessages: [ - contextMessage( - included2, - included2Code.range.sourceRange.offset, - included2Code.range.sourceRange.length, - ), - contextMessage( - included1, - included1Code.range.sourceRange.offset, - included1Code.range.sourceRange.length, - ), - ], - ), - ]); - } - - Future test_incompatible_multiple_include_noLintMainFile() async { - newFile('/included1.yaml', ''' + await assertDiagnosticsInFiles({ + getFile('/included1.yaml'): ''' linter: rules: - rule_neg -'''); - newFile('/included2.yaml', ''' +// ^^^^^^^^ +// [context 2] The rule 'rule_neg' is enabled here. +''', + getFile('/included2.yaml'): ''' linter: rules: - rule_pos -'''); - assertErrors( - ''' +// ^^^^^^^^ +// [context 1] The rule 'rule_pos' is enabled here. +''', + analysisOptionsFile: ''' include: - included1.yaml - included2.yaml +// ^^^^^^^^^^^^^^ +// [diag.incompatibleLintIncluded][context 1][context 2] The rule 'included2.yaml' is incompatible with 'rule_pos' and 'rule_neg', which is included from 2 files. +''', + }); + } + + Future test_incompatible_multiple_include_noLintMainFile() async { + assertRuleDiagnosticsInFiles({ + getFile('/included1.yaml'): ''' +linter: + rules: + - rule_neg +// ^^^^^^^^ +// [context 2] The rule 'rule_neg' is enabled here. +''', + getFile('/included2.yaml'): ''' +linter: + rules: + - rule_pos +// ^^^^^^^^ +// [context 1] The rule 'rule_pos' is enabled here. +''', + analysisOptionsFile: ''' +include: + - included1.yaml + - included2.yaml +// ^^^^^^^^^^^^^^ +// [diag.incompatibleLintIncluded][context 1][context 2] The rule 'included2.yaml' is incompatible with 'rule_pos' and 'rule_neg', which is included from 2 files. linter: rules: ''', - [diag.incompatibleLintIncluded], - ); + }); } Future test_incompatible_multiple_include_noLintMainFile_mixedCase() async { - newFile('/included1.yaml', ''' + assertRuleDiagnosticsInFiles({ + getFile('/included1.yaml'): ''' linter: rules: - ruLe_neg -'''); - newFile('/included2.yaml', ''' +// ^^^^^^^^ +// [context 2] The rule 'ruLe_neg' is enabled here. +''', + getFile('/included2.yaml'): ''' linter: rules: - rule_poS -'''); - assertErrors( - ''' +// ^^^^^^^^ +// [context 1] The rule 'rule_poS' is enabled here. +''', + analysisOptionsFile: ''' include: - included1.yaml - included2.yaml +// ^^^^^^^^^^^^^^ +// [diag.incompatibleLintIncluded][context 1][context 2] The rule 'included2.yaml' is incompatible with 'rule_poS' and 'ruLe_neg', which is included from 2 files. linter: rules: ''', - [diag.incompatibleLintIncluded], - ); + }); } void test_incompatible_noTrigger_invalidMap() { @@ -241,7 +224,7 @@ linter: rules: rule_neg: true '''); - assertNoErrors(''' + assertDiagnostics(''' include: included.yaml linter: @@ -252,33 +235,24 @@ linter: } Future test_incompatible_rule_map_include() async { - var includedCode = TestCode.parse(''' + await assertDiagnosticsInFiles({ + getFile('/included.yaml'): ''' linter: rules: - [!rule_neg!]: true -'''); - var included = newFile('/included.yaml', includedCode.code); - var testCode = TestCode.parse(''' + rule_neg: true +// ^^^^^^^^ +// [context 1] The rule 'rule_neg' is enabled here in the file '/included.yaml'. +''', + analysisOptionsFile: ''' include: included.yaml linter: rules: - [!rule_pos!]: true -'''); - await assertErrorsInCode(testCode.code, [ - error( - diag.incompatibleLintFiles, - testCode.range.sourceRange.offset, - testCode.range.sourceRange.length, - contextMessages: [ - contextMessage( - included, - includedCode.range.sourceRange.offset, - includedCode.range.sourceRange.length, - ), - ], - ), - ]); + rule_pos: true +// ^^^^^^^^ +// [diag.incompatibleLintFiles][context 1] The rule 'rule_pos' is incompatible with 'rule_neg'. +''', + }); } Future test_incompatible_rule_map_include_disabled() async { @@ -287,7 +261,7 @@ linter: rules: rule_neg: true '''); - await assertNoErrorsInCode(''' + await assertDiagnosticsInCode(''' include: included.yaml linter: @@ -298,52 +272,46 @@ linter: } Future test_incompatible_rule_map_include_mixedCase() async { - var includedCode = TestCode.parse(''' + await assertDiagnosticsInFiles({ + getFile('/included.yaml'): ''' linter: rules: - [!rulE_neg!]: true -'''); - var included = newFile('/included.yaml', includedCode.code); - var testCode = TestCode.parse(''' + rulE_neg: true +// ^^^^^^^^ +// [context 1] The rule 'rulE_neg' is enabled here in the file '/included.yaml'. +''', + analysisOptionsFile: ''' include: included.yaml linter: rules: - [!Rule_pos!]: true -'''); - await assertErrorsInCode(testCode.code, [ - error( - diag.incompatibleLintFiles, - testCode.range.sourceRange.offset, - testCode.range.sourceRange.length, - contextMessages: [ - contextMessage( - included, - includedCode.range.sourceRange.offset, - includedCode.range.sourceRange.length, - ), - ], - ), - ]); + Rule_pos: true +// ^^^^^^^^ +// [diag.incompatibleLintFiles][context 1] The rule 'Rule_pos' is incompatible with 'rulE_neg'. +''', + }); } void test_incompatible_trigger_invalidMap() { - newFile('/included.yaml', ''' + assertRuleDiagnosticsInFiles({ + getFile('/included.yaml'): ''' linter: rules: rule_neg: true -'''); - assertErrors( - ''' +// ^^^^^^^^ +// [context 1] The rule 'rule_neg' is enabled here in the file '/included.yaml'. +''', + analysisOptionsFile: ''' include: included.yaml linter: rules: rule_neg: rule_pos: true +// ^^^^^^^^ +// [diag.incompatibleLintFiles][context 1] The rule 'rule_pos' is incompatible with 'rule_neg'. ''', - [diag.incompatibleLintFiles], - ); + }); } void test_incompatible_unsuportedValue_invalidMap() { @@ -352,16 +320,15 @@ linter: rules: rule_neg: true '''); - assertErrors( - ''' + assertDiagnostics(''' include: included.yaml linter: rules: rule_pos: invalid_value -''', - [diag.unsupportedValue], - ); +// ^^^^^^^^^^^^^ +// [diag.unsupportedValue] The value 'invalid_value' isn't supported by 'rule_pos'. +'''); } void test_incompatible_unsuportedValue_invalidMap_mixedCase() { @@ -370,36 +337,38 @@ linter: rules: rUle_neg: true '''); - assertErrors( - ''' + assertDiagnostics(''' include: included.yaml linter: rules: Rule_pos: invalid_value -''', - [diag.unsupportedValue], - ); +// ^^^^^^^^^^^^^ +// [diag.unsupportedValue] The value 'invalid_value' isn't supported by 'Rule_pos'. +'''); } void test_package_import() { - newFile('$otherLib/analysis_options.yaml', ''' + testProjectPath = '/test'; + assertRuleDiagnosticsInFiles({ + getFile('$otherLib/analysis_options.yaml'): ''' linter: rules: rule_pos: true -'''); - testProjectPath = '/test'; - assertErrors( - ''' +// ^^^^^^^^ +// [context 1] The rule 'rule_pos' is enabled here in the file '/other/lib/analysis_options.yaml'. +''', + getFile('$testProjectPath/analysis_options.yaml'): ''' include: - package:other/analysis_options.yaml linter: rules: rule_neg: true +// ^^^^^^^^ +// [diag.incompatibleLintFiles][context 1] The rule 'rule_neg' is incompatible with 'rule_pos'. ''', - [diag.incompatibleLintFiles], - ); + }); } Future test_removed_rule_inInclude_ok() async { @@ -408,34 +377,30 @@ linter: rules: - removed_in_2_12_lint '''); - assertNoErrors(''' + assertDiagnostics(''' include: included.yaml '''); } /// https://github.com/dart-lang/sdk/issues/59869 test_removed_rule_previousSdk() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - removed_in_2_12_lint -''', - [diag.removedLint], - sdk: dart3_3, - ); +// ^^^^^^^^^^^^^^^^^^^^ +// [diag.removedLint] 'removed_in_2_12_lint' was removed in Dart '2.12.0' +''', sdk: dart3_3); } test_removed_rule_previousSdk_mixedCase() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - remOved_in_2_12_lint -''', - [diag.removedLint], - sdk: dart3_3, - ); +// ^^^^^^^^^^^^^^^^^^^^ +// [diag.removedLint] 'remOved_in_2_12_lint' was removed in Dart '2.12.0' +''', sdk: dart3_3); } } @@ -443,97 +408,87 @@ linter: class OptionsRuleValidatorTest extends AbstractAnalysisOptionsTest with OptionsRuleValidatorTestMixin { void test_deprecated_rule() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - deprecated_lint -''', - [diag.deprecatedLint], - ); +// ^^^^^^^^^^^^^^^ +// [diag.deprecatedLint] The lint rule 'deprecated_lint' is deprecated and shouldn't be enabled. +'''); } void test_deprecated_rule_map() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: deprecated_lint: false -''', - [diag.deprecatedLint], - ); +// ^^^^^^^^^^^^^^^ +// [diag.deprecatedLint] The lint rule 'deprecated_lint' is deprecated and shouldn't be enabled. +'''); } void test_deprecated_rule_map_mixedCase() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: deprecated_lInt: false -''', - [diag.deprecatedLint], - ); +// ^^^^^^^^^^^^^^^ +// [diag.deprecatedLint] The lint rule 'deprecated_lInt' is deprecated and shouldn't be enabled. +'''); } void test_deprecated_rule_mixedCase() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - deprecAted_lint -''', - [diag.deprecatedLint], - ); +// ^^^^^^^^^^^^^^^ +// [diag.deprecatedLint] The lint rule 'deprecAted_lint' is deprecated and shouldn't be enabled. +'''); } void test_deprecated_rule_previousSDK() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - deprecated_since_3_lint -''', - [diag.deprecatedLint], - sdk: dart3_3, - ); +// ^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.deprecatedLint] The lint rule 'deprecated_since_3_lint' is deprecated and shouldn't be enabled. +''', sdk: dart3_3); } void test_deprecated_rule_withReplacement() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - deprecated_lint_with_replacement -''', - [diag.deprecatedLintWithReplacement], - ); +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.deprecatedLintWithReplacement] The lint rule 'deprecated_lint_with_replacement' is deprecated and replaced by 'replacing_lint'. +'''); } void test_deprecated_rule_withReplacement_mixedCase() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - deprecated_lint_with_rePlacement -''', - [diag.deprecatedLintWithReplacement], - ); +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.deprecatedLintWithReplacement] The lint rule 'deprecated_lint_with_rePlacement' is deprecated and replaced by 'replacing_lint'. +'''); } void test_deprecated_rule_withSince_inCurrentSdk() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - deprecated_since_3_lint -''', - [diag.deprecatedLint], - sdk: dart3, - ); +// ^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.deprecatedLint] The lint rule 'deprecated_since_3_lint' is deprecated and shouldn't be enabled. +''', sdk: dart3); } void test_deprecated_rule_withSince_notInCurrentSdk() { - assertNoErrors(''' + assertDiagnostics(''' linter: rules: - deprecated_since_3_lint @@ -541,7 +496,7 @@ linter: } void test_deprecated_rule_withSince_unknownSdk() { - assertNoErrors(''' + assertDiagnostics(''' linter: rules: - deprecated_since_3_lint @@ -549,77 +504,55 @@ linter: } void test_duplicated_rule() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - stable_lint - stable_lint -''', - [diag.duplicateRule], - ); +// ^^^^^^^^^^^ +// [diag.duplicateRule] The rule 'stable_lint' is already enabled and doesn't need to be enabled again. +'''); } void test_duplicated_rule_mixedCase() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - stable_lint - staBle_lint -''', - [diag.duplicateRule], - ); +// ^^^^^^^^^^^ +// [diag.duplicateRule] The rule 'staBle_lint' is already enabled and doesn't need to be enabled again. +'''); } Future test_incompatible_rule() async { - var testCode = TestCode.parse(''' + await assertDiagnosticsInCode(''' linter: rules: - - /*[0*/rule_pos/*0]*/ - - /*[1*/rule_neg/*1]*/ + - rule_pos +// ^^^^^^^^ +// [context 1] The rule 'rule_pos' is enabled here. + - rule_neg +// ^^^^^^^^ +// [diag.incompatibleLint][context 1] The rule 'rule_neg' is incompatible with ''rule_pos''. '''); - await assertErrorsInCode(testCode.code, [ - error( - diag.incompatibleLint, - testCode.ranges.last.sourceRange.offset, - testCode.ranges.last.sourceRange.length, - contextMessages: [ - contextMessage( - analysisOptionsFile, - testCode.ranges.first.sourceRange.offset, - testCode.ranges.first.sourceRange.length, - ), - ], - ), - ]); } Future test_incompatible_rule_map() async { - var testCode = TestCode.parse(''' + await assertDiagnosticsInCode(''' linter: rules: - /*[0*/rule_pos/*0]*/: true - /*[1*/rule_neg/*1]*/: true + rule_pos: true +// ^^^^^^^^ +// [context 1] The rule 'rule_pos' is enabled here. + rule_neg: true +// ^^^^^^^^ +// [diag.incompatibleLint][context 1] The rule 'rule_neg' is incompatible with ''rule_pos''. '''); - await assertErrorsInCode(testCode.code, [ - error( - diag.incompatibleLint, - testCode.ranges.last.sourceRange.offset, - testCode.ranges.last.sourceRange.length, - contextMessages: [ - contextMessage( - analysisOptionsFile, - testCode.ranges.first.sourceRange.offset, - testCode.ranges.first.sourceRange.length, - ), - ], - ), - ]); } void test_incompatible_rule_map_disabled() { - assertNoErrors(''' + assertDiagnostics(''' linter: rules: rule_pos: true @@ -628,49 +561,29 @@ linter: } Future test_incompatible_rule_map_mixedCase() async { - var testCode = TestCode.parse(''' + await assertDiagnosticsInCode(''' linter: rules: - /*[0*/Rule_pos/*0]*/: true - /*[1*/rUle_neg/*1]*/: true + Rule_pos: true +// ^^^^^^^^ +// [context 1] The rule 'Rule_pos' is enabled here. + rUle_neg: true +// ^^^^^^^^ +// [diag.incompatibleLint][context 1] The rule 'rUle_neg' is incompatible with ''Rule_pos''. '''); - await assertErrorsInCode(testCode.code, [ - error( - diag.incompatibleLint, - testCode.ranges.last.sourceRange.offset, - testCode.ranges.last.sourceRange.length, - contextMessages: [ - contextMessage( - analysisOptionsFile, - testCode.ranges.first.sourceRange.offset, - testCode.ranges.first.sourceRange.length, - ), - ], - ), - ]); } Future test_incompatible_rule_mixedCase() async { - var testCode = TestCode.parse(''' + await assertDiagnosticsInCode(''' linter: rules: - - /*[0*/rule_Pos/*0]*/ - - /*[1*/rule_neG/*1]*/ + - rule_Pos +// ^^^^^^^^ +// [context 1] The rule 'rule_Pos' is enabled here. + - rule_neG +// ^^^^^^^^ +// [diag.incompatibleLint][context 1] The rule 'rule_neG' is incompatible with ''rule_Pos''. '''); - await assertErrorsInCode(testCode.code, [ - error( - diag.incompatibleLint, - testCode.ranges.last.sourceRange.offset, - testCode.ranges.last.sourceRange.length, - contextMessages: [ - contextMessage( - analysisOptionsFile, - testCode.ranges.first.sourceRange.offset, - testCode.ranges.first.sourceRange.length, - ), - ], - ), - ]); } void test_no_duplicated_rule_include() { @@ -679,7 +592,7 @@ linter: rules: - stable_lint '''); - assertNoErrors(''' + assertDiagnostics(''' include: included.yaml linter: @@ -689,19 +602,17 @@ linter: } void test_removed_rule() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - removed_in_2_12_lint -''', - [diag.removedLint], - sdk: dart2_12, - ); +// ^^^^^^^^^^^^^^^^^^^^ +// [diag.removedLint] 'removed_in_2_12_lint' was removed in Dart '2.12.0' +''', sdk: dart2_12); } void test_removed_rule_notYet_ok() { - assertNoErrors(''' + assertDiagnostics(''' linter: rules: - removed_in_2_12_lint @@ -709,31 +620,27 @@ linter: } void test_replaced_rule() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - replaced_lint -''', - [diag.replacedLint], - sdk: dart3, - ); +// ^^^^^^^^^^^^^ +// [diag.replacedLint] 'replaced_lint' was replaced by 'replacing_lint' in Dart '3.0.0'. +''', sdk: dart3); } void test_replaced_rule_mixedCase() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - replaCed_lint -''', - [diag.replacedLint], - sdk: dart3, - ); +// ^^^^^^^^^^^^^ +// [diag.replacedLint] 'replaCed_lint' was replaced by 'replacing_lint' in Dart '3.0.0'. +''', sdk: dart3); } void test_stable_rule() { - assertNoErrors(''' + assertDiagnostics(''' linter: rules: - stable_lint @@ -741,7 +648,7 @@ linter: } void test_stable_rule_map() { - assertNoErrors(''' + assertDiagnostics(''' linter: rules: stable_lint: true @@ -749,7 +656,7 @@ linter: } void test_stable_rule_map_mixedCase() { - assertNoErrors(''' + assertDiagnostics(''' linter: rules: sTable_lint: true @@ -757,7 +664,7 @@ linter: } void test_stable_rule_mixedCase() { - assertNoErrors(''' + assertDiagnostics(''' linter: rules: - Stable_lint @@ -765,44 +672,53 @@ linter: } void test_undefined_rule() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: - this_rule_does_not_exist -''', - [diag.undefinedLint], - ); +// ^^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.undefinedLint] 'this_rule_does_not_exist' isn't a recognized lint rule. +'''); } void test_undefined_rule_map() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: this_rule_does_not_exist: false -''', - [diag.undefinedLint], - ); +// ^^^^^^^^^^^^^^^^^^^^^^^^ +// [diag.undefinedLint] 'this_rule_does_not_exist' isn't a recognized lint rule. +'''); } } mixin OptionsRuleValidatorTestMixin on AbstractAnalysisOptionsTest { String? testProjectPath; - /// Assert that when the validator is used on the given [content] the - /// [expectedCodes] are produced. - void assertErrors( - String content, - List expectedCodes, { - VersionConstraint? sdk, - }) { - GatheringDiagnosticListener listener = GatheringDiagnosticListener(); + /// Assert that when the validator is used on the given [content] its + /// diagnostics match the inline diagnostic markers in [content]. + void assertDiagnostics(String content, {VersionConstraint? sdk}) { var optionsFile = analysisOptionsFile; if (testProjectPath != null) { - optionsFile = newFile('$testProjectPath/analysis_options.yaml', ''); + optionsFile = getFile('$testProjectPath/analysis_options.yaml'); } - optionsFile.writeAsStringSync(content); + assertRuleDiagnosticsInFiles({optionsFile: content}, sdk: sdk); + } + + /// Assert that rule-validator diagnostics for a main options file match + /// inline diagnostic markers across [codeByFile]. + void assertRuleDiagnosticsInFiles( + Map codeByFile, { + VersionConstraint? sdk, + }) { + var cleanCodeByFile = writeFilesWithoutDiagnosticExpectations(codeByFile); + + var optionsFile = testProjectPath != null + ? getFile('$testProjectPath/analysis_options.yaml') + : analysisOptionsFile; + var cleanContent = cleanCodeByFile[optionsFile]!; + + var listener = RecordingDiagnosticListener(); var source = FileSource(optionsFile); var reporter = DiagnosticReporter(listener, source); var validator = LinterRuleOptionsValidator( @@ -814,15 +730,14 @@ mixin OptionsRuleValidatorTestMixin on AbstractAnalysisOptionsTest { ); validator.validate( reporter, - loadYamlNode(content, sourceUrl: source.uri) as YamlMap, + loadYamlNode(cleanContent, sourceUrl: source.uri) as YamlMap, ); - listener.assertErrorsWithCodes(expectedCodes); - } - /// Assert that when the validator is used on the given [content] no errors - /// are produced. - void assertNoErrors(String content, {VersionConstraint? sdk}) => - assertErrors(content, const [], sdk: sdk); + assertDiagnosticMarkersInFiles( + codeByFile: codeByFile, + diagnostics: listener.diagnostics, + ); + } @override void setUp() { @@ -854,18 +769,17 @@ mixin OptionsRuleValidatorTestMixin on AbstractAnalysisOptionsTest { class OptionsRuleValidatorValueTest extends AbstractAnalysisOptionsTest with OptionsRuleValidatorTestMixin { void test_unsuportedValue_invalidValue() { - assertErrors( - ''' + assertDiagnostics(''' linter: rules: rule_pos: invalid_value -''', - [diag.unsupportedValue], - ); +// ^^^^^^^^^^^^^ +// [diag.unsupportedValue] The value 'invalid_value' isn't supported by 'rule_pos'. +'''); } void test_unsuportedValue_validError() { - assertNoErrors(''' + assertDiagnostics(''' include: included.yaml linter: @@ -875,7 +789,7 @@ linter: } void test_unsuportedValue_validFalse() { - assertNoErrors(''' + assertDiagnostics(''' include: included.yaml linter: @@ -885,7 +799,7 @@ linter: } void test_unsuportedValue_validIgnore() { - assertNoErrors(''' + assertDiagnostics(''' include: included.yaml linter: @@ -895,7 +809,7 @@ linter: } void test_unsuportedValue_validInfo() { - assertNoErrors(''' + assertDiagnostics(''' include: included.yaml linter: @@ -905,7 +819,7 @@ linter: } void test_unsuportedValue_validTrue() { - assertNoErrors(''' + assertDiagnostics(''' include: included.yaml linter: @@ -915,7 +829,7 @@ linter: } void test_unsuportedValue_validWarning() { - assertNoErrors(''' + assertDiagnostics(''' include: included.yaml linter: