contribute a command to reconcile the analysis_options.yaml file with standardized lint sets
diff --git a/analysis_options.yaml b/analysis_options.yaml index f53b40a..b78a6d9 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml
@@ -1,229 +1,246 @@ # Specify analysis options. # -# This file is a copy of analysis_options.yaml from flutter repo -# as of 2022-07-27, but with some modifications marked with -# "DIFFERENT FROM FLUTTER/FLUTTER" below. The file is expected to -# be kept in sync with the master file from the flutter repo. +# This file is a copy of analysis_options.yaml from flutter repo as of +# 2022-07-27, but with some modifications marked with "DIFFERENT FROM +# FLUTTER/FLUTTER" below. The file is expected to be kept in sync with the +# main file from the flutter repo. analyzer: language: strict-casts: true strict-raw-types: true errors: - # allow self-reference to deprecated members (we do this because otherwise we have - # to annotate every member in every test, assert, etc, when we deprecate something) + # Allow self-reference to deprecated members (we do this because otherwise + # we have to annotate every member in every test, assert, etc, when we + # deprecate something). deprecated_member_use_from_same_package: ignore - exclude: # DIFFERENT FROM FLUTTER/FLUTTER - # Ignore generated files + exclude: + # DIFFERENT FROM FLUTTER/FLUTTER: Ignore generated files - '**/*.g.dart' - '**/*.mocks.dart' # Mockito @GenerateMocks linter: rules: - # This list is derived from the list of all available lints located at - # https://github.com/dart-lang/linter/blob/master/example/all.yaml + # package:lints/core.yaml [32 lints] + - avoid_empty_else + - avoid_relative_lib_imports + - avoid_shadowing_type_parameters + - avoid_types_as_parameter_names + - await_only_futures + - camel_case_extensions + - camel_case_types + - collection_methods_unrelated_type + - curly_braces_in_flow_control_structures + - dangling_library_doc_comments + - depend_on_referenced_packages + - empty_catches + - file_names + - hash_and_equals + - implicit_call_tearoffs + - no_duplicate_case_values + - non_constant_identifier_names + - null_check_on_nullable_type_parameter + - package_prefixed_library_names + - prefer_generic_function_type_aliases + - prefer_is_empty + - prefer_is_not_empty + - prefer_iterable_whereType + - prefer_typing_uninitialized_variables + - provide_deprecation_message + - secure_pubspec_urls + - type_literal_in_constant_pattern + - unnecessary_overrides + - unrelated_type_equality_checks + - use_string_in_part_of_directives + - valid_regexps + - void_checks + + # package:lints/recommended.yaml [55 lints] + - annotate_overrides + - avoid_function_literals_in_foreach_calls + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null_for_void + - avoid_single_cascade_in_expression_statements + # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + - empty_constructor_bodies + - empty_statements + - exhaustive_cases + - implementation_imports + - library_names + - library_prefixes + - library_private_types_in_public_api + - no_leading_underscores_for_library_prefixes + - no_leading_underscores_for_local_identifiers + - null_closures + - overridden_fields + - package_names + - prefer_adjacent_string_concatenation + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_contains + - prefer_final_fields + - prefer_for_elements_to_map_fromIterable + - prefer_function_declarations_over_variables + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_interpolation_to_compose_strings + - prefer_is_not_operator + - prefer_null_aware_operators + - prefer_spread_collections + - recursive_getters + - slash_for_doc_comments + - type_init_formals + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_constructor_name + - unnecessary_getters_setters + - unnecessary_late + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - unnecessary_to_list_in_spreads + - use_function_type_syntax_for_parameters + - use_rethrow_when_possible + - use_super_parameters + + # package:dart_flutter_team_lints/analysis_options.yaml [26 lints] - always_declare_return_types + # - avoid_catching_errors # blocked on https://github.com/dart-lang/linter/issues/3023 + - avoid_dynamic_calls + # - combinators_ordering # DIFFERENT FROM FLUTTER/FLUTTER: This isn't available on stable yet. + # - comment_references # blocked on https://github.com/dart-lang/linter/issues/1142 + - conditional_uri_does_not_exist + - directives_ordering + - library_annotations + # - lines_longer_than_80_chars # not required by flutter style + # - omit_local_variable_types # opposite of always_specify_types + - only_throw_errors + - prefer_asserts_in_initializer_lists + - prefer_const_constructors + - prefer_relative_imports + - prefer_single_quotes + - sort_pub_dependencies + - test_types_in_equals + - throw_in_finally + # - type_annotate_public_apis # subset of always_specify_types + - unawaited_futures + # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 + - unnecessary_library_directive + - unnecessary_parenthesis + - unnecessary_statements + - unreachable_from_main + - use_is_even_rather_than_modulo + + # Additional customizations (full list at + # https://github.com/dart-lang/linter/blob/master/example/all.yaml) - always_put_control_body_on_new_line # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 - always_require_non_null_named_parameters - always_specify_types # - always_use_package_imports # we do this commonly - - annotate_overrides # - avoid_annotating_with_dynamic # conflicts with always_specify_types - avoid_bool_literals_in_conditional_expressions # - avoid_catches_without_on_clauses # blocked on https://github.com/dart-lang/linter/issues/3023 - # - avoid_catching_errors # blocked on https://github.com/dart-lang/linter/issues/3023 - avoid_classes_with_only_static_members - avoid_double_and_int_checks - - avoid_dynamic_calls - - avoid_empty_else - avoid_equals_and_hash_code_on_mutable_classes - avoid_escaping_inner_quotes - avoid_field_initializers_in_const_classes # - avoid_final_parameters # incompatible with prefer_final_parameters - - avoid_function_literals_in_foreach_calls - avoid_implementing_value_types - - avoid_init_to_null - avoid_js_rounded_ints # - avoid_multiple_declarations_per_line # seems to be a stylistic choice we don't subscribe to - - avoid_null_checks_in_equality_operators # - avoid_positional_boolean_parameters # would have been nice to enable this but by now there's too many places that break it - avoid_print # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) - avoid_redundant_argument_values - - avoid_relative_lib_imports - - avoid_renaming_method_parameters - - avoid_return_types_on_setters - avoid_returning_null - avoid_returning_null_for_future - - avoid_returning_null_for_void # - avoid_returning_this # there are enough valid reasons to return `this` that this lint ends up with too many false positives - avoid_setters_without_getters - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - avoid_slow_async_io - avoid_type_to_string - - avoid_types_as_parameter_names # - avoid_types_on_closure_parameters # conflicts with always_specify_types - avoid_unnecessary_containers - avoid_unused_constructor_parameters - avoid_void_async # - avoid_web_libraries_in_flutter # we use web libraries in web-specific code, and our tests prevent us from using them elsewhere - - await_only_futures - - camel_case_extensions - - camel_case_types - cancel_subscriptions # - cascade_invocations # doesn't match the typical style of this repo - cast_nullable_to_non_nullable # - close_sinks # not reliable enough - - collection_methods_unrelated_type - # - combinators_ordering # DIFFERENT FROM FLUTTER/FLUTTER: This isn't available on stable yet. - # - comment_references # blocked on https://github.com/dart-lang/linter/issues/1142 - - conditional_uri_does_not_exist - # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 - - control_flow_in_finally - - curly_braces_in_flow_control_structures - - depend_on_referenced_packages - deprecated_consistency # - diagnostic_describe_all_properties # enabled only at the framework level (packages/flutter/lib) - - directives_ordering # - discarded_futures # not yet tested # - do_not_use_environment # there are appropriate times to use the environment, especially in our tests and build logic - - empty_catches - - empty_constructor_bodies - - empty_statements - eol_at_end_of_file - - exhaustive_cases - - file_names - flutter_style_todos - - hash_and_equals - - implementation_imports # - join_return_with_assignment # not required by flutter style - leading_newlines_in_multiline_strings - - library_names - - library_prefixes - - library_private_types_in_public_api - # - lines_longer_than_80_chars # not required by flutter style # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/linter/issues/453 - missing_whitespace_between_adjacent_strings - no_adjacent_strings_in_list - no_default_cases - - no_duplicate_case_values - - no_leading_underscores_for_library_prefixes - - no_leading_underscores_for_local_identifiers - no_logic_in_create_state - no_runtimeType_toString # DIFFERENT FROM FLUTTER/FLUTTER - - non_constant_identifier_names - noop_primitive_operations - - null_check_on_nullable_type_parameter - - null_closures - # - omit_local_variable_types # opposite of always_specify_types # - one_member_abstracts # too many false positives - only_throw_errors # this does get disabled in a few places where we have legacy code that uses strings et al - - overridden_fields - package_api_docs - - package_names - - package_prefixed_library_names # - parameter_assignments # we do this commonly - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists # - prefer_asserts_with_message # not required by flutter style - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - prefer_const_constructors_in_immutables - prefer_const_declarations - prefer_const_literals_to_create_immutables # - prefer_constructors_over_static_methods # far too many false positives - - prefer_contains # - prefer_double_quotes # opposite of prefer_single_quotes - prefer_equal_for_default_values # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - - prefer_final_fields - prefer_final_in_for_each - prefer_final_locals # - prefer_final_parameters # we should enable this one day when it can be auto-fixed (https://github.com/dart-lang/linter/issues/3104), see also parameter_assignments - - prefer_for_elements_to_map_fromIterable - prefer_foreach - - prefer_function_declarations_over_variables - - prefer_generic_function_type_aliases - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds # - prefer_int_literals # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#use-double-literals-for-double-constants - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_is_not_operator - - prefer_iterable_whereType # - prefer_mixin # Has false positives, see https://github.com/dart-lang/linter/issues/3018 # - prefer_null_aware_method_calls # "call()" is confusing to people new to the language since it's not documented anywhere - - prefer_null_aware_operators - - prefer_relative_imports - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - prefer_void_to_null - - provide_deprecation_message - public_member_api_docs # DIFFERENT FROM FLUTTER/FLUTTER - - recursive_getters # - require_trailing_commas # blocked on https://github.com/dart-lang/sdk/issues/47441 - - secure_pubspec_urls - sized_box_for_whitespace # - sized_box_shrink_expand # not yet tested - - slash_for_doc_comments - sort_child_properties_last - sort_constructors_first - sort_pub_dependencies # DIFFERENT FROM FLUTTER/FLUTTER: Flutter's use case for not sorting does not apply to this repository. - sort_unnamed_constructors_first - - test_types_in_equals - - throw_in_finally - tighten_type_of_initializing_formals - # - type_annotate_public_apis # subset of always_specify_types - - type_init_formals - unawaited_futures # DIFFERENT FROM FLUTTER/FLUTTER: It's disabled there for "too many false positives"; that's not an issue here, and missing awaits have caused production issues in plugins. - unnecessary_await_in_return - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_constructor_name # - unnecessary_final # conflicts with prefer_final_locals - - unnecessary_getters_setters - # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 - - unnecessary_late - - unnecessary_new - - unnecessary_null_aware_assignments - unnecessary_null_aware_operator_on_extension_on_nullable - unnecessary_null_checks - - unnecessary_null_in_if_null_operators - - unnecessary_nullable_for_final_variable_declarations - - unnecessary_overrides - - unnecessary_parenthesis # - unnecessary_raw_strings # what's "necessary" is a matter of opinion; consistency across strings can help readability more than this lint - - unnecessary_statements - - unnecessary_string_escapes - - unnecessary_string_interpolations - - unnecessary_this - - unnecessary_to_list_in_spreads - - unrelated_type_equality_checks - unsafe_html - use_build_context_synchronously # - use_colored_box # not yet tested # - use_decorated_box # not yet tested # - use_enums # not yet tested - use_full_hex_values_for_flutter_colors - - use_function_type_syntax_for_parameters - use_if_null_to_convert_nulls_to_bools - - use_is_even_rather_than_modulo - use_key_in_widget_constructors - use_late_for_private_fields_and_variables - use_named_constants - use_raw_strings - - use_rethrow_when_possible - use_setters_to_change_properties # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 - - use_super_parameters - use_test_throws_matchers # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review - - valid_regexps - - void_checks
diff --git a/script/tool/lib/src/main.dart b/script/tool/lib/src/main.dart index 89ffae2..df6c3c9 100644 --- a/script/tool/lib/src/main.dart +++ b/script/tool/lib/src/main.dart
@@ -33,6 +33,7 @@ import 'pubspec_check_command.dart'; import 'readme_check_command.dart'; import 'remove_dev_dependencies_command.dart'; +import 'update_analysis_options_command.dart'; import 'update_dependency_command.dart'; import 'update_excerpts_command.dart'; import 'update_min_sdk_command.dart'; @@ -83,6 +84,7 @@ ..addCommand(ReadmeCheckCommand(packagesDir)) ..addCommand(RemoveDevDependenciesCommand(packagesDir)) ..addCommand(DartTestCommand(packagesDir)) + ..addCommand(UpdateAnalysisOptionsCommand(packagesDir)) ..addCommand(UpdateDependencyCommand(packagesDir)) ..addCommand(UpdateExcerptsCommand(packagesDir)) ..addCommand(UpdateMinSdkCommand(packagesDir))
diff --git a/script/tool/lib/src/update_analysis_options_command.dart b/script/tool/lib/src/update_analysis_options_command.dart new file mode 100644 index 0000000..9544cc1 --- /dev/null +++ b/script/tool/lib/src/update_analysis_options_command.dart
@@ -0,0 +1,171 @@ +// Copyright 2023 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:convert'; + +import 'package:file/file.dart'; +import 'package:path/path.dart' as p; +import 'package:yaml/yaml.dart'; + +import 'common/package_command.dart'; + +// TODO(devoncarew): figure out how to test this + +/// Update the main analysis_options.yaml file using the latest set of lints +/// from package:dart_flutter_team_lints/analysis_options.yaml. +class UpdateAnalysisOptionsCommand extends PackageCommand { + /// Creates an instance of the update-analysis-options command. + UpdateAnalysisOptionsCommand(super.packagesDir); + + @override + final String name = 'update-analysis-options'; + + @override + final String description = + 'Update the main analysis_options.yaml file using the latest set of ' + 'lints from package:dart_flutter_team_lints/analysis_options.yaml.'; + + @override + Future<void> run() async { + final FileSystem fileSystem = packagesDir.fileSystem; + final Directory rootDir = packagesDir.parent; + final File optionsFile = rootDir.childFile('analysis_options.yaml'); + + print('Updating ${optionsFile.basename}:'); + + final Directory toolDir = + rootDir.childDirectory('script').childDirectory('tool'); + final Map<String, Directory> packages = _findPackageConfig(toolDir)!; + + final _Lints lints = _Lints.readFrom( + 'package:dart_flutter_team_lints/analysis_options.yaml', + packages, + fileSystem: fileSystem, + ); + + void printLints(_Lints lints) { + if (lints.parent != null) { + printLints(lints.parent!); + } + print(' ${lints.include} [${lints.lints.length} lints]'); + } + + printLints(lints); + + final StringBuffer out = StringBuffer(); + final List<String> optionsAsList = optionsFile.readAsLinesSync(); + + // copy over the preamble, up to ' rules:' + int index = 0; + while (optionsAsList[index] != ' rules:') { + out.writeln(optionsAsList[index]); + index++; + } + out.writeln(optionsAsList[index]); + + // copy the included lints + void copyLints(_Lints lints) { + if (lints.parent != null) { + copyLints(lints.parent!); + } + out.writeln(' # ${lints.include} [${lints.lints.length} lints]'); + for (final String lint in lints.lints) { + optionsAsList.remove(' - $lint'); + final int location = optionsAsList + .indexWhere((String line) => line.startsWith(' # - $lint')); + final String line = + location == -1 ? ' - $lint' : optionsAsList.removeAt(location); + out.writeln(line); + } + out.writeln(); + } + + copyLints(lints); + + // copy the additional customization, from ' # Additional customizations' + int location = optionsAsList.indexWhere( + (String line) => line.contains('# Additional customizations')); + + while (location < optionsAsList.length) { + out.writeln(optionsAsList[location]); + location++; + } + + // update the original file + optionsFile.writeAsStringSync(out.toString()); + print('Wrote update lints to ${optionsFile.path}'); + } +} + +Map<String, Directory>? _findPackageConfig(Directory dir) { + final File configFile = + dir.childDirectory('.dart_tool').childFile('package_config.json'); + if (configFile.existsSync()) { + return _parseConfigFile(configFile); + } else { + return null; + } +} + +Map<String, Directory>? _parseConfigFile(File configFile) { + final Map<String, dynamic> json = + jsonDecode(configFile.readAsStringSync()) as Map<String, dynamic>; + final List<Map<String, dynamic>> packages = + (json['packages'] as List<dynamic>).cast<Map<String, dynamic>>(); + + return Map<String, Directory>.fromIterable( + packages, + key: (dynamic package) => + (package as Map<String, dynamic>)['name'] as String, + value: (dynamic package) { + final String rootUri = + (package as Map<String, dynamic>)['rootUri'] as String; + final String filePath = Uri.parse(rootUri).toFilePath(); + if (p.isRelative(filePath)) { + return configFile.fileSystem + .directory(p.normalize(p.join(configFile.parent.path, filePath))); + } else { + return configFile.fileSystem.directory(filePath); + } + }, + ); +} + +class _Lints { + _Lints._({ + this.parent, + required this.include, + required this.lints, + }); + + static _Lints readFrom( + String include, + Map<String, Directory> packages, { + required FileSystem fileSystem, + }) { + // "package:lints/recommended.yaml" + final Uri uri = Uri.parse(include); + final String package = uri.pathSegments[0]; + final String filePath = uri.pathSegments[1]; + + final Directory dir = packages[package]!; + final File configFile = fileSystem.file(p.join(dir.path, 'lib', filePath)); + + final YamlMap yaml = loadYaml(configFile.readAsStringSync()) as YamlMap; + final String? localInclude = yaml['include'] as String?; + final YamlList lints = (yaml['linter'] as YamlMap?)?['rules'] as YamlList; + + return _Lints._( + parent: localInclude == null + ? null + : _Lints.readFrom(localInclude, packages, fileSystem: fileSystem), + include: include, + lints: lints.cast<String>().toList()..sort(), + ); + } + + final _Lints? parent; + final String include; + final List<String> lints; +}
diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml index cfc95ba..75fb90e 100644 --- a/script/tool/pubspec.yaml +++ b/script/tool/pubspec.yaml
@@ -4,6 +4,9 @@ version: 1.0.0 publish_to: none +environment: + sdk: '>=2.18.0 <4.0.0' + dependencies: args: ^2.1.0 async: ^2.6.1 @@ -27,8 +30,6 @@ dev_dependencies: build_runner: ^2.0.3 + dart_flutter_team_lints: ^2.1.0 matcher: ^0.12.10 mockito: '>=5.3.2 <=5.4.0' - -environment: - sdk: '>=2.18.0 <4.0.0'