Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@ jobs:
git config user.name VGV Bot
git config user.email [email protected]

- name: ✍️ Make changes
- name: ✍️ Make changes for exclusion table
if: ${{ env.did_change == 'true' }}
run: dart lib/exclusion_reason_table.dart

- name: 📝 Create Pull Request
- name: 📝 Create Pull Request for exclusion table
if: ${{ env.did_change == 'true' }}
uses: peter-evans/[email protected]
with:
base: main
branch: chore/update-spdx-license
branch: chore/update-exclusion-table
commit-message: "docs: update exclusion table"
title: "docs: update exclusion table"
body: |
Expand All @@ -54,3 +54,27 @@ jobs:
author: VGV Bot <[email protected]>
assignees: vgvbot
committer: VGV Bot <[email protected]>

- name: 🔍 Check for deprecated rules changes
id: deprecated
run: (dart bin/analyze.dart --set-exit-if-changed && echo "deprecated_rules_changed=false" >> $GITHUB_ENV) || echo "deprecated_rules_changed=true" >> $GITHUB_ENV

- name: ✍️ Remove deprecated rules
if: ${{ env.deprecated_rules_changed == 'true' }}
run: dart bin/remove_deprecated_rules.dart

- name: 📝 Create Pull Request for deprecated rules
if: ${{ env.deprecated_rules_changed == 'true' }}
uses: peter-evans/[email protected]
with:
base: main
branch: feat/remove-deprecated-rules
commit-message: "feat: remove deprecated rules"
title: "feat: remove deprecated rules"
body: |
There are now rules deprecated that require an update to the Very Good Analysis.
labels: bot
author: VGV Bot <[email protected]>
assignees: vgvbot
committer: VGV Bot <[email protected]>

38 changes: 14 additions & 24 deletions .github/workflows/tool_linter_rules.yaml
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
name: linter_rules (tool)

on: pull_request
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
pull_request:
paths:
- 'tool/linter_rules/**'
- '.github/workflows/tool_linter_rules.yaml'
- 'pubspec.yaml'

jobs:
build:
defaults:
run:
working-directory: tool/linter_rules

runs-on: ubuntu-latest

steps:
- name: 📚 Git Checkout
uses: actions/checkout@v5

- name: 🎯 Setup Dart
uses: dart-lang/setup-dart@v1
with:
sdk: 3.8.0

- name: 📦 Install Dependencies
run: dart pub get

- name: ✨ Check Formatting
run: dart format --set-exit-if-changed .

- name: 🕵️ Analyze
run: dart analyze --fatal-infos --fatal-warnings
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1
with:
dart_sdk: 3.8.0
working_directory: tool/linter_rules
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,23 @@ final _latestAnalysisVersionRegExp = RegExp(
r'analysis_options\.(\d+\.\d+\.\d+)\.yaml',
);

