add-dart-lint-validation-rule

作成者: flutter

dart_skills_lintに新しい検証ルールとCLIフラグを追加するための手順

npx skills add https://github.com/flutter/skills --skill add-dart-lint-validation-rule

Add a New Validation Rule and Flag

Use this skill when you need to add a new validation rule to the dart_skills_lint package, expose it as a toggleable CLI flag, and verify its behavior.


🛠️ Step-by-Step Implementation

1. Create the Rule Class

Create a new file in lib/src/rules/ extending SkillRule.

[!TIP] If your rule expects a specific structure in the skill's YAML frontmatter (e.g., inside metadata), document this structure clearly in the class Dart docstring.

// lib/src/rules/my_new_rule.dart

import '../models/analysis_severity.dart';
import '../models/skill_context.dart';
import '../models/skill_rule.dart';
import '../models/validation_error.dart';

class MyNewRule extends SkillRule {
  MyNewRule({super.severity});

  @override
  Future<List<ValidationError>> validate(SkillContext context) async {
    final errors = <ValidationError>[];
    // Add validation logic here using context.rawContent or context.directory
    return errors;
  }
}

Accessing YAML Frontmatter

If your rule needs configuration from the skill's YAML frontmatter, you can access it via context.parsedYaml.

  @override
  Future<List<ValidationError>> validate(SkillContext context) async {
    final errors = <ValidationError>[];
    final yaml = context.parsedYaml;
    if (yaml != null) {
      final metadata = yaml['metadata'];
      if (metadata is Map) {
        // Read your custom config here
      }
    }
    return errors;
  }

2. Register the Rule in lib/src/rule_registry.dart

Add a new CheckType instance to RuleRegistry.allChecks list. This automatically exposes it as a CLI flag.

// lib/src/rule_registry.dart in allChecks list

  const CheckType(
    name: MyNewRule.ruleName,
    defaultSeverity: MyNewRule.defaultSeverity,
    help: 'Description of what the rule does for CLI help.',
  ),

Then, add a case to RuleRegistry.createRule to instantiate your rule:

// lib/src/rule_registry.dart in createRule method

  static SkillRule? createRule(String name, AnalysisSeverity severity) {
    switch (name) {
      // ... other rules
      case MyNewRule.ruleName:
        return MyNewRule(severity: severity);
      default:
        return null;
    }
  }

3. Handle Disabled by Default Rules (If applicable)

If the rule is disabled by default (defaultSeverity: AnalysisSeverity.disabled), passing the flag --check-my-new-rule will automatically enable it with AnalysisSeverity.error severity (handled in entry_point.dart).


🧪 Testing the New Rule

You must write automated tests verifying your rule triggers when it should and skips when it shouldn't.

Preferred Approach: In-Memory Unit Tests

Instead of writing files to disk, test the rule directly using a mock SkillContext. This is faster and avoids I/O dependencies.

// test/my_new_rule_test.dart

import 'dart:io';
import 'package:dart_skills_lint/src/models/analysis_severity.dart';
import 'package:dart_skills_lint/src/models/skill_context.dart';
import 'package:dart_skills_lint/src/models/validation_error.dart';
import 'package:dart_skills_lint/src/rules/my_new_rule.dart';
import 'package:test/test.dart';

void main() {
  group('MyNewRule', () {
    test('flags invalid content', () async {
      final rule = MyNewRule(severity: AnalysisSeverity.warning);
      final context = SkillContext(
        directory: Directory('dummy'),
        rawContent: 'Invalid content',
      );

      final List<ValidationError> errors = await rule.validate(context);

      expect(errors, isNotEmpty);
      expect(errors.first.message, contains('Expected error message'));
    });

    test('passes valid content', () async {
      final rule = MyNewRule(severity: AnalysisSeverity.warning);
      final context = SkillContext(
        directory: Directory('dummy'),
        rawContent: 'Valid content',
      );

      final List<ValidationError> errors = await rule.validate(context);

      expect(errors, isEmpty);
    });
  });
}

Alternative Approach: File System Interaction

