@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
package/cli.js
CHANGED
|
@@ -24,16 +24,18 @@ program
|
|
|
24
24
|
.command('init [directory]')
|
|
25
25
|
.description('Initialize a project with SunLint code quality skill and AGENTS.md')
|
|
26
26
|
.option('-f, --force', 'Overwrite existing files')
|
|
27
|
-
.option('-t, --tool <tool>', `Target AI tool (default: ${DEFAULT_TOOL})
|
|
27
|
+
.option('-t, --tool <tool>', `Target AI tool (default: ${DEFAULT_TOOL})`)
|
|
28
|
+
.option('-l, --language <language>', `Target language (default: typescript)`)
|
|
29
|
+
.option('--non-interactive', 'Disable interactive prompts')
|
|
28
30
|
.addHelpText('after', `
|
|
29
31
|
AI Tool Options:
|
|
30
32
|
${getAvailableToolsHelp()}
|
|
31
33
|
|
|
32
34
|
Examples:
|
|
33
|
-
$ sunlint init # Use default (antigravity)
|
|
35
|
+
$ sunlint init # Use default (antigravity, typescript)
|
|
34
36
|
$ sunlint init -t cursor # For Cursor AI
|
|
35
|
-
$ sunlint init -
|
|
36
|
-
$ sunlint init -t github-copilot # For GitHub Copilot
|
|
37
|
+
$ sunlint init -l csharp # For C# project
|
|
38
|
+
$ sunlint init -t github-copilot -l csharp # For GitHub Copilot with C# rules
|
|
37
39
|
$ sunlint init ./my-project -t cursor --force
|
|
38
40
|
`)
|
|
39
41
|
.action(async (directory, options) => {
|
|
@@ -3640,6 +3640,39 @@
|
|
|
3640
3640
|
"headers",
|
|
3641
3641
|
"privacy"
|
|
3642
3642
|
]
|
|
3643
|
+
},
|
|
3644
|
+
"D001": {
|
|
3645
|
+
"id": "D001",
|
|
3646
|
+
"name": "Recommended Lint Rules Should Be Enabled",
|
|
3647
|
+
"description": "Ensure recommended lint rules from flutter_lints or very_good_analysis are enabled in analysis_options.yaml with appropriate severity",
|
|
3648
|
+
"category": "dart",
|
|
3649
|
+
"severity": "warning",
|
|
3650
|
+
"languages": [
|
|
3651
|
+
"dart"
|
|
3652
|
+
],
|
|
3653
|
+
"analyzer": "dart",
|
|
3654
|
+
"config": "./rules/dart/D001_recommended_lint_rules/config.json",
|
|
3655
|
+
"version": "1.0.0",
|
|
3656
|
+
"status": "stable",
|
|
3657
|
+
"tags": [
|
|
3658
|
+
"dart",
|
|
3659
|
+
"flutter",
|
|
3660
|
+
"lint",
|
|
3661
|
+
"best-practices",
|
|
3662
|
+
"code-quality"
|
|
3663
|
+
],
|
|
3664
|
+
"strategy": {
|
|
3665
|
+
"preferred": "dart",
|
|
3666
|
+
"fallbacks": [],
|
|
3667
|
+
"accuracy": {
|
|
3668
|
+
"dart": 95
|
|
3669
|
+
}
|
|
3670
|
+
},
|
|
3671
|
+
"engineMappings": {
|
|
3672
|
+
"dart": [
|
|
3673
|
+
"D001_recommended_lint_rules"
|
|
3674
|
+
]
|
|
3675
|
+
}
|
|
3643
3676
|
}
|
|
3644
3677
|
}
|
|
3645
3678
|
}
|
|
@@ -1833,8 +1833,8 @@
|
|
|
1833
1833
|
]
|
|
1834
1834
|
},
|
|
1835
1835
|
"D001": {
|
|
1836
|
-
"name": "
|
|
1837
|
-
"description": "
|
|
1836
|
+
"name": "Recommended Lint Rules Should Be Enabled",
|
|
1837
|
+
"description": "Ensure code quality through standard lint configurations",
|
|
1838
1838
|
"category": "Common",
|
|
1839
1839
|
"severity": "major",
|
|
1840
1840
|
"languages": [
|
|
@@ -1848,7 +1848,9 @@
|
|
|
1848
1848
|
"code-quality"
|
|
1849
1849
|
],
|
|
1850
1850
|
"tools": [
|
|
1851
|
-
"`dart lint` (
|
|
1851
|
+
"`dart lint` (flutter_lints",
|
|
1852
|
+
"very_good_analysis",
|
|
1853
|
+
"lints)"
|
|
1852
1854
|
],
|
|
1853
1855
|
"framework": "All",
|
|
1854
1856
|
"principles": [
|
|
@@ -170,7 +170,8 @@ class CliActionHandler {
|
|
|
170
170
|
performanceMode: this.options.performanceMode,
|
|
171
171
|
ruleBatchSize: parseInt(this.options.ruleBatchSize) || 10,
|
|
172
172
|
fileBatchSize: parseInt(this.options.fileBatchSize) || 50,
|
|
173
|
-
maxFiles:
|
|
173
|
+
// maxFiles: 0 or undefined = unlimited, positive number = limit
|
|
174
|
+
maxFiles: parseInt(this.options.maxFiles) || 0,
|
|
174
175
|
enableFileFiltering: !this.options.noFileFiltering,
|
|
175
176
|
enableBatching: !this.options.noBatching
|
|
176
177
|
}
|
package/core/cli-program.js
CHANGED
|
@@ -85,8 +85,8 @@ function createCliProgram() {
|
|
|
85
85
|
program
|
|
86
86
|
.option('--performance <mode>', 'Performance mode: auto, fast, careful', 'auto')
|
|
87
87
|
.option('--timeout <ms>', 'Analysis timeout in milliseconds', '0')
|
|
88
|
-
.option('--max-files <n>', 'Maximum files to analyze', '0')
|
|
89
|
-
.option('--max-semantic-files <n>', 'TypeScript symbol table limit (default
|
|
88
|
+
.option('--max-files <n>', 'Maximum files to analyze (0=unlimited)', '0')
|
|
89
|
+
.option('--max-semantic-files <n>', 'TypeScript symbol table limit (0=unlimited, default=1000)');
|
|
90
90
|
|
|
91
91
|
// ──────────────────────────────────────────────────────────────
|
|
92
92
|
// Output Control
|
package/core/init-command.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const chalk = require('chalk');
|
|
10
|
+
const readline = require('readline');
|
|
10
11
|
|
|
11
12
|
// Path to the bundled skill directory (relative to this file in the package)
|
|
12
13
|
const SKILL_SOURCE_DIR = path.join(__dirname, '..', 'skill-assets', 'sunlint-code-quality');
|
|
@@ -51,6 +52,52 @@ function getAvailableToolsHelp() {
|
|
|
51
52
|
.join('\n');
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Get available languages from rules directory
|
|
57
|
+
*/
|
|
58
|
+
function getAvailableLanguages() {
|
|
59
|
+
const rulesDir = path.join(SKILL_SOURCE_DIR, 'rules');
|
|
60
|
+
if (!fs.existsSync(rulesDir)) return ['typescript', 'csharp', 'python'];
|
|
61
|
+
|
|
62
|
+
return fs.readdirSync(rulesDir)
|
|
63
|
+
.filter(item => {
|
|
64
|
+
const fullPath = path.join(rulesDir, item);
|
|
65
|
+
return fs.statSync(fullPath).isDirectory();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Interactive selection helper
|
|
71
|
+
*/
|
|
72
|
+
async function promptSelection(question, options) {
|
|
73
|
+
const rl = readline.createInterface({
|
|
74
|
+
input: process.stdin,
|
|
75
|
+
output: process.stdout
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
console.log(chalk.cyan(`\n? ${question}`));
|
|
80
|
+
options.forEach((opt, i) => {
|
|
81
|
+
console.log(` ${chalk.white(i + 1)}) ${opt.name || opt}`);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const ask = () => {
|
|
85
|
+
rl.question(chalk.yellow(`\nSelect [1-${options.length}]: `), (answer) => {
|
|
86
|
+
const choice = parseInt(answer.trim());
|
|
87
|
+
if (choice >= 1 && choice <= options.length) {
|
|
88
|
+
rl.close();
|
|
89
|
+
const selected = options[choice - 1];
|
|
90
|
+
resolve(selected.key || selected);
|
|
91
|
+
} else {
|
|
92
|
+
console.log(chalk.red(`Invalid selection. Please enter a number between 1 and ${options.length}.`));
|
|
93
|
+
ask();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
ask();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
54
101
|
/**
|
|
55
102
|
* Initialize a project with SunLint skill and AGENTS.md
|
|
56
103
|
* @param {string} targetDir - The target project directory
|
|
@@ -58,7 +105,33 @@ function getAvailableToolsHelp() {
|
|
|
58
105
|
*/
|
|
59
106
|
async function initProject(targetDir, options = {}) {
|
|
60
107
|
const resolvedTargetDir = path.resolve(targetDir);
|
|
61
|
-
|
|
108
|
+
|
|
109
|
+
let tool = options.tool;
|
|
110
|
+
let language = options.language;
|
|
111
|
+
|
|
112
|
+
// Interactive step-by-step setup if options are missing and in TTY
|
|
113
|
+
if (process.stdin.isTTY && process.stdout.isTTY && !options.nonInteractive) {
|
|
114
|
+
if (!tool) {
|
|
115
|
+
const toolOptions = Object.entries(AI_TOOL_CONFIG).map(([key, config]) => ({
|
|
116
|
+
key,
|
|
117
|
+
name: config.name
|
|
118
|
+
}));
|
|
119
|
+
tool = await promptSelection('Select Target AI Tool', toolOptions);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!language) {
|
|
123
|
+
const languageOptions = getAvailableLanguages();
|
|
124
|
+
language = await promptSelection('Select Target Language', languageOptions);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Apply defaults if still missing (non-interactive or failed prompt)
|
|
129
|
+
tool = tool || DEFAULT_TOOL;
|
|
130
|
+
language = language || 'typescript';
|
|
131
|
+
|
|
132
|
+
// Update options for subsequent steps
|
|
133
|
+
options.tool = tool;
|
|
134
|
+
options.language = language;
|
|
62
135
|
|
|
63
136
|
// Validate tool option
|
|
64
137
|
if (!AI_TOOL_CONFIG[tool]) {
|
|
@@ -72,6 +145,7 @@ async function initProject(targetDir, options = {}) {
|
|
|
72
145
|
|
|
73
146
|
console.log(chalk.cyan('\n☀️ SunLint Init - Setting up code quality standards...\n'));
|
|
74
147
|
console.log(chalk.blue(` 🤖 Target AI Tool: ${toolConfig.name}`));
|
|
148
|
+
console.log(chalk.blue(` 📝 Target Language: ${language}`));
|
|
75
149
|
|
|
76
150
|
// Validate target directory exists
|
|
77
151
|
if (!fs.existsSync(resolvedTargetDir)) {
|
|
@@ -91,7 +165,7 @@ async function initProject(targetDir, options = {}) {
|
|
|
91
165
|
console.log(chalk.white(' Files created/updated:'));
|
|
92
166
|
console.log(chalk.gray(` • ${path.relative(resolvedTargetDir, skillTargetDir)}/`));
|
|
93
167
|
console.log(chalk.gray(` • AGENTS.md`));
|
|
94
|
-
console.log(chalk.cyan(`\n📖 ${toolConfig.name} will now follow SunLint code quality standards.\n`));
|
|
168
|
+
console.log(chalk.cyan(`\n📖 ${toolConfig.name} will now follow SunLint code quality standards for ${language}.\n`));
|
|
95
169
|
}
|
|
96
170
|
|
|
97
171
|
/**
|
|
@@ -120,10 +194,43 @@ async function copySkillFolder(targetPath, options = {}, toolConfig = {}) {
|
|
|
120
194
|
fs.mkdirSync(parentDir, { recursive: true });
|
|
121
195
|
}
|
|
122
196
|
|
|
123
|
-
|
|
197
|
+
const language = options.language || 'typescript';
|
|
198
|
+
|
|
199
|
+
// Copy skill folder usage files (root)
|
|
124
200
|
const relativePath = toolConfig.skillPath || '.agent/skills';
|
|
125
201
|
console.log(chalk.blue(` 📁 Copying skill folder to ${relativePath}/sunlint-code-quality/`));
|
|
126
|
-
|
|
202
|
+
|
|
203
|
+
// 1. Copy root files (SKILL.md, AGENTS.md, etc) - Non-recursive
|
|
204
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
205
|
+
|
|
206
|
+
// Copy root files
|
|
207
|
+
if (fs.existsSync(SKILL_SOURCE_DIR)) {
|
|
208
|
+
const items = fs.readdirSync(SKILL_SOURCE_DIR);
|
|
209
|
+
for (const item of items) {
|
|
210
|
+
const sourcePath = path.join(SKILL_SOURCE_DIR, item);
|
|
211
|
+
const targetItemPath = path.join(targetPath, item);
|
|
212
|
+
const stat = fs.statSync(sourcePath);
|
|
213
|
+
|
|
214
|
+
if (!stat.isDirectory()) {
|
|
215
|
+
fs.copyFileSync(sourcePath, targetItemPath);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 2. Copy Language Rules to rules/ folder
|
|
221
|
+
const rulesSourceDir = path.join(SKILL_SOURCE_DIR, 'rules', language);
|
|
222
|
+
const rulesTargetDir = path.join(targetPath, 'rules');
|
|
223
|
+
|
|
224
|
+
if (fs.existsSync(rulesSourceDir)) {
|
|
225
|
+
console.log(chalk.blue(` 📝 Installing ${language} rules...`));
|
|
226
|
+
copyDirectoryRecursive(rulesSourceDir, rulesTargetDir);
|
|
227
|
+
} else {
|
|
228
|
+
console.warn(chalk.yellow(` ⚠️ Language '${language}' rules not found. Falling back to typescript rules.`));
|
|
229
|
+
const fallbackDir = path.join(SKILL_SOURCE_DIR, 'rules', 'typescript');
|
|
230
|
+
if (fs.existsSync(fallbackDir)) {
|
|
231
|
+
copyDirectoryRecursive(fallbackDir, rulesTargetDir);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
127
234
|
}
|
|
128
235
|
|
|
129
236
|
/**
|
|
@@ -133,7 +240,8 @@ async function copySkillFolder(targetPath, options = {}, toolConfig = {}) {
|
|
|
133
240
|
*/
|
|
134
241
|
function copyDirectoryRecursive(source, target) {
|
|
135
242
|
if (!fs.existsSync(source)) {
|
|
136
|
-
throw new Error(`Source directory not found: ${source}`);
|
|
243
|
+
// throw new Error(`Source directory not found: ${source}`);
|
|
244
|
+
return;
|
|
137
245
|
}
|
|
138
246
|
|
|
139
247
|
fs.mkdirSync(target, { recursive: true });
|
package/core/output-service.js
CHANGED
|
@@ -381,16 +381,22 @@ class OutputService {
|
|
|
381
381
|
if (index > 0) {
|
|
382
382
|
output += '\n';
|
|
383
383
|
}
|
|
384
|
-
output += `\n${chalk.underline(path.resolve(file))}\n`;
|
|
385
384
|
|
|
386
|
-
sortedViolations.forEach(violation => {
|
|
385
|
+
sortedViolations.forEach((violation, index) => {
|
|
386
|
+
const isDuplicateCodeRule = violation.ruleId === 'C002';
|
|
387
|
+
if (index === 0 && !isDuplicateCodeRule) {
|
|
388
|
+
output += `\n${chalk.underline(path.resolve(file))}\n`;
|
|
389
|
+
}
|
|
387
390
|
// Support both location.start.line (new format) and line (legacy format)
|
|
388
391
|
const line = (violation.location?.start?.line || violation.line || 1).toString();
|
|
389
392
|
const column = (violation.location?.start?.column || violation.column || 1).toString();
|
|
390
|
-
const severityText = violation.severity === 'error' ? '
|
|
393
|
+
const severityText = violation.severity === 'error' ? 'Error' : 'Warning';
|
|
391
394
|
const severityColor = violation.severity === 'error' ? chalk.red : chalk.yellow;
|
|
392
|
-
|
|
393
|
-
|
|
395
|
+
if (isDuplicateCodeRule) {
|
|
396
|
+
output += `${severityColor(severityText)} ${chalk.white(` ${chalk.underline(path.resolve(file))}:${line}:${column} ${violation.message} ${chalk.yellow(`(Confidence: ${violation.confidence}%)`)}`)} ${chalk.gray(violation.ruleId)}\n`;
|
|
397
|
+
} else {
|
|
398
|
+
output += ` ${chalk.blue(`${line}:${column}`)} ${severityColor(severityText)} ${violation.message} ${chalk.gray(violation.ruleId)}\n`;
|
|
399
|
+
}
|
|
394
400
|
});
|
|
395
401
|
});
|
|
396
402
|
|
|
@@ -112,7 +112,7 @@ class PerformanceOptimizer {
|
|
|
112
112
|
break;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// Check file count limit (skip if unlimited
|
|
115
|
+
// Check file count limit (skip if unlimited: 0)
|
|
116
116
|
if (this.fileSizeLimits.maxTotalFiles > 0 && filtered.length >= this.fileSizeLimits.maxTotalFiles) {
|
|
117
117
|
if (this.config.verbose) {
|
|
118
118
|
console.log(`⚠️ Reached file count limit: ${this.fileSizeLimits.maxTotalFiles} files`);
|
|
@@ -207,8 +207,8 @@ class UnifiedRuleRegistry {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
// Also check other category directories (security, typescript, etc.)
|
|
211
|
-
const otherDirs = ['security', 'typescript', 'react'];
|
|
210
|
+
// Also check other category directories (security, typescript, dart, etc.)
|
|
211
|
+
const otherDirs = ['security', 'typescript', 'dart', 'react'];
|
|
212
212
|
for (const categoryDir of otherDirs) {
|
|
213
213
|
const categoryPath = path.join(rulesBaseDir, categoryDir);
|
|
214
214
|
|
|
@@ -66,7 +66,7 @@ node cli.js --rule=C002 \
|
|
|
66
66
|
// Line 27-51
|
|
67
67
|
function detectDartSupport(ruleId) {
|
|
68
68
|
const rulesBasePath = path.join(__dirname, '../rules');
|
|
69
|
-
const categories = ['common', 'security', 'typescript'];
|
|
69
|
+
const categories = ['common', 'security', 'typescript', 'dart'];
|
|
70
70
|
|
|
71
71
|
for (const category of categories) {
|
|
72
72
|
const categoryPath = path.join(rulesBasePath, category);
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Registry Generation - Visual Diagram
|
|
2
|
+
|
|
3
|
+
> Quick visual reference cho registry generation flow
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🔄 High-Level Overview
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
EDIT HERE AUTO-COPY AUTO-GENERATE USED HERE
|
|
11
|
+
↓ ↓ ↓ ↓
|
|
12
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
13
|
+
│ Markdown │ → │ origin-rules │ → │ Registry │ → │ SunLint │
|
|
14
|
+
│ Files │ │ (copies) │ │ JSON │ │ CLI/VSCode │
|
|
15
|
+
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
|
|
16
|
+
dart-en.md dart-en.md (copy) rules-registry- Display rule names
|
|
17
|
+
common-en.md common-en.md generated.json Show descriptions
|
|
18
|
+
Run analysis
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 📂 Directory Structure
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
engineer-excellence/
|
|
27
|
+
│
|
|
28
|
+
├── coding-quality/
|
|
29
|
+
│ │
|
|
30
|
+
│ ├── rules/ ← 📝 EDIT HERE (Step 1)
|
|
31
|
+
│ │ ├── dart-en.md ← Source of Truth
|
|
32
|
+
│ │ ├── dart.md ← Vietnamese version
|
|
33
|
+
│ │ ├── common-en.md
|
|
34
|
+
│ │ ├── security-en.md
|
|
35
|
+
│ │ ├── examples/
|
|
36
|
+
│ │ │ ├── en/D001.md ← Create examples
|
|
37
|
+
│ │ │ └── vi/D001.md
|
|
38
|
+
│ │ └── ...
|
|
39
|
+
│ │
|
|
40
|
+
│ └── extensions/sunlint/
|
|
41
|
+
│ │
|
|
42
|
+
│ ├── origin-rules/ ← 📋 AUTO-COPIED (Step 2)
|
|
43
|
+
│ │ ├── dart-en.md ← Copy from ../../../rules/
|
|
44
|
+
│ │ ├── common-en.md
|
|
45
|
+
│ │ └── ...
|
|
46
|
+
│ │
|
|
47
|
+
│ ├── config/
|
|
48
|
+
│ │ └── rules/
|
|
49
|
+
│ │ └── rules-registry-generated.json ← 🤖 AUTO-GENERATED (Step 3)
|
|
50
|
+
│ │
|
|
51
|
+
│ └── scripts/
|
|
52
|
+
│ ├── copy-rules.js ← Run this first
|
|
53
|
+
│ └── generate-rules-registry.js ← Then run this
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 🔄 Step-by-Step Flow
|
|
59
|
+
|
|
60
|
+
### Step 1: Edit Markdown (SOURCE OF TRUTH)
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
📝 Edit: coding-quality/rules/dart-en.md
|
|
64
|
+
|
|
65
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
66
|
+
│ ### 📘 Rule D001 – Recommended Lint Rules Should Be Enabled │
|
|
67
|
+
│ │
|
|
68
|
+
│ - **Objective**: Ensure code quality... │
|
|
69
|
+
│ - **Details**: The `analysis_options.yaml`... │
|
|
70
|
+
│ - **Applies to**: Flutter/Dart │
|
|
71
|
+
│ - **Tools**: `dart lint` (flutter_lints...) │
|
|
72
|
+
│ - **Principles**: CODE_QUALITY │
|
|
73
|
+
│ - **Version**: 1.0 │
|
|
74
|
+
│ - **Status**: activated │
|
|
75
|
+
│ - **Severity**: major │
|
|
76
|
+
└─────────────────────────────────────────────────────────────┘
|
|
77
|
+
↓
|
|
78
|
+
This is what users
|
|
79
|
+
will see in CLI
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Step 2: Copy Rules Script
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
$ cd coding-quality/extensions/sunlint
|
|
86
|
+
$ node scripts/copy-rules.js
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
📋 Copy Process:
|
|
91
|
+
|
|
92
|
+
Source Destination
|
|
93
|
+
↓ ↓
|
|
94
|
+
../../../rules/ origin-rules/
|
|
95
|
+
├── dart-en.md → ├── dart-en.md
|
|
96
|
+
├── common-en.md → ├── common-en.md
|
|
97
|
+
├── security-en.md → ├── security-en.md
|
|
98
|
+
└── ... → └── ...
|
|
99
|
+
|
|
100
|
+
✅ Successfully copied 8 rule files
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Step 3: Generate Registry Script
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
$ node scripts/generate-rules-registry.js
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
🤖 Generation Process:
|
|
111
|
+
|
|
112
|
+
origin-rules/ Parser Logic config/rules/
|
|
113
|
+
↓ ↓ ↓
|
|
114
|
+
dart-en.md → Extract: rules-registry-generated.json
|
|
115
|
+
common-en.md → - Rule ID (D001) {
|
|
116
|
+
security-en.md → - Name "rules": {
|
|
117
|
+
... → - Description "D001": {
|
|
118
|
+
- Tools "name": "Recommended...",
|
|
119
|
+
- Severity "description": "...",
|
|
120
|
+
- etc. "severity": "major",
|
|
121
|
+
...
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
✅ Generated registry with 257 rules
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Step 4: Used by SunLint
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
config/rules/ UnifiedRuleRegistry CLI Output
|
|
133
|
+
↓ ↓ ↓
|
|
134
|
+
rules-registry- Loads registry $ sunlint --rule=D001
|
|
135
|
+
generated.json → Finds D001 entry →
|
|
136
|
+
{ Reads: D001: Recommended Lint
|
|
137
|
+
"D001": { - name Rules Should Be Enabled
|
|
138
|
+
"name": "...", - description ─────────────────────
|
|
139
|
+
"description": "..." - severity analysis_options.yaml
|
|
140
|
+
} 1:1 warning ...
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 🔁 Complete Cycle with Example
|
|
147
|
+
|
|
148
|
+
### Scenario: Rename Rule D001
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
┌─ STEP 1: Edit Markdown ────────────────────────────────────┐
|
|
152
|
+
│ │
|
|
153
|
+
│ $ vim ../../../rules/dart-en.md │
|
|
154
|
+
│ │
|
|
155
|
+
│ BEFORE: │
|
|
156
|
+
│ ### 📘 Rule D001 – Keep parameter names consistent │
|
|
157
|
+
│ │
|
|
158
|
+
│ AFTER: │
|
|
159
|
+
│ ### 📘 Rule D001 – Recommended Lint Rules Should Be Enabled │
|
|
160
|
+
│ │
|
|
161
|
+
└──────────────────────────────────────────────────────────────┘
|
|
162
|
+
↓
|
|
163
|
+
┌─ STEP 2: Copy Rules ───────────────────────────────────────┐
|
|
164
|
+
│ │
|
|
165
|
+
│ $ node scripts/copy-rules.js │
|
|
166
|
+
│ │
|
|
167
|
+
│ Output: │
|
|
168
|
+
│ ✅ Copied: dart-en.md │
|
|
169
|
+
│ ✅ Successfully copied 8 rule files │
|
|
170
|
+
│ │
|
|
171
|
+
└──────────────────────────────────────────────────────────────┘
|
|
172
|
+
↓
|
|
173
|
+
┌─ STEP 3: Generate Registry ────────────────────────────────┐
|
|
174
|
+
│ │
|
|
175
|
+
│ $ node scripts/generate-rules-registry.js │
|
|
176
|
+
│ │
|
|
177
|
+
│ Output: │
|
|
178
|
+
│ Parsed rule D001: 0 good examples, 0 bad examples │
|
|
179
|
+
│ ✅ Generated registry with 257 rules │
|
|
180
|
+
│ │
|
|
181
|
+
└──────────────────────────────────────────────────────────────┘
|
|
182
|
+
↓
|
|
183
|
+
┌─ STEP 4: Verify ───────────────────────────────────────────┐
|
|
184
|
+
│ │
|
|
185
|
+
│ $ grep -A 3 '"D001"' config/rules/rules-registry- │
|
|
186
|
+
│ generated.json │
|
|
187
|
+
│ │
|
|
188
|
+
│ Output: │
|
|
189
|
+
│ "D001": { │
|
|
190
|
+
│ "name": "Recommended Lint Rules Should Be Enabled", │
|
|
191
|
+
│ "description": "Ensure code quality...", │
|
|
192
|
+
│ ... │
|
|
193
|
+
│ } │
|
|
194
|
+
│ │
|
|
195
|
+
└──────────────────────────────────────────────────────────────┘
|
|
196
|
+
↓
|
|
197
|
+
┌─ STEP 5: Test ─────────────────────────────────────────────┐
|
|
198
|
+
│ │
|
|
199
|
+
│ $ node cli.js --rule=D001 --input=./test --languages=dart │
|
|
200
|
+
│ │
|
|
201
|
+
│ Output shows NEW NAME: │
|
|
202
|
+
│ D001: Recommended Lint Rules Should Be Enabled │
|
|
203
|
+
│ ───────────────────────────────────────────────── │
|
|
204
|
+
│ analysis_options.yaml │
|
|
205
|
+
│ 1:1 warning ... │
|
|
206
|
+
│ │
|
|
207
|
+
└──────────────────────────────────────────────────────────────┘
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## ❌ Wrong Way vs ✅ Right Way
|
|
213
|
+
|
|
214
|
+
### ❌ WRONG: Edit JSON Directly
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
┌─ WRONG APPROACH ──────────────────────────────────────────┐
|
|
218
|
+
│ │
|
|
219
|
+
│ $ vim config/rules/rules-registry-generated.json │
|
|
220
|
+
│ │
|
|
221
|
+
│ Change: │
|
|
222
|
+
│ "name": "Old Name" │
|
|
223
|
+
│ To: │
|
|
224
|
+
│ "name": "New Name" │
|
|
225
|
+
│ │
|
|
226
|
+
│ ❌ Problem: Will be overwritten on next generation! │
|
|
227
|
+
│ │
|
|
228
|
+
└─────────────────────────────────────────────────────────────┘
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### ✅ RIGHT: Edit Markdown → Regenerate
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
┌─ CORRECT APPROACH ────────────────────────────────────────┐
|
|
235
|
+
│ │
|
|
236
|
+
│ STEP 1: Edit source │
|
|
237
|
+
│ $ vim ../../../rules/dart-en.md │
|
|
238
|
+
│ Change: ### 📘 Rule D001 – Old Name │
|
|
239
|
+
│ To: ### 📘 Rule D001 – New Name │
|
|
240
|
+
│ │
|
|
241
|
+
│ STEP 2 & 3: Regenerate │
|
|
242
|
+
│ $ node scripts/copy-rules.js │
|
|
243
|
+
│ $ node scripts/generate-rules-registry.js │
|
|
244
|
+
│ │
|
|
245
|
+
│ ✅ Result: Changes persist, properly tracked in git │
|
|
246
|
+
│ │
|
|
247
|
+
└─────────────────────────────────────────────────────────────┘
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 🎯 Quick Reference Card
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
256
|
+
║ REGISTRY GENERATION CHEATSHEET ║
|
|
257
|
+
╠═══════════════════════════════════════════════════════════╣
|
|
258
|
+
║ ║
|
|
259
|
+
║ 📝 To change rule name: ║
|
|
260
|
+
║ 1. Edit: ../../../rules/dart-en.md ║
|
|
261
|
+
║ 2. Run: node scripts/copy-rules.js ║
|
|
262
|
+
║ 3. Run: node scripts/generate-rules-registry.js ║
|
|
263
|
+
║ ║
|
|
264
|
+
║ 🔍 To verify changes: ║
|
|
265
|
+
║ grep '"D001"' config/rules/rules-registry- ║
|
|
266
|
+
║ generated.json ║
|
|
267
|
+
║ ║
|
|
268
|
+
║ ✅ Files you SHOULD edit: ║
|
|
269
|
+
║ - coding-quality/rules/dart-en.md ║
|
|
270
|
+
║ - coding-quality/rules/dart.md ║
|
|
271
|
+
║ - coding-quality/rules/examples/en/D001.md ║
|
|
272
|
+
║ ║
|
|
273
|
+
║ ❌ Files you should NEVER edit: ║
|
|
274
|
+
║ - origin-rules/*.md (auto-copied) ║
|
|
275
|
+
║ - config/rules/rules-registry-generated.json ║
|
|
276
|
+
║ ║
|
|
277
|
+
║ 📖 Read more: ║
|
|
278
|
+
║ docs/REGISTRY_GENERATION_FLOW.md ║
|
|
279
|
+
║ ║
|
|
280
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 🔗 Related Documents
|
|
286
|
+
|
|
287
|
+
- [REGISTRY_GENERATION_FLOW.md](./REGISTRY_GENERATION_FLOW.md) - Detailed explanation
|
|
288
|
+
- [CREATE_NEW_DART_RULE.md](./skills/CREATE_NEW_DART_RULE.md) - How to create rules
|
|
289
|
+
- [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) - Overall structure
|