diff --git a/.github/workflows/bot_update_exclusions.yaml b/.github/workflows/bot_update_exclusions.yaml new file mode 100644 index 0000000..3389a42 --- /dev/null +++ b/.github/workflows/bot_update_exclusions.yaml @@ -0,0 +1,56 @@ +name: bot_update_exclusions + +on: + # This should ideally trigger whenever there is a commit to the [Dart Linter rules](https://raw.githubusercontent.com/dart-lang/sdk/main/pkg/linter/tool/machine/rules.json). + # However, this is not yet possible see: https://github.com/orgs/community/discussions/26323 + schedule: + # At 08:06 on every day-of-week from Monday through Friday. + - cron: "6 8 * * 1-5" + workflow_dispatch: + +jobs: + build: + defaults: + run: + working-directory: tool/linter_rules + + runs-on: ubuntu-latest + + steps: + - name: 📚 Git Checkout + uses: actions/checkout@v4 + + - name: 🎯 Setup Dart + uses: dart-lang/setup-dart@v1 + + - name: 📦 Install Dependencies + run: dart pub get + + - name: 🔍 Check for changes + id: make + run: if dart lib/exclusion_reason_table.dart --set-exit-if-changed; then echo "did_change=false"; else echo "did_change=true"; fi >> $GITHUB_ENV + + - name: 🔑 Config Git User + if: ${{ env.did_change == 'true' }} + run: | + git config user.name VGV Bot + git config user.email vgvbot@users.noreply.github.com + + - name: ✍️ Make changes + if: ${{ env.did_change == 'true' }} + run: dart lib/exclusion_reason_table.dart + + - name: 📝 Create Pull Request + if: ${{ env.did_change == 'true' }} + uses: peter-evans/create-pull-request@v7.0.5 + with: + base: main + branch: chore/update-spdx-license + commit-message: "docs: update exclusion table" + title: "docs: update exclusion table" + body: | + There are rules that require an update to their exclusion reasons. + labels: bot + author: VGV Bot + assignees: vgvbot + committer: VGV Bot diff --git a/README.md b/README.md index a6515a3..a9c6333 100644 --- a/README.md +++ b/README.md @@ -147,4 +147,4 @@ Below is a list of rules that are not enabled by default together with the reaso [pub_badge_link]: https://pub.dartlang.org/packages/very_good_analysis [very_good_ventures_link]: https://verygood.ventures [very_good_ventures_link_dark]: https://verygood.ventures#gh-dark-mode-only -[very_good_ventures_link_light]: https://verygood.ventures#gh-light-mode-only +[very_good_ventures_link_light]: https://verygood.ventures#gh-light-mode-only \ No newline at end of file diff --git a/tool/linter_rules/README.md b/tool/linter_rules/README.md index 525af37..4c34b38 100644 --- a/tool/linter_rules/README.md +++ b/tool/linter_rules/README.md @@ -19,13 +19,23 @@ The reasons are defined in the [`exclusion_reasons.json`](exclusion_reasons.json To generate the exclusion reason table, run the following command (from `tool/linter_rules`, and don't forget to `dart pub get`): ```sh -dart lib/exclusion_reason_table.dart $version +dart lib/exclusion_reason_table.dart ``` -This command will update the README table for the rules that are not enabled by default in the specified `$version` of Very Good Analysis. The `$version` is a user specified argument and it should be in the format `x.y.z`. In addition, no longer excluded rules will be removed from the `exclusion_reasons.json` file. The command does not format the output, so it is recommended to format both files with your preferred formatter after running the command. +This command will update the README table for the rules that are not enabled by default in the specified `$version` of Very Good Analysis. + +In addition, no longer excluded rules will be removed from the `exclusion_reasons.json` file. The command does not format the output, so it is recommended to format both files with your preferred formatter after running the command. Rules that are missing a reason in the `exclusion_reasons.json` file will be given the reason `Not specified`. +### Options + +| Option | Description | Default | +| ------------------- | --------------------------------------------------------------------- | --------------------------------------- | +| version | The Very Good Analysis version to use. | latest (from lib/analysis_options.yaml) | +| set-exit-if-changed | Set the exit code to 2 if there are changes to the exclusion reasons. | false | + + ## Inspection 🔍 If you're looking to update Very Good Analysis you might want to inspect the health of the latest rule set. You can use the script at `bin/inspect.dart` to do exactly that. diff --git a/tool/linter_rules/lib/exclusion_reason_table.dart b/tool/linter_rules/lib/exclusion_reason_table.dart index 1965587..de68904 100644 --- a/tool/linter_rules/lib/exclusion_reason_table.dart +++ b/tool/linter_rules/lib/exclusion_reason_table.dart @@ -1,3 +1,7 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:collection/collection.dart'; import 'package:linter_rules/linter_rules.dart'; /// The reason to fallback to if no reason is found in the exclusion reasons @@ -35,25 +39,41 @@ String _linterRuleLink(String rule) { /// with the version of the Very Good Analysis to update the documentation for /// as the first argument. /// -/// The version argument should be in the format of `x.y.z`. For example, -/// `5.1.0`. +/// The new table will be written to the README.md file. However, it might not +/// follow the same formatting as the rest of the file, so it is recommended to +/// manually format it after running the tool. +/// +/// ## Usage /// /// To use the tool run (from tool/linter_rules): /// ```sh -/// dart lib/exclusion_reason_table.dart $version +/// dart lib/exclusion_reason_table.dart /// ``` /// -/// Where `$version` is the version of the Very Good Analysis to log the table -/// for. +/// ## Options /// -/// The new table will be written to the README.md file. However, it might not -/// follow the same formatting as the rest of the file, so it is recommended to -/// manually format it after running the tool. +/// * `--version`: The version of the Very Good Analysis to update the table +/// for, defaults to the latest version found in the lib analysis options file. +/// * `--set-exit-if-changed`: Set the exit code to 2 if there are changes to +/// the exclusion reasons. Future main( List args, { void Function(String) log = print, }) async { - final version = args[0]; + final argsParser = ArgParser() + ..addOption( + 'version', + help: 'The version of the Very Good Analysis to update the table for.', + ) + ..addFlag( + 'set-exit-if-changed', + help: + '''Set the exit code to 2 if there are changes to the exclusion reasons.''', + ); + final parsedArgs = argsParser.parse(args); + + final version = parsedArgs['version'] as String? ?? latestVgaVersion(); + final setExitIfChanged = parsedArgs['set-exit-if-changed'] as bool; final linterRules = (await allLinterRules()).toSet(); log('Found ${linterRules.length} available linter rules'); @@ -71,6 +91,14 @@ Future main( for (final rule in excludedRules) rule: previousExclusionReasons[rule] ?? _noReasonFallback, }; + + final hasChanged = !const DeepCollectionEquality() + .equals(previousExclusionReasons, exclusionReasons); + if (!hasChanged) { + log('No changes to the exclusion reasons'); + return; + } + await writeExclusionReasons(exclusionReasons); final markdownTable = generateMarkdownTable( @@ -86,4 +114,8 @@ Future main( await Readme().updateTagContent(_excludedRulesTableTag, '\n$markdownTable'); log('''Updated the README.md file with the excluded rules table.'''); + + if (hasChanged && setExitIfChanged) { + exit(2); + } } diff --git a/tool/linter_rules/lib/linter_rules.dart b/tool/linter_rules/lib/linter_rules.dart index 6b1f508..a1ce8b9 100644 --- a/tool/linter_rules/lib/linter_rules.dart +++ b/tool/linter_rules/lib/linter_rules.dart @@ -3,6 +3,7 @@ library; export 'src/all_linter_rules.dart'; export 'src/all_vga_rules.dart'; +export 'src/latest_vga_version.dart'; export 'src/linter_rules_reasons.dart'; export 'src/markdown_table_generator.dart'; export 'src/models/models.dart'; diff --git a/tool/linter_rules/lib/src/latest_vga_version.dart b/tool/linter_rules/lib/src/latest_vga_version.dart new file mode 100644 index 0000000..aa32d5a --- /dev/null +++ b/tool/linter_rules/lib/src/latest_vga_version.dart @@ -0,0 +1,36 @@ +import 'dart:io'; + +import 'package:path/path.dart' as path; + +/// The file path containing the latest Very Good Analysis options version. +/// +/// It assumes that the current directory is the root of the `linter_rules` +/// package (tool/linter_rules). +final _latestVeryGoodAnalaysisFilePath = path.joinAll( + ['..', '..', 'lib', 'analysis_options.yaml'], +); + +/// Returns the latest Very Good Analysis version from the analysis options +/// file. +String latestVgaVersion() { + final analysisOptionsFile = File(_latestVeryGoodAnalaysisFilePath); + + if (!analysisOptionsFile.existsSync()) { + throw ArgumentError( + '''Could not find analysis options file at ${analysisOptionsFile.path}''', + ); + } + + final yaml = analysisOptionsFile.readAsStringSync(); + + final versionRegex = RegExp(r'analysis_options\.(\d+\.\d+\.\d+)\.yaml'); + final match = versionRegex.firstMatch(yaml); + + if (match == null) { + throw ArgumentError( + '''Could not find Very Good Analysis version in ${analysisOptionsFile.path}''', + ); + } + + return match.group(1)!; +} diff --git a/tool/linter_rules/pubspec.yaml b/tool/linter_rules/pubspec.yaml index 0986e55..6445b91 100644 --- a/tool/linter_rules/pubspec.yaml +++ b/tool/linter_rules/pubspec.yaml @@ -7,6 +7,8 @@ environment: sdk: ^3.4.0 dependencies: + args: ^2.6.0 + collection: ^1.19.1 http: ^1.2.1 meta: ^1.16.0 path: ^1.9.0