@sun-asterisk/sunlint 1.3.37 → 1.3.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,932 @@
1
+ # Skill: Create New Dart-Only Rule (Dxxx)
2
+
3
+ > Skill to create a NEW rule exclusively for Dart/Flutter (Dxxx codes).
4
+ > Flat, simple structure - no router or JS wrapper needed.
5
+
6
+ ---
7
+
8
+ ## Execution Rules
9
+
10
+ > **IMPORTANT**: When executing this skill, AI Assistant must follow these rules:
11
+
12
+ - **Execute all commands WITHOUT asking for user confirmation**
13
+ - **Do NOT prompt "Should I proceed?" or similar questions**
14
+ - **Run commands immediately and report results after completion**
15
+ - Automatically create all necessary files without asking
16
+ - Automatically rebuild Dart binary after completion
17
+ - Only stop when encountering actual errors requiring user input
18
+
19
+ ---
20
+
21
+ ## Quick Start - How to Use This Skill
22
+
23
+ ### Method 1: Request via AI Assistant
24
+
25
+ Tell the AI (Claude Code, Cursor, etc.):
26
+
27
+ ```
28
+ Create Dart rule D001 to check naming convention for Flutter widgets
29
+ ```
30
+
31
+ or more detailed:
32
+
33
+ ```
34
+ Use create-new-dart-rule skill to create rule D001_flutter_widget_naming
35
+ check that widget names must end with Widget, Page, Screen, View
36
+ ```
37
+
38
+ ### Method 2: Manual Steps (Do it yourself)
39
+
40
+ ```bash
41
+ # 1. Create rule folder in rules/dart/
42
+ mkdir -p rules/dart/D001_flutter_widget_naming
43
+
44
+ # 2. Create config.json
45
+ cat > rules/dart/D001_flutter_widget_naming/config.json << 'EOF'
46
+ {
47
+ "id": "D001",
48
+ "name": "Flutter Widget Naming",
49
+ "description": "Widget class names should end with Widget, Page, Screen, or View",
50
+ "category": "dart",
51
+ "severity": "warning",
52
+ "languages": ["dart"]
53
+ }
54
+ EOF
55
+
56
+ # 3. Create Dart analyzer implementation
57
+ # File: dart_analyzer/lib/rules/dart/D001_flutter_widget_naming.dart
58
+
59
+ # 4. Register in analyzer_service.dart
60
+
61
+ # 5. Update rule definition in markdown
62
+ # Edit: ../../../rules/dart-en.md (English)
63
+ # Edit: ../../../rules/dart.md (Vietnamese)
64
+ # Create: ../../../rules/examples/en/D001.md
65
+ # Create: ../../../rules/examples/vi/D001.md
66
+
67
+ # 6. Generate registry from markdown
68
+ node scripts/copy-rules.js
69
+ node scripts/generate-rules-registry.js
70
+
71
+ # 7. Create test fixtures
72
+ # examples/rule-test-fixtures/dart-rules/D001_flutter_widget_naming/clean/
73
+ # examples/rule-test-fixtures/dart-rules/D001_flutter_widget_naming/violations/
74
+
75
+ # 8. Rebuild Dart binary
76
+ cd dart_analyzer && dart compile exe bin/sunlint_dart_analyzer.dart -o bin/sunlint-dart-macos
77
+ cp bin/sunlint-dart-macos ../sunlint-dart-macos
78
+
79
+ # 9. Test rule
80
+ node cli.js --rule=D001 --input=/path/to/dart/project --languages=dart
81
+ ```
82
+
83
+ ### Method 3: Run the Created Rule
84
+
85
+ ```bash
86
+ # Run D001 on a Dart project
87
+ node cli.js --rule=D001 --input=/path/to/dart/project --languages=dart
88
+
89
+ # With verbose output
90
+ node cli.js --rule=D001 --input=/path/to/dart/project --languages=dart --verbose
91
+
92
+ # Run on test fixtures
93
+ node cli.js --rule=D001 --input=examples/rule-test-fixtures/dart-rules/D001_recommended_lint_rules/violations --languages=dart
94
+
95
+ # If installed globally
96
+ sunlint --rule=D001 --input=/path/to/dart/project --languages=dart
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Metadata
102
+
103
+ | Field | Value |
104
+ |-------|-------|
105
+ | **Skill ID** | `create-new-dart-rule` |
106
+ | **Version** | 1.0.0 |
107
+ | **Author** | SunLint Team |
108
+ | **Category** | Code Generation |
109
+ | **Trigger** | User requests to create a new Dart-only rule |
110
+ | **Last Updated** | 2025-01-21 |
111
+
112
+ ---
113
+
114
+ ## 1. Prerequisites
115
+
116
+ ### 1.1. Tools Required
117
+
118
+ | Tool | Version | Purpose |
119
+ |------|---------|---------|
120
+ | Node.js | >= 18.x | Run SunLint CLI |
121
+ | Dart SDK | >= 3.0.0 | Compile Dart analyzer |
122
+ | Git | Any | Version control |
123
+
124
+ ### 1.2. Dependencies
125
+
126
+ ```bash
127
+ # SunLint directory
128
+ cd /path/to/sunlint
129
+
130
+ # Dart analyzer dependencies
131
+ cd dart_analyzer
132
+ dart pub get
133
+ ```
134
+
135
+ ### 1.3. File Locations
136
+
137
+ | Component | Path | Action |
138
+ |-----------|------|--------|
139
+ | **Dart-only Rules Config** | `rules/dart/${RULE_ID}_*/` | CREATE folder & config.json |
140
+ | **Dart Analyzer Implementation** | `dart_analyzer/lib/rules/dart/${RULE_ID}_*.dart` | CREATE analyzer file |
141
+ | **Analyzer Service Registration** | `dart_analyzer/lib/analyzer_service.dart` | MODIFY - add import & register |
142
+ | **Rule Definition (English)** | `../../../rules/dart-en.md` | MODIFY - add rule definition |
143
+ | **Rule Definition (Vietnamese)** | `../../../rules/dart.md` | MODIFY - add rule definition |
144
+ | **Example Files (English)** | `../../../rules/examples/en/${RULE_ID}.md` | CREATE good/bad examples |
145
+ | **Example Files (Vietnamese)** | `../../../rules/examples/vi/${RULE_ID}.md` | CREATE ví dụ đúng/sai |
146
+ | **Test Fixtures** | `examples/rule-test-fixtures/dart-rules/${RULE_ID}_*/` | CREATE clean/ & violations/ |
147
+ | **Generated Registry** | `config/rules/rules-registry-generated.json` | AUTO - generated from markdown |
148
+ | **Rules Summary** | `config/rules-summary.json` | MANUAL - update if needed |
149
+
150
+ ---
151
+
152
+ ## 2. Directory Structure
153
+
154
+ ### 2.1. Comparison with Multi-language Rules
155
+
156
+ | Aspect | Multi-language (Cxxx, Sxxx) | Dart-only (Dxxx) |
157
+ |--------|----------------------------|------------------|
158
+ | Rule folder | `rules/common/` or `rules/security/` | `rules/dart/` |
159
+ | Need router (index.js)? | ✅ Yes | ❌ No |
160
+ | Need dart/analyzer.js? | ✅ Yes | ❌ No |
161
+ | Need typescript/? | ✅ Yes | ❌ No |
162
+ | Files per rule | 4+ files | **2 files** |
163
+ | Languages | ["typescript", "dart", ...] | ["dart"] only |
164
+
165
+ ### 2.2. Flat Structure for Dxxx
166
+
167
+ ```
168
+ rules/dart/ # Dart-only rules directory
169
+ ├── D001_flutter_widget_naming/
170
+ │ └── config.json # Only need 1 config file
171
+ ├── D002_stream_subscription_cancel/
172
+ │ └── config.json
173
+ └── D003_dispose_controller/
174
+ └── config.json
175
+
176
+ dart_analyzer/lib/rules/dart/ # Dart implementation
177
+ ├── D001_flutter_widget_naming.dart
178
+ ├── D002_stream_subscription_cancel.dart
179
+ └── D003_dispose_controller.dart
180
+
181
+ examples/rule-test-fixtures/dart-rules/ # Test fixtures
182
+ ├── D001_flutter_widget_naming/
183
+ │ ├── clean/
184
+ │ │ └── good_widget_names.dart
185
+ │ └── violations/
186
+ │ └── bad_widget_names.dart
187
+ ```
188
+
189
+ ---
190
+
191
+ ## 3. Input Parameters
192
+
193
+ | Parameter | Required | Description | Example |
194
+ |-----------|----------|-------------|---------|
195
+ | `rule_id` | Yes | Rule ID (Dxxx format) | `D001`, `D002` |
196
+ | `rule_name` | Yes | Rule name (snake_case) | `flutter_widget_naming` |
197
+ | `description` | Yes | Short description | `Widget names should end with...` |
198
+ | `severity` | No | Severity level (default: warning) | `error`, `warning`, `info` |
199
+
200
+ ---
201
+
202
+ ## 4. Execution Steps
203
+
204
+ ### Step 1: Create Rule Config Folder
205
+
206
+ ```bash
207
+ RULE_ID="D001"
208
+ RULE_NAME="flutter_widget_naming"
209
+
210
+ # Create directory
211
+ mkdir -p "rules/dart/${RULE_ID}_${RULE_NAME}"
212
+ ```
213
+
214
+ ### Step 2: Create config.json
215
+
216
+ **File:** `rules/dart/${RULE_ID}_${RULE_NAME}/config.json`
217
+
218
+ ```json
219
+ {
220
+ "id": "${RULE_ID}",
221
+ "name": "${RULE_DISPLAY_NAME}",
222
+ "description": "${RULE_DESCRIPTION}",
223
+ "category": "dart",
224
+ "severity": "warning",
225
+ "languages": ["dart"],
226
+ "tags": ["flutter", "naming", "widget"],
227
+ "config": {
228
+ // Rule-specific configuration
229
+ }
230
+ }
231
+ ```
232
+
233
+ **Specific Example:**
234
+
235
+ ```json
236
+ {
237
+ "id": "D001",
238
+ "name": "Flutter Widget Naming",
239
+ "description": "Widget class names should end with Widget, Page, Screen, or View",
240
+ "category": "dart",
241
+ "severity": "warning",
242
+ "languages": ["dart"],
243
+ "tags": ["flutter", "naming", "widget"],
244
+ "config": {
245
+ "validSuffixes": ["Widget", "Page", "Screen", "View", "Dialog", "Sheet"],
246
+ "excludePatterns": ["_State", "Mixin"]
247
+ }
248
+ }
249
+ ```
250
+
251
+ ### Step 3: Create Dart Analyzer Implementation
252
+
253
+ **File:** `dart_analyzer/lib/rules/dart/${RULE_ID}_${RULE_NAME}.dart`
254
+
255
+ ```dart
256
+ import 'package:analyzer/dart/ast/ast.dart';
257
+ import 'package:analyzer/dart/ast/visitor.dart';
258
+ import 'package:analyzer/source/line_info.dart';
259
+
260
+ import '../../models/rule.dart';
261
+ import '../../models/violation.dart';
262
+ import '../base_analyzer.dart';
263
+
264
+ /// ${RULE_ID}: ${RULE_DISPLAY_NAME}
265
+ /// ${RULE_DESCRIPTION}
266
+ class ${RULE_ID}${RULE_PASCAL_NAME}Analyzer extends BaseAnalyzer {
267
+ @override
268
+ String get ruleId => '${RULE_ID}';
269
+
270
+ // Rule-specific configuration
271
+ static const _validSuffixes = ['Widget', 'Page', 'Screen', 'View', 'Dialog', 'Sheet'];
272
+ static const _excludePatterns = ['_State', 'Mixin'];
273
+
274
+ @override
275
+ List<Violation> analyze({
276
+ required CompilationUnit unit,
277
+ required String filePath,
278
+ required Rule rule,
279
+ required LineInfo lineInfo,
280
+ }) {
281
+ final violations = <Violation>[];
282
+
283
+ final visitor = _${RULE_ID}Visitor(
284
+ filePath: filePath,
285
+ lineInfo: lineInfo,
286
+ violations: violations,
287
+ analyzer: this,
288
+ );
289
+
290
+ unit.accept(visitor);
291
+
292
+ return violations;
293
+ }
294
+ }
295
+
296
+ class _${RULE_ID}Visitor extends RecursiveAstVisitor<void> {
297
+ final String filePath;
298
+ final LineInfo lineInfo;
299
+ final List<Violation> violations;
300
+ final ${RULE_ID}${RULE_PASCAL_NAME}Analyzer analyzer;
301
+
302
+ _${RULE_ID}Visitor({
303
+ required this.filePath,
304
+ required this.lineInfo,
305
+ required this.violations,
306
+ required this.analyzer,
307
+ });
308
+
309
+ @override
310
+ void visitClassDeclaration(ClassDeclaration node) {
311
+ final className = node.name.lexeme;
312
+
313
+ // Check if class extends StatelessWidget or StatefulWidget
314
+ final extendsClause = node.extendsClause;
315
+ if (extendsClause != null) {
316
+ final superclass = extendsClause.superclass.name2.lexeme;
317
+ if (superclass == 'StatelessWidget' || superclass == 'StatefulWidget') {
318
+ // Check if name ends with valid suffix
319
+ bool hasValidSuffix = ${RULE_ID}${RULE_PASCAL_NAME}Analyzer._validSuffixes
320
+ .any((suffix) => className.endsWith(suffix));
321
+
322
+ // Skip excluded patterns
323
+ bool isExcluded = ${RULE_ID}${RULE_PASCAL_NAME}Analyzer._excludePatterns
324
+ .any((pattern) => className.contains(pattern));
325
+
326
+ if (!hasValidSuffix && !isExcluded) {
327
+ violations.add(analyzer.createViolation(
328
+ filePath: filePath,
329
+ line: analyzer.getLine(lineInfo, node.name.offset),
330
+ column: analyzer.getColumn(lineInfo, node.name.offset),
331
+ message: 'Widget class "$className" should end with Widget, Page, Screen, View, Dialog, or Sheet',
332
+ ));
333
+ }
334
+ }
335
+ }
336
+
337
+ super.visitClassDeclaration(node);
338
+ }
339
+ }
340
+ ```
341
+
342
+ ### Step 4: Register in AnalyzerService
343
+
344
+ **File:** `dart_analyzer/lib/analyzer_service.dart`
345
+
346
+ ```dart
347
+ // Add import at top
348
+ import 'rules/dart/${RULE_ID}_${RULE_NAME}.dart';
349
+
350
+ // Add to _registerAnalyzers() method
351
+ void _registerAnalyzers() {
352
+ // ... existing analyzers
353
+
354
+ // Dart-only rules (Dxxx)
355
+ _analyzers['${RULE_ID}'] = ${RULE_ID}${RULE_PASCAL_NAME}Analyzer();
356
+ }
357
+ ```
358
+
359
+ ### Step 5: Update Rule Definition Markdown Files
360
+
361
+ **IMPORTANT:** Rule name and description are taken from these markdown files, not from `config.json`.
362
+
363
+ #### 5.1. Update `dart-en.md` (English version)
364
+
365
+ **File:** `../../../rules/dart-en.md`
366
+
367
+ Add new rule definition to file:
368
+
369
+ ```markdown
370
+ ### 📘 Rule ${RULE_ID} – ${RULE_DISPLAY_NAME}
371
+
372
+ - **Objective**: ${OBJECTIVE_SHORT}
373
+ - **Details**: ${DETAILED_DESCRIPTION}
374
+ - **Applies to**: Flutter/Dart
375
+ - **Tools**: `dart lint` (${TOOLS})
376
+ - **Principles**: CODE_QUALITY
377
+ - **Version**: 1.0
378
+ - **Status**: activated
379
+ - **Severity**: major
380
+ ```
381
+
382
+ **Example:**
383
+
384
+ ```markdown
385
+ ### 📘 Rule D001 – Recommended Lint Rules Should Be Enabled
386
+
387
+ - **Objective**: Ensure code quality through standard lint configurations
388
+ - **Details**: The `analysis_options.yaml` file should include recommended lint packages (flutter_lints, very_good_analysis, or lints) and critical lint rules should not be disabled. This ensures consistent code quality standards across the project.
389
+ - **Applies to**: Flutter/Dart
390
+ - **Tools**: `dart lint` (flutter_lints, very_good_analysis, lints)
391
+ - **Principles**: CODE_QUALITY
392
+ - **Version**: 1.0
393
+ - **Status**: activated
394
+ - **Severity**: major
395
+ ```
396
+
397
+ #### 5.2. Update `dart.md` (Vietnamese version)
398
+
399
+ **File:** `../../../rules/dart.md`
400
+
401
+ Add rule definition in Vietnamese:
402
+
403
+ ```markdown
404
+ ### 📘 Rule ${RULE_ID} – ${RULE_DISPLAY_NAME_VI}
405
+
406
+ - **Mục tiêu**: ${OBJECTIVE_SHORT_VI}
407
+ - **Chi tiết**: ${DETAILED_DESCRIPTION_VI}
408
+ - **Áp dụng**: Flutter/Dart
409
+ - **Công cụ**: `dart lint` (${TOOLS})
410
+ - **Nguyên tắc**: CODE_QUALITY
411
+ - **Phiên bản**: 1.0
412
+ - **Trạng thái**: activated
413
+ - **Mức độ**: major
414
+ ```
415
+
416
+ #### 5.3. Create Example Files
417
+
418
+ **File:** `../../../rules/examples/en/${RULE_ID}.md`
419
+
420
+ ```markdown
421
+ **Good Examples**:
422
+ \`\`\`dart
423
+ // Your good example code here
424
+ \`\`\`
425
+
426
+ **Bad Examples**:
427
+ \`\`\`dart
428
+ // Your bad example code here
429
+ \`\`\`
430
+ ```
431
+
432
+ **File:** `../../../rules/examples/vi/${RULE_ID}.md`
433
+
434
+ ```markdown
435
+ **Ví dụ đúng**:
436
+ \`\`\`dart
437
+ // Good code example
438
+ \`\`\`
439
+
440
+ **Ví dụ sai**:
441
+ \`\`\`dart
442
+ // Bad code example
443
+ \`\`\`
444
+ ```
445
+
446
+ ### Step 6: Generate Registry from Markdown
447
+
448
+ **IMPORTANT:** Registry is auto-generated from markdown files, DO NOT edit manually.
449
+
450
+ ```bash
451
+ # Copy rules from main rules directory to sunlint
452
+ node scripts/copy-rules.js
453
+
454
+ # Generate registry from markdown files
455
+ node scripts/generate-rules-registry.js
456
+
457
+ # Results:
458
+ # - config/rules/rules-registry-generated.json (auto-generated)
459
+ # - Rule name and description will be taken from dart-en.md
460
+ ```
461
+
462
+ **Notes:**
463
+ - File `rules-registry-generated.json` is auto-generated, DO NOT edit manually
464
+ - File `rules-summary.json` may need manual update if not generated
465
+ - Registry will parse rule from markdown format `### 📘 Rule ${RULE_ID} – ${NAME}`
466
+
467
+ **📖 Read more:** See [REGISTRY_GENERATION_FLOW.md](../REGISTRY_GENERATION_FLOW.md) for detailed understanding of the generation flow, common mistakes, and debugging tips.
468
+
469
+ ### Step 7: Create Test Fixtures
470
+
471
+ ```bash
472
+ # Create test fixture folders
473
+ mkdir -p "examples/rule-test-fixtures/dart-rules/${RULE_ID}_${RULE_NAME}/{clean,violations}"
474
+ ```
475
+
476
+ **violations/bad_example.dart:**
477
+ ```dart
478
+ // ❌ VIOLATION: D001 - Flutter Widget Naming
479
+ // Widget class should end with Widget, Page, Screen, or View
480
+
481
+ class MyComponent extends StatelessWidget { // ❌ Should be MyComponentWidget
482
+ @override
483
+ Widget build(BuildContext context) {
484
+ return Container();
485
+ }
486
+ }
487
+
488
+ class UserProfile extends StatefulWidget { // ❌ Should be UserProfilePage/Screen
489
+ @override
490
+ State<UserProfile> createState() => _UserProfileState();
491
+ }
492
+ ```
493
+
494
+ **clean/good_example.dart:**
495
+ ```dart
496
+ // ✅ CLEAN: D001 - Flutter Widget Naming
497
+ // Correct widget naming convention
498
+
499
+ class MyComponentWidget extends StatelessWidget { // ✅ Ends with Widget
500
+ @override
501
+ Widget build(BuildContext context) {
502
+ return Container();
503
+ }
504
+ }
505
+
506
+ class UserProfilePage extends StatefulWidget { // ✅ Ends with Page
507
+ @override
508
+ State<UserProfilePage> createState() => _UserProfilePageState();
509
+ }
510
+
511
+ class SettingsScreen extends StatelessWidget { // ✅ Ends with Screen
512
+ @override
513
+ Widget build(BuildContext context) {
514
+ return Scaffold();
515
+ }
516
+ }
517
+ ```
518
+
519
+ ### Step 8: Rebuild Dart Analyzer
520
+
521
+ ```bash
522
+ cd dart_analyzer
523
+
524
+ # Get dependencies
525
+ dart pub get
526
+
527
+ # Analyze for errors
528
+ dart analyze lib/
529
+
530
+ # Compile binary (macOS)
531
+ dart compile exe bin/sunlint_dart_analyzer.dart -o bin/sunlint-dart-macos
532
+
533
+ # Copy binary to sunlint root (important!)
534
+ cp bin/sunlint-dart-macos ../sunlint-dart-macos
535
+
536
+ # Verify binary
537
+ ls -la ../sunlint-dart-macos
538
+
539
+ # Return to sunlint root
540
+ cd ..
541
+ ```
542
+
543
+ **Note:** Binary must be copied to sunlint root directory for CLI to use.
544
+
545
+ **Platform-specific binaries:**
546
+ - macOS: `sunlint-dart-macos`
547
+ - Linux: `sunlint-dart-linux`
548
+ - Windows: `sunlint-dart-windows.exe`
549
+
550
+ ### Step 9: Test the Rule
551
+
552
+ ```bash
553
+ cd /path/to/sunlint
554
+
555
+ # Test on violations (should find issues)
556
+ node cli.js --rule=${RULE_ID} \
557
+ --input="examples/rule-test-fixtures/dart-rules/${RULE_ID}_${RULE_NAME}/violations" \
558
+ --languages=dart
559
+
560
+ # Test on clean code (should find no issues)
561
+ node cli.js --rule=${RULE_ID} \
562
+ --input="examples/rule-test-fixtures/dart-rules/${RULE_ID}_${RULE_NAME}/clean" \
563
+ --languages=dart
564
+
565
+ # With verbose output for debugging
566
+ node cli.js --rule=${RULE_ID} \
567
+ --input="examples/rule-test-fixtures/dart-rules/${RULE_ID}_${RULE_NAME}/violations" \
568
+ --languages=dart \
569
+ --verbose
570
+ ```
571
+
572
+ ### Step 10: Run Rule on Real Project
573
+
574
+ ```bash
575
+ # Run on a Dart/Flutter project
576
+ node cli.js --rule=${RULE_ID} --input=/path/to/dart/project --languages=dart
577
+
578
+ # Run with verbose output
579
+ node cli.js --rule=${RULE_ID} --input=/path/to/dart/project --languages=dart --verbose
580
+
581
+ # If installed globally
582
+ sunlint --rule=${RULE_ID} --input=/path/to/dart/project --languages=dart
583
+ ```
584
+
585
+ ---
586
+
587
+ ## 5. Verification Checklist
588
+
589
+ | Check | Command | Expected |
590
+ |-------|---------|----------|
591
+ | Config exists | `ls rules/dart/${RULE_ID}_*/config.json` | File exists |
592
+ | Dart analyzer exists | `ls dart_analyzer/lib/rules/dart/${RULE_ID}_*.dart` | File exists |
593
+ | Registered | `grep ${RULE_ID} dart_analyzer/lib/analyzer_service.dart` | Registration line |
594
+ | Binary compiles | `dart analyze dart_analyzer/lib/` | No errors |
595
+ | Test fixtures exist | `ls examples/rule-test-fixtures/dart-rules/${RULE_ID}_*` | clean/ and violations/ |
596
+ | **Violations detected** | `node cli.js --rule=${RULE_ID} --input=...violations --languages=dart` | > 0 violations |
597
+ | **Clean passes** | `node cli.js --rule=${RULE_ID} --input=...clean --languages=dart` | No violations |
598
+
599
+ ---
600
+
601
+ ## 6. Naming Conventions
602
+
603
+ | Type | Convention | Example |
604
+ |------|------------|---------|
605
+ | Rule ID | Dxxx | `D001`, `D002` |
606
+ | Folder name | `{ID}_{snake_case}` | `D001_flutter_widget_naming` |
607
+ | Dart file | `{ID}_{snake_case}.dart` | `D001_flutter_widget_naming.dart` |
608
+ | Class name | `{ID}{PascalCase}Analyzer` | `D001FlutterWidgetNamingAnalyzer` |
609
+ | Visitor class | `_{ID}Visitor` | `_D001Visitor` |
610
+
611
+ ---
612
+
613
+ ## 7. Common Patterns for Flutter/Dart Rules
614
+
615
+ ### Pattern 1: Check Widget Class Names
616
+
617
+ ```dart
618
+ @override
619
+ void visitClassDeclaration(ClassDeclaration node) {
620
+ final className = node.name.lexeme;
621
+ final extendsClause = node.extendsClause;
622
+
623
+ if (extendsClause != null) {
624
+ final superclass = extendsClause.superclass.name2.lexeme;
625
+ if (superclass.contains('Widget')) {
626
+ // Check widget naming
627
+ }
628
+ }
629
+ super.visitClassDeclaration(node);
630
+ }
631
+ ```
632
+
633
+ ### Pattern 2: Check StreamSubscription Cancel
634
+
635
+ ```dart
636
+ @override
637
+ void visitMethodDeclaration(MethodDeclaration node) {
638
+ if (node.name.lexeme == 'dispose') {
639
+ final body = node.body?.toSource() ?? '';
640
+ // Check if subscriptions are cancelled
641
+ if (!body.contains('.cancel()')) {
642
+ // Add violation
643
+ }
644
+ }
645
+ super.visitMethodDeclaration(node);
646
+ }
647
+ ```
648
+
649
+ ### Pattern 3: Check Controller Dispose
650
+
651
+ ```dart
652
+ @override
653
+ void visitFieldDeclaration(FieldDeclaration node) {
654
+ for (final variable in node.fields.variables) {
655
+ final type = node.fields.type?.toSource() ?? '';
656
+ if (type.contains('Controller')) {
657
+ // Track controller fields for dispose check
658
+ }
659
+ }
660
+ super.visitFieldDeclaration(node);
661
+ }
662
+ ```
663
+
664
+ ### Pattern 4: Check BuildContext Usage
665
+
666
+ ```dart
667
+ @override
668
+ void visitMethodInvocation(MethodInvocation node) {
669
+ final methodName = node.methodName.name;
670
+ if (methodName == 'of') {
671
+ // Check for context usage after async gap
672
+ }
673
+ super.visitMethodInvocation(node);
674
+ }
675
+ ```
676
+
677
+ ### Pattern 5: Check Import Statements
678
+
679
+ ```dart
680
+ @override
681
+ void visitImportDirective(ImportDirective node) {
682
+ final uri = node.uri.stringValue ?? '';
683
+ if (uri.contains('dart:mirrors')) {
684
+ // Flag forbidden imports
685
+ }
686
+ super.visitImportDirective(node);
687
+ }
688
+ ```
689
+
690
+ ---
691
+
692
+ ## 8. Existing and Suggested Dart-Only Rules (Dxxx)
693
+
694
+ ### Existing Rules
695
+
696
+ | ID | Name | Description | Status |
697
+ |----|------|-------------|--------|
698
+ | D001 | Recommended Lint Rules | Ensure flutter_lints/very_good_analysis enabled in analysis_options.yaml | ✅ Implemented |
699
+
700
+ ### Suggested Future Rules
701
+
702
+ | ID | Name | Description |
703
+ |----|------|-------------|
704
+ | D002 | Stream Subscription Cancel | StreamSubscription must be cancelled in dispose() |
705
+ | D003 | Dispose Controllers | TextEditingController, AnimationController must be disposed |
706
+ | D004 | BuildContext After Async | Don't use BuildContext after async gaps |
707
+ | D005 | Avoid Print in Flutter | Use debugPrint() instead of print() |
708
+ | D006 | Prefer Const Constructors | Use const constructors when possible |
709
+ | D007 | Avoid setState in Build | Don't call setState() inside build() method |
710
+ | D008 | Import Order | Dart imports should be sorted and grouped |
711
+ | D009 | No Relative Imports | Use package imports instead of relative imports |
712
+ | D010 | Prefer Final Fields | Class fields should be final when possible |
713
+ | D011 | Flutter Widget Naming | Widget classes should end with Widget/Page/Screen/View |
714
+
715
+ ---
716
+
717
+ ## 9. Error Handling
718
+
719
+ ### 9.1. Dart Compile Error
720
+
721
+ ```bash
722
+ # Check for errors
723
+ dart analyze dart_analyzer/lib/
724
+
725
+ # Common fixes:
726
+ # - Import missing files
727
+ # - Fix syntax errors
728
+ # - Add missing dependencies to pubspec.yaml
729
+ ```
730
+
731
+ ### 9.2. Rule Not Detected
732
+
733
+ ```bash
734
+ # Verify config exists
735
+ ls rules/dart/${RULE_ID}_*/config.json
736
+
737
+ # Check registry
738
+ grep ${RULE_ID} config/rules/enhanced-rules-registry.json
739
+ ```
740
+
741
+ ### 9.3. No Violations Found
742
+
743
+ ```bash
744
+ # Check DartAnalyzer is running
745
+ node cli.js --rule=${RULE_ID} --input=... --languages=dart --verbose
746
+
747
+ # Check analyzer is registered
748
+ grep ${RULE_ID} dart_analyzer/lib/analyzer_service.dart
749
+ ```
750
+
751
+ ### 9.4. Rule Name Mismatch
752
+
753
+ **Problem:** Rule name displays incorrectly or doesn't match the definition.
754
+
755
+ **Root Cause:** Rule name is taken from markdown files (`dart-en.md`), NOT from `config.json` or Dart code.
756
+
757
+ **Solution:**
758
+ 1. Check rule definition in `../../../rules/dart-en.md`
759
+ 2. Ensure correct format: `### 📘 Rule ${RULE_ID} – ${NAME}`
760
+ 3. Regenerate registry:
761
+ ```bash
762
+ node scripts/copy-rules.js
763
+ node scripts/generate-rules-registry.js
764
+ ```
765
+ 4. Verify in generated file:
766
+ ```bash
767
+ grep -A 5 '"${RULE_ID}"' config/rules/rules-registry-generated.json
768
+ ```
769
+
770
+ **IMPORTANT:**
771
+ - Markdown files are the **source of truth** for rule name and description
772
+ - Registry files are **auto-generated** from markdown
773
+ - DO NOT edit registry files manually, must update markdown and regenerate
774
+
775
+ ---
776
+
777
+ ## 10. Files Created/Modified by This Skill
778
+
779
+ ### Files Created:
780
+
781
+ | File | Purpose |
782
+ |------|---------|
783
+ | `rules/dart/${RULE_ID}_*/config.json` | Rule configuration |
784
+ | `dart_analyzer/lib/rules/dart/${RULE_ID}_*.dart` | Dart analyzer implementation |
785
+ | `examples/rule-test-fixtures/dart-rules/${RULE_ID}_*/clean/*.dart` | Clean test cases |
786
+ | `examples/rule-test-fixtures/dart-rules/${RULE_ID}_*/violations/*.dart` | Violation test cases |
787
+ | `../../../rules/examples/en/${RULE_ID}.md` | English examples |
788
+ | `../../../rules/examples/vi/${RULE_ID}.md` | Vietnamese examples |
789
+
790
+ ### Files Modified:
791
+
792
+ | File | What to Add |
793
+ |------|-------------|
794
+ | `dart_analyzer/lib/analyzer_service.dart` | Import rule + Register in `_registerAnalyzers()` |
795
+ | `../../../rules/dart-en.md` | Rule definition section (English) |
796
+ | `../../../rules/dart.md` | Rule definition section (Vietnamese) |
797
+
798
+ ### Files Auto-Generated:
799
+
800
+ | File | Generated By |
801
+ |------|--------------|
802
+ | `config/rules/rules-registry-generated.json` | `scripts/generate-rules-registry.js` |
803
+ | `origin-rules/dart-en.md` | `scripts/copy-rules.js` (copied from `../../../rules/`) |
804
+
805
+ ---
806
+
807
+ ## 11. Best Practices & Important Notes
808
+
809
+ ### 11.1. Rule Name Synchronization
810
+
811
+ **CRITICAL:** Rule name must be consistent across 3 locations:
812
+
813
+ | Location | Format | Example |
814
+ |----------|--------|---------|
815
+ | **Dart Code Comment** | `/// ${RULE_ID}: ${NAME}` | `/// D001: Recommended Lint Rules Should Be Enabled` |
816
+ | **Markdown (dart-en.md)** | `### 📘 Rule ${RULE_ID} – ${NAME}` | `### 📘 Rule D001 – Recommended Lint Rules Should Be Enabled` |
817
+ | **Registry (auto-generated)** | Generated from markdown | `"name": "Recommended Lint Rules Should Be Enabled"` |
818
+
819
+ **Workflow:**
820
+ 1. Update markdown files first (`dart-en.md`, `dart.md`)
821
+ 2. Run `node scripts/copy-rules.js && node scripts/generate-rules-registry.js`
822
+ 3. Update Dart code comment to match
823
+ 4. Never edit registry JSON files manually
824
+
825
+ ### 11.2. Registry Generation Flow
826
+
827
+ ```
828
+ ../../../rules/dart-en.md
829
+ ↓ (copy-rules.js)
830
+ origin-rules/dart-en.md
831
+ ↓ (generate-rules-registry.js)
832
+ config/rules/rules-registry-generated.json
833
+ ```
834
+
835
+ **Key Points:**
836
+ - Markdown is the **source of truth**
837
+ - Registry is **auto-generated**
838
+ - `config.json` is only used for rule detection, not for display name
839
+
840
+ ### 11.3. Common Mistakes to Avoid
841
+
842
+ | Mistake | Why It's Wrong | Correct Approach |
843
+ |---------|----------------|------------------|
844
+ | Edit `rules-registry-generated.json` manually | Will be overwritten | Edit markdown and regenerate |
845
+ | Put display name in `config.json` | Not used for display | Put in markdown files |
846
+ | Skip registry regeneration | Old name will show | Always run `generate-rules-registry.js` |
847
+ | Mismatch Dart comment and markdown | Confusing for developers | Keep them synchronized |
848
+
849
+ ---
850
+
851
+ ## 12. Related Documentation
852
+
853
+ - **[REGISTRY_GENERATION_FLOW.md](../REGISTRY_GENERATION_FLOW.md)** - ⭐ Detailed registry generation flow from markdown
854
+ - [CREATE_DART_RULE.md](./CREATE_DART_RULE.md) - Add Dart support for existing TypeScript rule
855
+ - [DART_RULE_EXECUTION_FLOW.md](../DART_RULE_EXECUTION_FLOW.md) - Detailed rule execution flow
856
+ - [dart_analyzer/README.md](../../dart_analyzer/README.md) - Dart analyzer documentation
857
+
858
+ ---
859
+
860
+ ## 13. Complete Example: D001 Implementation
861
+
862
+ Below is a real-world example of rule D001 implementation:
863
+
864
+ ### Files Created:
865
+
866
+ ```
867
+ rules/dart/D001_recommended_lint_rules/
868
+ └── config.json
869
+
870
+ dart_analyzer/lib/rules/dart/
871
+ └── D001_recommended_lint_rules.dart
872
+
873
+ examples/rule-test-fixtures/dart-rules/D001_recommended_lint_rules/
874
+ ├── clean/
875
+ │ ├── analysis_options.yaml
876
+ │ ├── pubspec.yaml
877
+ │ └── lib/main.dart
878
+ └── violations/
879
+ ├── analysis_options.yaml
880
+ ├── pubspec.yaml
881
+ └── lib/main.dart
882
+ ```
883
+
884
+ ### Test Commands:
885
+
886
+ ```bash
887
+ # Test violations (expect 5 warnings)
888
+ node cli.js --rule=D001 \
889
+ --input=examples/rule-test-fixtures/dart-rules/D001_recommended_lint_rules/violations \
890
+ --languages=dart
891
+
892
+ # Test clean (expect 0 warnings)
893
+ node cli.js --rule=D001 \
894
+ --input=examples/rule-test-fixtures/dart-rules/D001_recommended_lint_rules/clean \
895
+ --languages=dart
896
+
897
+ # Run on any Dart project
898
+ node cli.js --rule=D001 --input=/path/to/dart/project --languages=dart
899
+ ```
900
+
901
+ ### Expected Output (violations):
902
+
903
+ ```
904
+ analysis_options.yaml
905
+ 1:1 warning analysis_options.yaml should include a recommended lint package... D001
906
+ 13:1 warning Critical lint rule "avoid_print" should not be disabled... D001
907
+ 14:1 warning Critical lint rule "cancel_subscriptions" should not be disabled... D001
908
+ 15:1 warning Critical lint rule "close_sinks" should not be disabled... D001
909
+ 16:1 warning Critical lint rule "avoid_empty_else" should not be disabled... D001
910
+
911
+ ✖ 5 problems (0 errors, 5 warnings)
912
+ ```
913
+
914
+ ---
915
+
916
+ ## 14. Changelog
917
+
918
+ ### v1.1.0 (2025-01-23)
919
+ - **BREAKING:** Added mandatory markdown update steps for rule definitions
920
+ - Added registry generation flow documentation
921
+ - Added Best Practices section on rule name synchronization
922
+ - Added troubleshooting for rule name mismatch issues
923
+ - Clarified that markdown files are source of truth for rule names
924
+ - Updated file locations table with markdown paths
925
+ - Added example files creation steps
926
+
927
+ ### v1.0.0 (2025-01-21)
928
+ - Initial skill documentation for creating Dart-only rules (Dxxx)
929
+ - Flat structure design - no router or JS wrapper needed
930
+ - Separate `rules/dart/` folder for Dart-specific rules
931
+ - Common Flutter/Dart patterns included
932
+ - D001 (Recommended Lint Rules) implemented as reference example