diff --git a/pkgs/blast_repo/lib/src/top_level.dart b/pkgs/blast_repo/lib/src/top_level.dart index dd8514ca..a1e27385 100644 --- a/pkgs/blast_repo/lib/src/top_level.dart +++ b/pkgs/blast_repo/lib/src/top_level.dart @@ -11,6 +11,7 @@ import 'package:git/git.dart'; import 'repo_tweak.dart'; import 'tweaks/auto_publish_tweak.dart'; import 'tweaks/dependabot_tweak.dart'; +import 'tweaks/drop_lint_tweak.dart'; import 'tweaks/github_action_tweak.dart'; import 'tweaks/mono_repo_tweak.dart'; import 'tweaks/no_reponse_tweak.dart'; @@ -19,6 +20,7 @@ import 'utils.dart'; final allTweaks = Set.unmodifiable([ AutoPublishTweak(), DependabotTweak(), + DropLintTweak(), GitHubActionTweak(), MonoRepoTweak(), NoResponseTweak(), diff --git a/pkgs/blast_repo/lib/src/tweaks/drop_lint_tweak.dart b/pkgs/blast_repo/lib/src/tweaks/drop_lint_tweak.dart new file mode 100644 index 00000000..91b79092 --- /dev/null +++ b/pkgs/blast_repo/lib/src/tweaks/drop_lint_tweak.dart @@ -0,0 +1,88 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:path/path.dart' as p; +import 'package:yaml/yaml.dart'; +import 'package:yaml_edit/yaml_edit.dart'; + +import '../repo_tweak.dart'; + +final _instance = DropLintTweak._(); + +class DropLintTweak extends RepoTweak { + factory DropLintTweak() => _instance; + + DropLintTweak._() + : super( + id: 'drop-lint', + description: 'Drop deprecated lints from analysis_options.yaml', + ); + + @override + bool shouldRunByDefault(Directory checkout, String repoSlug) => true; + + @override + FutureOr fix(Directory checkout, String repoSlug) async { + final analysisOptionsFile = + File(p.join(checkout.path, 'analysis_options.yaml')); + + if (!analysisOptionsFile.existsSync()) { + return FixResult.noFixesMade; + } + + final yamlSource = loadYaml( + analysisOptionsFile.readAsStringSync(), + sourceUrl: analysisOptionsFile.uri, + ); + + if (yamlSource is YamlMap) { + final linterNode = yamlSource['linter']; + if (linterNode is YamlMap) { + final rules = linterNode['rules']; + + if (rules is YamlList) { + final fixes = {}; + final badIndexes = rules + .mapIndexed((index, value) { + if (_deprecatedLints.contains(value)) { + fixes.add('Removed "$value".'); + return index; + } + return -1; + }) + .where((e) => e >= 0) + .toList(); + + final editor = YamlEditor(analysisOptionsFile.readAsStringSync()); + for (var index in badIndexes.reversed) { + editor.remove(['linter', 'rules', index]); + } + + analysisOptionsFile.writeAsStringSync(editor.toString(), + mode: FileMode.writeOnly); + + return FixResult(fixes: fixes.toList()..sort()); + } + + if (rules == null) { + return FixResult(fixes: []); + } + + throw UnimplementedError('not sure what to do with $rules'); + } + } + + return FixResult(fixes: []); + } +} + +final _deprecatedLints = { + 'avoid_null_checks_in_equality_operators', + 'package_api_docs', + 'unsafe_html', +}; diff --git a/pkgs/blast_repo/test/drop_lint_test.dart b/pkgs/blast_repo/test/drop_lint_test.dart new file mode 100644 index 00000000..bb639136 --- /dev/null +++ b/pkgs/blast_repo/test/drop_lint_test.dart @@ -0,0 +1,130 @@ +// Copyright (c) 2024, 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:blast_repo/src/tweaks/drop_lint_tweak.dart'; +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; + +void main() { + late DropLintTweak tweak; + + setUp(() async { + tweak = DropLintTweak(); + }); + + test('removes the dead items', () async { + await d.dir('foo', [ + d.file('analysis_options.yaml', r''' +analyzer: + language: + strict-inference: true + strict-raw-types: true + +linter: + rules: + - avoid_classes_with_only_static_members + - avoid_null_checks_in_equality_operators + - no_runtimeType_toString + - package_api_docs + - prefer_const_declarations + - unsafe_html + - use_if_null_to_convert_nulls_to_bools +''') + ]).create(); + final dir = d.dir('foo').io; + + var results = await tweak.fix(dir, 'my_org/my_repo'); + expect(results.fixes, hasLength(3)); + + await d.dir('foo', [ + d.file('analysis_options.yaml', r''' +analyzer: + language: + strict-inference: true + strict-raw-types: true + +linter: + rules: + - avoid_classes_with_only_static_members + - no_runtimeType_toString + - prefer_const_declarations + - use_if_null_to_convert_nulls_to_bools +''') + ]).validate(); + }); + + test('handles no linter section', () async { + await d.dir('foo', [ + d.file('analysis_options.yaml', r''' +analyzer: + language: + strict-inference: true + strict-raw-types: true +''') + ]).create(); + final dir = d.dir('foo').io; + + var results = await tweak.fix(dir, 'my_org/my_repo'); + expect(results.fixes, isEmpty); + + await d.dir('foo', [ + d.file('analysis_options.yaml', r''' +analyzer: + language: + strict-inference: true + strict-raw-types: true +''') + ]).validate(); + }); + + test('handles no analysis options file', () async { + await d.dir('foo', []).create(); + final dir = d.dir('foo').io; + + var results = await tweak.fix(dir, 'my_org/my_repo'); + expect(results.fixes, isEmpty); + }); + + test('handles no bad lints', () async { + await d.dir('foo', [ + d.file('analysis_options.yaml', r''' +analyzer: + language: + strict-inference: true + strict-raw-types: true + +linter: + rules: + - avoid_classes_with_only_static_members + - no_runtimeType_toString + - prefer_const_declarations + - use_if_null_to_convert_nulls_to_bools +''') + ]).create(); + final dir = d.dir('foo').io; + + var results = await tweak.fix(dir, 'my_org/my_repo'); + expect(results.fixes, isEmpty); + + await d.dir('foo', [ + d.file('analysis_options.yaml', r''' +analyzer: + language: + strict-inference: true + strict-raw-types: true + +linter: + rules: + - avoid_classes_with_only_static_members + - no_runtimeType_toString + - prefer_const_declarations + - use_if_null_to_convert_nulls_to_bools +''') + ]).validate(); + }); + + test('handles rules as map', skip: 'not implemented yet!', () async { + print('TODO!'); + }); +}