blob: 32c0731450269734a43671b92cbdfe32387bfd78 [file]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer_testing/package_config_file_builder.dart';
import 'package:analyzer_testing/utilities/utilities.dart';
import 'package:analyzer_utilities/analyzer_messages.dart';
import 'package:analyzer_utilities/lint_messages.dart';
import 'package:analyzer_utilities/messages.dart';
import 'package:dart_style/dart_style.dart';
import 'package:linter/src/lint_names.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../tool/messages/error_code_documentation_info.dart';
import 'src/dart/resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(VerifyDiagnosticsTest);
});
}
/// A class used to validate diagnostic documentation.
class DocumentationValidator {
/// The sequence used to mark the start of an error range.
static const String errorRangeStart = '[!';
/// The sequence used to mark the end of an error range.
static const String errorRangeEnd = '!]';
/// Whether the formatting of the snippets should be validated.
///
/// This should always be `true` except when rolling in a new version of
/// `dart_style`. To ease the process, this flag can be set to `false` prior
/// to the roll, and then reverted to `true` in a follow-on CL in which all
/// of the impacted doc snippets are updated to conform.
static const bool validateFormatting = true;
/// A list of the diagnostic codes that are not being verified. These should
/// ony include docs that can't be verified because of missing support in the
/// verifier.
static const List<String> unverifiedDocs = [
// The following diagnostics can't be verified because the examples aren't
// Dart code. The verifier needs to add the ability to verify YAML snippets
// and to use a YAML snippet as the example. If we can do this based on the
// class of the diagnostic, then there will be less chance of a false
// positive.
'analysis_option_deprecated',
'asset_does_not_exist',
'asset_directory_does_not_exist',
'asset_field_not_list',
'asset_missing_path',
'asset_not_string',
'asset_not_string_or_map',
'asset_path_not_string',
'dependencies_field_not_map',
'deprecated_field',
'deprecated_lint',
'duplicate_rule',
'flutter_field_not_map',
'included_file_parse_error',
'included_file_warning',
'include_file_not_found',
'incompatible_lint',
'invalid_dependency',
'invalid_option',
'invalid_platforms_field',
'invalid_section_format',
'missing_name',
'missing_dependency',
'name_not_string',
'package_names',
'path_does_not_exist',
'path_not_posix',
'path_pubspec_does_not_exist',
'parse_error',
'platform_value_disallowed',
'plugins_in_inner_options',
'recursive_include_file',
'removed_lint',
'removed_lint_use',
'secure_pubspec_urls',
'sort_pub_dependencies',
'unknown_platform',
'undefined_lint',
'unnecessary_dev_dependency',
'unrecognized_error_code',
'unsupported_option_with_legal_value',
'unsupported_value',
'workspace_field_not_list',
'workspace_value_not_string',
'workspace_value_not_subdirectory',
// The following diagnostics can't be verified because they necessarily
// produce more than one diagnostic. This is typically because of a conflict
// between two or more declarations, neither of which is obviously the
// better declaration to flag, and we have chosen to create a diagnostic for
// all of them.
'ambiguous_import',
'extension_type_implements_itself',
'extension_type_representation_depends_on_itself',
'recursive_compile_time_constant',
'recursive_constructor_redirect',
'recursive_interface_inheritance',
'text_direction_code_point_in_comment',
'text_direction_code_point_in_literal',
'top_level_cycle',
'type_alias_cannot_reference_itself',
'type_parameter_supertype_of_its_bound',
// const_constructor_param_type_mismatch (analyzer)
// - Expected an error with code const_constructor_param_type_mismatch,
// found const_eval_throws_exception (example 0).
//
// Based on the TODO comment below, it appears that this diagnostic is never
// reported, and it should be marked as removed.
//
// TODO(kallentu): This is always reported with
// `argument_type_not_assignable` or is reported as
// `const_eval_throws_exception` in const constructor evaluation.
'const_constructor_param_type_mismatch',
// invalid_implementation_override (analyzer)
// - No error range in example
// - Expected no errors but found 1 (example 1):
// undefined_class (7, 1) Undefined class 'B'.
//
// Has code in the example section that needs to be skipped (because it's
// part of the explanatory text not part of the example), but there's
// currently no way to do that. We could try to rewrite the text so that all
// of the code is in a single snippet, or we could introduce a way to skip
// some code blocks.
'invalid_implementation_override',
// invalid_uri (analyzer)
// - Expected an error with code invalid_uri, found uri_does_not_exist
// (example 0).
//
// It's possible that this diagnostic is no longer reported.
'invalid_uri',
// yield_each_in_non_generator (analyzer)
// - No error range in example
// - Expected no errors but found 2 (example 0):
// - undefined_identifier (29, 5) Undefined name 'yield'.
// - body_might_complete_normally (18, 6) The body might complete
// normally, causing 'null' to be returned, but the return type,
// 'Iterable<int>', is a potentially non-nullable type.
'yield_each_in_non_generator',
// deprecated_colon_for_default_value (analyzer)
// - Expected an error with code deprecated_colon_for_default_value, found
// obsolete_colon_for_default_value (example 0).
//
// This no longer works in 3.0 and should be marked as removed.
'deprecated_colon_for_default_value',
// deprecated_member_use (analyzer)
// - Expected an error with code deprecated_member_use, found
// undefined_class (example 0).
//
// The example needs to have a definition of `C` that is marked as
// deprecated.
'deprecated_member_use',
// avoid_relative_lib_imports (linter)
// - Expected one error but found 2 (example 0):
// - uri_does_not_exist (7, 15) Target of URI doesn't exist: '../lib/a.dart'.
// - avoid_relative_lib_imports (7, 15) Can't use a relative path to import a library in 'lib'.
// - Expected no errors but found 1 (fixes 0):
// - unused_import (7, 8) Unused import: 'a.dart'.
//
// Missing support for example files outside of `lib`.
'avoid_relative_lib_imports',
// avoid_web_libraries_in_flutter (linter)
// - Expected one error but found none (example 0).
//
// The example isn't being recognized as a flutter app. We might need to
// build a pubspec.yaml when analyzing flutter code.
'avoid_web_libraries_in_flutter',
// depend_on_referenced_packages (linter)
// - Expected one error but found none (example 0).
//
// The example doesn't generate the documented diagnostic.
'depend_on_referenced_packages',
// file_names (linter)
// - No example.
//
// There's no interesting file content to use as an example. We could have
// some placeholder content to get rid of the failure, but the documentation
// wouldn't be improved.
'file_names',
// prefer_inlined_adds_single (linter)
// - Expected one error but found none (example 1).
//
// Doesn't produce a lint for the second example, even though the analyzer
// does when the example is pasted into a file.
'prefer_inlined_adds_single',
// library_annotations (linter)
// - Expected an error with code library_annotations, found
// undefined_annotation (example 0).
// - Expected no errors but found 1 (fixes 0):
// - undefined_annotation (0, 18) Undefined name 'TestOn' used as an
// annotation.
//
// No mock 'test' package, no good library annotations in 'meta'.
'library_annotations',
// package_prefixed_library_names (linter)
// - Expected one error but found none (example 0).
//
// The lint does nothing, so no diagnostic is produced. I needs to be marked
// as 'removed'.
'package_prefixed_library_names',
// prefer_relative_imports (linter)
// No error range in example
// - Expected no errors but found 1 (example 0):
// - uri_does_not_exist (7, 29) Target of URI doesn't exist:
// 'package:my_package/bar.dart'.
// - Expected no errors but found 1 (fixes 0):
// - uri_does_not_exist (7, 10) Target of URI doesn't exist: 'bar.dart'.
//
// Need a way to specify the existance of files whose content is irrelevant.
// Either that or the example needs to include a minial file to refer to.
'prefer_relative_imports',
// public_member_api_docs (linter)
// - Expected one error but found none (example 0).
//
// The test file is in a basic workspace, so it can't have public API. I
// think we'd need to add a `pubspec.yaml` file to the example.
'public_member_api_docs',
// recursive_getters (linter)
// - Expected an error at 39, found 48 (example 0).
//
// The lint fires when the example is pasted into an empty file.
'recursive_getters',
// Missing a mock of `Expando` in `dart:core`.
'extension_declares_instance_field',
// deprecated_extends_function (analyzer)
// - Expected an error with code deprecated_subtype_of_function, found
// final_class_extended_outside_of_library (example 0).
//
// Probably needs a language override comment, but I don't know which
// version.
'deprecated_extends_function',
];
/// The buffer to which validation errors are written.
final StringBuffer buffer = StringBuffer();
/// The name of the package containing the variables currently being verified.
late String packageName;
/// The name of the variable currently being verified.
late String variableName;
/// The name of the error code currently being verified.
late String codeName;
/// A flag indicating whether the [variableName] has already been written to
/// the buffer.
bool hasWrittenVariableName = false;
/// Initialize a newly created documentation validator.
DocumentationValidator();
/// Validate the documentation.
Future<void> validate() async {
packageName = '_fe_analyzer_shared';
await _validateMessages(feAnalyzerSharedMessages);
packageName = 'analyzer';
await _validateMessages(analyzerMessages);
packageName = 'analysis_server';
await _validateMessages(analysisServerMessages);
packageName = 'linter';
await _validateMessages(lintMessages);
if (buffer.isNotEmpty) {
fail(buffer.toString());
}
}
_SnippetData _extractSnippetData({
required String snippet,
required int index,
required bool errorRequired,
required bool onlyValidateFormatting,
required Map<String, String> auxiliaryFiles,
required List<String> experiments,
required List<String> ignores,
required String? languageVersion,
}) {
// TODO(brianwilkerson): This doesn't check to ensure that there are no
// ranges specified in fixes (when `errorRequired` is `false`), but it
// probably should.
var section = errorRequired ? _Section.examples : _Section.fixes;
var experimentalFeatures = <Feature>[];
for (var experiment in experiments) {
var feature = ExperimentStatus.knownFeatures[experiment];
if (feature != null) {
experimentalFeatures.add(feature);
} else if (!onlyValidateFormatting) {
_reportProblem("Unknown experiment '$experiment' in $section $index");
}
}
int rangeStart = snippet.indexOf(errorRangeStart);
if (rangeStart < 0) {
if (errorRequired && !onlyValidateFormatting) {
_reportProblem('No error range in $section $index');
}
return _SnippetData(
snippet,
-1,
0,
auxiliaryFiles,
experimentalFeatures,
ignores,
languageVersion,
);
}
int rangeEnd = snippet.indexOf(errorRangeEnd, rangeStart + 1);
if (rangeEnd < 0) {
if (!onlyValidateFormatting) {
_reportProblem('No end of error range in $section $index');
}
return _SnippetData(
snippet,
-1,
0,
auxiliaryFiles,
experimentalFeatures,
ignores,
languageVersion,
);
} else if (snippet.indexOf(errorRangeStart, rangeEnd) > 0) {
if (!onlyValidateFormatting) {
_reportProblem('More than one error range in $section $index');
}
}
String content;
try {
content =
snippet.substring(0, rangeStart) +
snippet.substring(rangeStart + errorRangeStart.length, rangeEnd) +
snippet.substring(rangeEnd + errorRangeEnd.length);
} on RangeError catch (exception) {
_reportProblem('${exception.message} in $section $index');
content = '';
}
return _SnippetData(
content,
rangeStart,
rangeEnd - rangeStart - 2,
auxiliaryFiles,
experimentalFeatures,
ignores,
languageVersion,
);
}
/// Extract the snippets of Dart code from [documentationParts] that are
/// tagged as belonging to the given [blockSection].
List<_SnippetData> _extractSnippets(
List<ErrorCodeDocumentationPart> documentationParts,
BlockSection blockSection,
bool onlyValidateFormatting,
) {
var snippets = <_SnippetData>[];
var auxiliaryFiles = <String, String>{};
for (var documentationPart in documentationParts) {
if (documentationPart is ErrorCodeDocumentationBlock) {
if (documentationPart.containingSection != blockSection) {
continue;
}
var uri = documentationPart.uri;
if (uri != null) {
auxiliaryFiles[uri] = documentationPart.text;
} else {
if (documentationPart.fileType == 'dart') {
snippets.add(
_extractSnippetData(
snippet: documentationPart.text,
index: snippets.length,
errorRequired: blockSection == BlockSection.examples,
onlyValidateFormatting: onlyValidateFormatting,
auxiliaryFiles: auxiliaryFiles,
experiments: documentationPart.experiments,
ignores: documentationPart.ignores,
languageVersion: documentationPart.languageVersion,
),
);
}
auxiliaryFiles = <String, String>{};
}
}
}
return snippets;
}
/// Report a problem with the current error code.
void _reportProblem(
String problem, {
List<Diagnostic> diagnostics = const [],
}) {
if (!hasWrittenVariableName) {
buffer.writeln(' $variableName ($packageName)');
hasWrittenVariableName = true;
}
buffer.writeln(' $problem');
for (Diagnostic diagnostic in diagnostics) {
buffer.write(' ');
buffer.write(diagnostic.diagnosticCode.lowerCaseName);
buffer.write(' (');
buffer.write(diagnostic.offset);
buffer.write(', ');
buffer.write(diagnostic.length);
buffer.write(') ');
buffer.writeln(diagnostic.message);
}
}
/// Validates that the [snippet] is formatted correctly.
///
/// The snippet is identified for reporting purposes by the [section] and
/// [index].
void _validateFormatting(_SnippetData snippet, _Section section, int index) {
if (!validateFormatting) return;
var ignoreFormatting = snippet.ignores.contains('formatting');
String? formattedContent;
try {
var formatter = DartFormatter(languageVersion: Version(3, 13, 0));
formattedContent = formatter.format(snippet.content).trimRight();
if (formattedContent != snippet.content) {
if (!ignoreFormatting) {
_reportProblem('''
Snippet is not formatted ($section $index). Content is:
${snippet.content}
Formatted content is:
$formattedContent''');
}
} else if (ignoreFormatting) {
_reportProblem('Formatting is unnecessarily ignored ($section $index)');
}
} catch (e) {
if (section != _Section.examples) {
// The assumption is that the examples will only fail to format when the
// diagnostic is an expected symtactic error, in which case we don't
// need to report the problem.
_reportProblem('''
Exception while formatting ($section $index): $e
Content is:
${snippet.content}
Formatted content is:
$formattedContent''');
}
}
}
/// Extract documentation from the given [messages].
Future<void> _validateMessages(List<MessageWithAnalyzerCode> messages) async {
for (var message in messages) {
// If the diagnostic is no longer generated,
// the corresponding code snippets won't report it.
if (message.isRemoved) {
continue;
}
var docs = parseErrorCodeDocumentation(
message.analyzerCode.toString(),
message.documentation,
);
if (docs != null) {
codeName = (message.sharedName ?? message.analyzerCode).snakeCaseName;
// Even if the message has been exempted from other verification,
// the formatting of the code will still be verified.
var onlyValidateFormatting = unverifiedDocs.contains(
message.analyzerCode.snakeCaseName,
);
if (onlyValidateFormatting && !validateFormatting) {
continue;
}
variableName = message.analyzerCode.camelCaseName;
hasWrittenVariableName = false;
List<_SnippetData> exampleSnippets = _extractSnippets(
docs,
BlockSection.examples,
onlyValidateFormatting,
);
_SnippetData? firstExample;
if (exampleSnippets.isEmpty) {
if (!onlyValidateFormatting) {
_reportProblem('No example.');
}
} else {
firstExample = exampleSnippets[0];
}
for (int i = 0; i < exampleSnippets.length; i++) {
_SnippetData snippet = exampleSnippets[i];
if (message.type == AnalyzerDiagnosticType.lint) {
snippet.lintCode = codeName;
}
_validateFormatting(snippet, _Section.examples, i);
if (!onlyValidateFormatting) {
await _validateSnippet(snippet, _Section.examples, i);
}
}
List<_SnippetData> fixesSnippets = _extractSnippets(
docs,
BlockSection.commonFixes,
onlyValidateFormatting,
);
for (int i = 0; i < fixesSnippets.length; i++) {
_SnippetData snippet = fixesSnippets[i];
if (firstExample != null) {
snippet.auxiliaryFiles.addAll(firstExample.auxiliaryFiles);
}
if (message.type == AnalyzerDiagnosticType.lint) {
snippet.lintCode = codeName;
}
_validateFormatting(snippet, _Section.fixes, i);
if (!onlyValidateFormatting) {
await _validateSnippet(snippet, _Section.fixes, i);
}
}
}
}
}
/// Resolve the [snippet]. If the snippet's offset is less than zero, then
/// verify that no diagnostics are reported. If the offset is greater than or
/// equal to zero, verify that one error whose name matches the current code
/// is reported at that offset with the expected length.
Future<void> _validateSnippet(
_SnippetData snippet,
_Section section,
int index,
) async {
var test = _SnippetTest(snippet);
test.setUp();
var result = await test.resolveTestFile();
var diagnostics = result.diagnostics;
var filteredDiagnostics = <Diagnostic>[];
var errorCount = 0;
var unneededIgnores = snippet.ignores.toList()..remove('formatting');
for (var diagnostic in diagnostics) {
var diagnosticName = diagnostic.diagnosticCode.lowerCaseName;
if (snippet.ignores.contains(diagnosticName)) {
unneededIgnores.remove(diagnosticName);
} else {
errorCount++;
filteredDiagnostics.add(diagnostic);
}
}
if (snippet.offset < 0) {
if (errorCount > 0) {
_reportProblem(
'Expected no errors but found $errorCount ($section $index):',
diagnostics: diagnostics,
);
}
} else {
if (errorCount == 0) {
_reportProblem('Expected one error but found none ($section $index).');
} else if (errorCount == 1) {
var diagnostic = filteredDiagnostics[0];
if (diagnostic.diagnosticCode.lowerCaseName != codeName) {
_reportProblem(
'Expected an error with code $codeName, '
'found ${diagnostic.diagnosticCode.lowerCaseName} ($section $index).',
);
}
if (diagnostic.offset != snippet.offset) {
_reportProblem(
'Expected an error at ${snippet.offset}, '
'found ${diagnostic.offset} ($section $index).',
);
}
if (diagnostic.length != snippet.length) {
_reportProblem(
'Expected an error of length ${snippet.length}, '
'found ${diagnostic.length} ($section $index).',
);
}
} else {
_reportProblem(
'Expected one error but found $errorCount ($section $index):',
diagnostics: diagnostics,
);
}
}
if (unneededIgnores.isNotEmpty) {
var list = unneededIgnores.join(', ');
_reportProblem('Unneeded ignores: $list ($section $index).');
}
}
}
/// Validate the documentation associated with the declarations of the error
/// codes.
@reflectiveTest
class VerifyDiagnosticsTest {
@TestTimeout(Timeout.factor(4))
test_diagnostics() async {
//
// Validate that the input to the generator is correct.
//
DocumentationValidator validator = DocumentationValidator();
await validator.validate();
}
test_published() {
// Verify that if _any_ error code is marked as having published docs then
// _all_ codes with the same name are also marked that way.
var nameToCodeMap = <String, List<DiagnosticCode>>{};
var nameToPublishedMap = <String, bool>{};
for (var code in diagnosticCodeValues) {
var name = code.lowerCaseName;
nameToCodeMap.putIfAbsent(name, () => []).add(code);
nameToPublishedMap[name] =
(nameToPublishedMap[name] ?? false) || code.hasPublishedDocs;
}
var unpublished = <DiagnosticCode>[];
for (var entry in nameToCodeMap.entries) {
var name = entry.key;
if (nameToPublishedMap[name]!) {
for (var code in entry.value) {
if (!code.hasPublishedDocs) {
unpublished.add(code);
}
}
}
}
if (unpublished.isNotEmpty) {
var buffer = StringBuffer();
buffer.write(
"The following error codes have published docs but aren't "
"marked as such:",
);
for (var code in unpublished) {
buffer.writeln();
buffer.write('- ${code.runtimeType}.${code.lowerCaseUniqueName}');
}
fail(buffer.toString());
}
}
}
enum _Section {
examples,
fixes;
@override
String toString() => name;
}
/// A data holder used to return multiple values when extracting an error range
/// from a snippet.
class _SnippetData {
final String content;
final int offset;
final int length;
final Map<String, String> auxiliaryFiles;
final List<Feature> experimentalFeatures;
final List<String> ignores;
final String? languageVersion;
String? lintCode;
_SnippetData(
this.content,
this.offset,
this.length,
this.auxiliaryFiles,
this.experimentalFeatures,
this.ignores,
this.languageVersion,
);
}
/// A test class that creates an environment suitable for analyzing the
/// snippets.
class _SnippetTest extends PubPackageResolutionTest {
/// The lints that are used to find code that needs to be updated to conform
/// with the currently recommended style.
///
/// The lints that are commented out are lints that currently cause the test
/// to break. There are two common reasons.
///
/// - There is are examples that generate multiple diagnostics when they
/// should only generate a single diagnostic. Most of the time this will be
/// handled by adding an `%ignore` directive and filing an issue to reduce
/// the number of diagnostics being produced.
///
/// - There is are examples that need to be updated in order to conform. The
/// examples will be updated and the lint uncommented.
//
// TODO(brianwilkerson): We should consider reading the list of 'core' and
// 'recommended' lints from `sdk/third_party/pkg/core/pkgs/lints/lib`. Doing
// so would have the advantage that the test would always use the most
// current set of lints. It would have the disadvantage that it might be more
// difficult to roll in a new version of these files.
//
// If we do that, we'll need to have a list of rules that we exclude so that
// lints that are just too hard to accomodate can be ignored, and so that
// there's a way to simplify rolls.
//
// If we don't do that, then this to-do should be removed and the comment
// updated to indicate why we chose not to.
//
// I'm not sure how we could get the flutter lints, so they might have to
// remain an explicitly duplicated list.
static final List<String> lints = [
// The lints from the 'core' lint set.
LintNames.avoid_empty_else,
LintNames.avoid_relative_lib_imports,
LintNames.avoid_shadowing_type_parameters,
// LintNames.avoid_types_as_parameter_names,
LintNames.await_only_futures,
// LintNames.camel_case_extensions,
// LintNames.camel_case_types,
LintNames.collection_methods_unrelated_type,
// LintNames.curly_braces_in_flow_control_structures,
LintNames.dangling_library_doc_comments,
LintNames.depend_on_referenced_packages,
LintNames.empty_catches,
LintNames.file_names,
// LintNames.hash_and_equals,
LintNames.implicit_call_tearoffs,
LintNames.library_annotations,
LintNames.no_duplicate_case_values,
LintNames.no_wildcard_variable_uses,
// LintNames.non_constant_identifier_names,
LintNames.null_check_on_nullable_type_parameter,
LintNames.prefer_generic_function_type_aliases,
LintNames.prefer_is_empty,
LintNames.prefer_is_not_empty,
LintNames.prefer_iterable_wheretype,
// LintNames.prefer_typing_uninitialized_variables,
LintNames.provide_deprecation_message,
LintNames.secure_pubspec_urls,
// LintNames.strict_top_level_inference,
LintNames.type_literal_in_constant_pattern,
LintNames.unintended_html_in_doc_comment,
// LintNames.unnecessary_overrides,
LintNames.unrelated_type_equality_checks,
LintNames.use_string_in_part_of_directives,
LintNames.valid_regexps,
LintNames.void_checks,
// The lints from the 'recommended' lint set.
LintNames.annotate_overrides,
// LintNames.avoid_function_literals_in_foreach_calls,
LintNames.avoid_init_to_null,
LintNames.avoid_renaming_method_parameters,
// LintNames.avoid_return_types_on_setters,
LintNames.avoid_returning_null_for_void,
// LintNames.avoid_single_cascade_in_expression_statements,
LintNames.constant_identifier_names,
LintNames.control_flow_in_finally,
// LintNames.empty_constructor_bodies,
// LintNames.empty_statements,
LintNames.exhaustive_cases,
// LintNames.implementation_imports,
LintNames.invalid_runtime_check_with_js_interop_types,
LintNames.library_prefixes,
LintNames.library_private_types_in_public_api,
LintNames.no_leading_underscores_for_library_prefixes,
LintNames.no_leading_underscores_for_local_identifiers,
LintNames.null_closures,
LintNames.overridden_fields,
LintNames.package_names,
// LintNames.prefer_adjacent_string_concatenation,
LintNames.prefer_collection_literals,
LintNames.prefer_conditional_assignment,
LintNames.prefer_contains,
LintNames.prefer_final_fields,
LintNames.prefer_for_elements_to_map_fromiterable,
// LintNames.prefer_function_declarations_over_variables,
LintNames.prefer_if_null_operators,
// LintNames.prefer_initializing_formals,
LintNames.prefer_inlined_adds,
LintNames.prefer_interpolation_to_compose_strings,
LintNames.prefer_is_not_operator,
LintNames.prefer_null_aware_operators,
LintNames.prefer_spread_collections,
LintNames.recursive_getters,
LintNames.slash_for_doc_comments,
LintNames.type_init_formals,
LintNames.unnecessary_brace_in_string_interps,
LintNames.unnecessary_const,
LintNames.unnecessary_constructor_name,
LintNames.unnecessary_getters_setters,
LintNames.unnecessary_late,
// LintNames.unnecessary_library_name,
// LintNames.unnecessary_new,
LintNames.unnecessary_null_aware_assignments,
LintNames.unnecessary_null_in_if_null_operators,
LintNames.unnecessary_nullable_for_final_variable_declarations,
LintNames.unnecessary_string_escapes,
LintNames.unnecessary_string_interpolations,
LintNames.unnecessary_this,
LintNames.unnecessary_to_list_in_spreads,
LintNames.unnecessary_underscores,
// LintNames.use_function_type_syntax_for_parameters,
LintNames.use_null_aware_elements,
LintNames.use_rethrow_when_possible,
// LintNames.use_super_parameters,
// The lints from the 'flutter' lint set.
// LintNames.avoid_print,
LintNames.avoid_unnecessary_containers,
LintNames.avoid_web_libraries_in_flutter,
LintNames.no_logic_in_create_state,
// LintNames.prefer_const_constructors_in_immutables,
LintNames.sized_box_for_whitespace,
LintNames.sort_child_properties_last,
LintNames.use_build_context_synchronously,
LintNames.use_full_hex_values_for_flutter_colors,
// LintNames.use_key_in_widget_constructors,
// Additional lints.
//
// At this point these are purely speculative. The goal of any additional
// lints should be to conform to common standards or to catch errors, but
// there is no process at this point for deciding which lints to add.
//
// LintNames.empty_container_bodies,
// LintNames.prefer_single_quotes,
// LintNames.unnecessary_const_in_enum_constructor,
LintNames.unnecessary_constructor_name,
LintNames.unnecessary_primary_constructor_body,
// LintNames.unnecessary_type_name_in_constructor,
LintNames.var_with_no_type_annotation,
];
/// The snippet being tested.
final _SnippetData snippet;
/// Initialize a newly created test to test the given [snippet].
_SnippetTest(this.snippet) {
writeTestPackageAnalysisOptionsFile(
analysisOptionsContent(
experimentalFeatures: snippet.experimentalFeatures,
),
);
}
@override
String? get testPackageLanguageVersion {
return snippet.languageVersion;
}
@override
String get testPackageRootPath => '$workspaceRootPath/docTest';
@override
void setUp() {
super.setUp();
_createAnalysisOptionsFile();
_createAuxiliaryFiles(snippet.auxiliaryFiles);
addTestFile(snippet.content);
}
void _createAnalysisOptionsFile() {
var lintCode = snippet.lintCode;
var rules = [?lintCode, ...lints];
var experimentalFeatures = snippet.experimentalFeatures;
if (rules.isNotEmpty || experimentalFeatures.isNotEmpty) {
writeTestPackageAnalysisOptionsFile(
analysisOptionsContent(
rules: rules,
experimentalFeatures: experimentalFeatures,
),
);
}
}
void _createAuxiliaryFiles(Map<String, String> auxiliaryFiles) {
var packageConfigBuilder = PackageConfigFileBuilder();
for (String uriStr in auxiliaryFiles.keys) {
if (uriStr.startsWith('package:')) {
Uri uri = Uri.parse(uriStr);
String packageName = uri.pathSegments[0];
String packageRootPath = '/packages/$packageName';
packageConfigBuilder.add(
name: packageName,
rootFolder: getFolder(packageRootPath),
);
String pathInLib = uri.pathSegments.skip(1).join('/');
newFile('$packageRootPath/lib/$pathInLib', auxiliaryFiles[uriStr]!);
} else {
newFile('$testPackageRootPath/$uriStr', auxiliaryFiles[uriStr]!);
}
}
writeTestPackageConfig(
packageConfigBuilder,
angularMeta: true,
ffi: true,
flutter: true,
meta: true,
);
}
}