If the rule interacts with the file system or wraps an external CLI tool (like popmark), you should use a temporary directory for testing instead of in-memory mocks.

    late Directory tempDir;

    setUp(() async {
      tempDir = await Directory.systemTemp.createTemp('my_rule_test.');
    });

    tearDown(() async {
      if (tempDir.existsSync()) {
        await tempDir.delete(recursive: true);
      }
    });

    test('flags invalid file content', () async {
      final Directory skillDir = await Directory('${tempDir.path}/test-skill').create();
      await File('${skillDir.path}/SKILL.md').writeAsString('Invalid content');

      final rule = MyNewRule(severity: AnalysisSeverity.warning);
      final context = SkillContext(directory: skillDir, rawContent: 'Invalid content');

      final List<ValidationError> errors = await rule.validate(context);

      expect(errors, isNotEmpty);
    });

Integration Tests

If the rule interacts with CLI flags or configuration files, add a test in test/cli_integration_test.dart using TestProcess.

[!IMPORTANT] When writing integration tests that use config files and TestProcess, ensure that paths in the config file and paths passed to the CLI match in style (both relative or both absolute) to avoid issues with path matching in entry_point.dart.


📚 Documentation Updates

When a new rule is introduced, verify that you synchronize sibling markdown files!

  1. README.md:
    • Add your flag under the Flags section (under Usage) so users know it exists.
    • CRITICAL FORMATTING: You MUST use the exact format - \--[no-]`: . (Disabled by default if applicable)`.
    • CRITICAL NAMING: Ensure the flag string matches the ruleName EXACTLY. For example, if the ruleName is file-existence, the flag MUST be documented as --[no-]file-existence (do NOT hallucinate a check- prefix like --[no-]check-file-existence). Do NOT add empty bullet points.
  2. RULES.md:
    • Add a new entry for your rule documenting its default severity, fixability, what it checks, diagnostic shape, auto-fix behavior, and how to disable it. This is strictly required by the rules_md_consistency_test.dart test.
  3. documentation/knowledge/SPECIFICATION.md:
    • Document the formal constraint in the specification if it defines a standard for skill files.

🚦 Checklist Before Submitting PR

  • Rule class created in lib/src/rules/.
  • Rule registered in lib/src/rule_registry.dart.
  • Unit tests added in test/ using in-memory SkillContext.
  • CRITICAL: Usage flag correctly documented in README.md under Flags (ensure flag string matches ruleName EXACTLY and format is correct).
  • Rule documented in RULES.md.
  • Schema documented in documentation/knowledge/SPECIFICATION.md (if applicable).
  • Run dart format . to format code.
  • Run dart analyze --fatal-infos to ensure no issues.
  • Run dart test to ensure tests passing.

flutterのその他のスキル

adding-release-notes
flutter
DevToolsのリリースノートにユーザー向けの変更説明を追加します。NEXT_RELEASE_NOTES.mdファイルに改善点、修正、新機能を文書化する際に使用します。
official
dart-modern-features
flutter
モダナイゼーションの候補を見つけるには:
official
api-review
flutter
指定されたコードを標準的なAPI設計ガイドラインに照らしてレビューします。ユーザーがAPIレビューを依頼した場合や、コードをAPI設計に照らして確認したい場合にこのスキルを使用します。
official
code-documentation
flutter
効果的なコードドキュメントを書くためのガイド。docstring、JSDoc、dartdoc、実装コメントを含む。新しいコードを書く際や追加する際にこのスキルを使用する。
official
dart-add-unit-test
flutter
Write and organize unit tests for functions, methods, and classes using `package:test`. Use when creating new logic or fixing bugs to ensure code remains…
official
dart-build-cli-app
flutter
エントリポイント構造、終了コード、クロスプラットフォームのスクリプト。コマンドラインユーティリティ、スクリプト、またはアプリケーションを構築する際に使用します。
official
dart-collect-coverage
flutter
coverageパッケージを使用してカバレッジを収集し、LCOVレポートを作成します
official
dart-fix-runtime-errors
flutter
get_runtime_errors と lsp を使用してアクティブなスタックトレースを取得し、失敗している行を特定して修正を適用し、hot_reload で解決を確認します。
official