void main(List<String> args) {
final analysisOptionsFile = File('lib/analysis_options.yaml');
void main(List<String> args) => bumpVersion(args[0]);

/// Bumps the version of the analysis options file and the pubspec.yaml file.
void bumpVersion(String newVersion, {String basePath = ''}) {
final analysisOptionsFile = File('${basePath}lib/analysis_options.yaml');
final content = analysisOptionsFile.readAsStringSync();
final latestVersion = _latestAnalysisVersionRegExp
.firstMatch(content)
?.group(1);

final latestAnalysisOptionsFile = File(
'lib/analysis_options.$latestVersion.yaml',
'${basePath}lib/analysis_options.$latestVersion.yaml',
);

final newVersion = args[0];
final newAnalysisOptionsFile = File('lib/analysis_options.$newVersion.yaml');
final newAnalysisOptionsFile = File(
'${basePath}lib/analysis_options.$newVersion.yaml',
);
latestAnalysisOptionsFile.copySync(newAnalysisOptionsFile.path);

final newContent = content.replaceFirst(
Expand Down
8 changes: 8 additions & 0 deletions tool/bump_version/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: bump_version
description: Bumps the version of the analysis options file and the Very Good Analysis package.
version: 0.1.0+1
publish_to: none

environment:
sdk: ^3.8.0

3 changes: 2 additions & 1 deletion tool/linter_rules/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
.dart_tool/
.packages
build/
pubspec.lock
pubspec.lock
coverage/
32 changes: 32 additions & 0 deletions tool/linter_rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,35 @@ dart bin/analyze.dart $version
```

Where version is the existing Very Good Analysis version you would like to analyze, for example `9.0.0`.


## Check and remove deprecated rules 🔍

The script `tool/linter_rules/bin/remove_deprecated_rules.dart` helps maintain Very Good Analysis by automatically handling deprecated linter rules.

This script will:

- Fetch the latest rules from the official Dart SDK
- Identify any deprecated rules currently used in Very Good Analysis
- Remove deprecated rules from the analysis options file by creating a new minor version of Very Good Analysis without the deprecated rules
- Assign the new file as the latest version in lib/analysis_options.yaml
- Add removed rules to the exclusion table with reason 'Deprecated'

### Usage

To check and remove deprecated rules, run the following command (from `tool/linter_rules`):

```sh
dart bin/remove_deprecated_rules.dart
```


## Automations

There is a [GitHub workflow](../../.github/workflows/bot_updater.yaml) that automates the maintenance of the linter rules. This workflow runs on a schedule (every weekday) and can also be triggered manually.

It performs two main tasks:

1. **Update Exclusion Table**: It checks if there are any changes to the exclusion reasons for linter rules. If there are, it regenerates the exclusion table in this README and creates a pull request with the title `docs: update exclusion table`.

2. **Remove Deprecated Rules**: It checks for deprecated linter rules currently used in Very Good Analysis. If any are found, it automatically removes them from the analysis options, creates a new version of the analysis options file, and opens a pull request with the changes, titled `feat: remove deprecated rules`.
46 changes: 28 additions & 18 deletions tool/linter_rules/bin/analyze.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import 'dart:convert';
import 'dart:io';

import 'package:http/http.dart';
import 'package:args/args.dart';
import 'package:linter_rules/linter_rules.dart';

/// The [Uri] to fetch all linter rules from.
final Uri _allLinterRulesUri = Uri.parse(
'https://raw.githubusercontent.com/dart-lang/site-www/refs/heads/main/src/_data/linter_rules.json',
);

/// Compares Very Good Analysis with the all available Dart linter rules.
///
/// Should be run from the root of the `linter_rules` package (tool/linter_rules).
Expand All @@ -27,6 +22,9 @@ final Uri _allLinterRulesUri = Uri.parse(
/// dart bin/analyze.dart 5.1.0
/// ```
///
/// Set `--set-exit-if-changed` to exit with code 2 if there are deprecated
/// rules in the given Very Good Analysis version.
///
/// It will log information about:
/// - The number of Dart linter rules fetched.
/// - The number of rules being declared in the given Very Good Analysis
Expand All @@ -37,24 +35,32 @@ Future<void> main(
List<String> args, {
void Function(String) log = print,
}) async {
final version = args.isNotEmpty ? args[0] : latestVgaVersion();
final argsParser = ArgParser()
..addOption(
'version',
help:
'The version of the VGA to check for deprecated rules. '
'If not provided, the latest version will be used.',
)
..addFlag(
'set-exit-if-changed',
help:
'''Set the exit code to 2 if there are changes to the deprecated rules.''',
);

final response = await get(_allLinterRulesUri);
final json = jsonDecode(response.body) as List<dynamic>;
final parsedArgs = argsParser.parse(args);

final dartRules = json
.map((rule) => LinterRule.fromJson(rule as Map<String, dynamic>))
.toList();
log('Fetched ${dartRules.length} Dart linter rules');
final version = parsedArgs['version'] as String? ?? latestVgaVersion();
final setExitIfChanged = parsedArgs['set-exit-if-changed'] as bool;

final dartRules = await allLinterRules(state: LinterRuleState.deprecated);
log('Fetched ${dartRules.length} deprecated Dart linter rules');

final vgaRules = await allVeryGoodAnalysisRules(version: version);
log('Fetched ${vgaRules.length} Very Good Analysis rules');
log('');

final deprecatedDartRules = dartRules
.where((rule) => rule.state == LinterRuleState.deprecated)
.map((rule) => rule.name)
.toSet();
final deprecatedDartRules = dartRules.map((rule) => rule.name).toSet();
final deprecatedVgaRules = vgaRules
.where(deprecatedDartRules.contains)
.toList();
Expand All @@ -65,4 +71,8 @@ Future<void> main(
deprecationMessage.write('\n - $rule');
}
log(deprecationMessage.toString());

if (deprecatedVgaRules.isNotEmpty && setExitIfChanged) {
exit(2);
}
}
117 changes: 117 additions & 0 deletions tool/linter_rules/bin/remove_deprecated_rules.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import 'dart:io';

import 'package:bump_version/bump_version.dart';
import 'package:linter_rules/linter_rules.dart';
import 'package:yaml_edit/yaml_edit.dart';

/// Removes deprecated rules from the analysis options file based on the
/// official Dart linter rules.
///
/// It will create a new version of the analysis options file and update the
/// exclusion reasons file and the table of excluded rules in the README.md
/// file.
Future<void> main({
void Function(String) log = print,
}) async {
const basePath = '../../';
final deprecatedRules = await allLinterRules(
state: LinterRuleState.deprecated,
);
final deprecatedRulesCount = deprecatedRules.length;
log('Fetched $deprecatedRulesCount Dart deprecated linter rules');
log('');

if (deprecatedRulesCount == 0) {
log('No deprecated Dart linter rules found.');
return;
}

final latestVersion = latestVgaVersion();
log('Latest Very Good Analysis version: $latestVersion');
log('');

final latestVgaRules = await allVeryGoodAnalysisRules(
version: latestVersion,
);
log('Fetched ${latestVgaRules.length} Very Good Analysis linter rules');
log('');

final deprecatedVgaRules = latestVgaRules
.where(
(rule) => deprecatedRules.any((dartRule) => dartRule.name == rule),
)
.toList();
final deprecatedVgaRulesCount = deprecatedVgaRules.length;
log(
'Found $deprecatedVgaRulesCount deprecated Very Good Analysis rules:',
);

if (deprecatedVgaRulesCount == 0) {
log('No deprecated Very Good Analysis rules found.');
return;
}

for (final rule in deprecatedVgaRules) {
log(' - $rule');
}
log('');

//// Update the exclusion reasons file.
final currentExclusionReasons = await readExclusionReasons();
final newExclusionReasons = currentExclusionReasons
..addAll({
for (final rule in deprecatedVgaRules) rule: 'Deprecated',
});
await writeExclusionReasons(newExclusionReasons);
log('''Updated the exclusion reasons file.''');
log('');

//// Bump the version of the Very Good Analysis package.
final parts = latestVersion.split('.');
// Increment the minor version.
final newVersion = '${parts[0]}.${int.parse(parts[1]) + 1}.0';
bumpVersion(
newVersion,
basePath: basePath,
);
log('Bumped Very Good Analysis version to $newVersion');
log('');

//// Remove deprecated rules from the analysis options file.
final analysisOptionsFile = File(
'$basePath/lib/analysis_options.$newVersion.yaml',
);
_removeLinterRules(
analysisOptionsFile.path,
deprecatedVgaRules,
);

//// Update the table of excluded rules in the README.md file.
final readme = Readme();
final currentExclusionReasonsKeys = currentExclusionReasons.keys.toList();
final markdownTable = readme.generateExcludedRulesTable(
currentExclusionReasonsKeys,
currentExclusionReasons,
);
await readme.updateTagContent(excludedRulesTableTag, '\n$markdownTable');

log('''Updated the README.md file with the excluded rules table.''');
}

void _removeLinterRules(String filePath, List<String> ruleNames) {
final yamlEditor = YamlEditor(File(filePath).readAsStringSync());

// Get the current rules list
final rules = yamlEditor.parseAt(['linter', 'rules']) as List;

// Remove rules in reverse order to avoid index shifting
for (final ruleName in ruleNames.reversed) {
final index = rules.indexOf(ruleName);
if (index != -1) {
yamlEditor.remove(['linter', 'rules', index]);
}
}

// Write back to file
File(filePath).writeAsStringSync(yamlEditor.toString());
}
Loading