@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.
- package/cli.js +6 -4
- package/config/rules/enhanced-rules-registry.json +33 -0
- package/config/rules/rules-registry-generated.json +5 -3
- package/config/rules-summary.json +1 -1
- package/core/cli-action-handler.js +2 -1
- package/core/cli-program.js +2 -2
- package/core/init-command.js +113 -5
- package/core/output-service.js +11 -5
- package/core/performance-optimizer.js +1 -1
- package/core/unified-rule-registry.js +2 -2
- package/docs/DART_RULE_EXECUTION_FLOW.md +1 -1
- package/docs/REGISTRY_GENERATION_DIAGRAM.md +289 -0
- package/docs/REGISTRY_GENERATION_FLOW.md +486 -0
- package/docs/skills/CREATE_NEW_DART_RULE.md +932 -0
- package/engines/heuristic-engine.js +23 -10
- package/origin-rules/dart-en.md +4 -4
- package/package.json +1 -1
- package/rules/dart/D001_recommended_lint_rules/config.json +134 -0
- package/rules/index.js +6 -4
|
@@ -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
|