@sun-asterisk/sunlint 1.3.9 → 1.3.11

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,397 @@
1
+ # Quality Scoring & Summary Report Guide
2
+
3
+ ## Overview
4
+
5
+ SunLint includes a comprehensive quality scoring system and summary report generation feature designed for CI/CD integration and management dashboards.
6
+
7
+ ## Features
8
+
9
+ ### 1. Quality Scoring System
10
+
11
+ The quality score is calculated based on multiple factors:
12
+
13
+ ```javascript
14
+ Score Formula: 100 - (errorCount × 5 + warningCount × 1) × (1000 / LOC) + (rulesChecked × 0.5)
15
+ ```
16
+
17
+ **Scoring Components:**
18
+ - **Base Score**: Starts at 100 (perfect quality)
19
+ - **Error Penalty**: -5 points per error
20
+ - **Warning Penalty**: -1 point per warning
21
+ - **LOC Normalization**: Violations are normalized per 1000 lines of code
22
+ - **Rule Bonus**: +0.5 points per rule checked (max 10 points)
23
+ - **Final Range**: 0-100
24
+
25
+ **Grade Scale:**
26
+ - **A+**: 95-100
27
+ - **A**: 90-94
28
+ - **B+**: 85-89
29
+ - **B**: 80-84
30
+ - **C+**: 75-79
31
+ - **C**: 70-74
32
+ - **D**: 60-69
33
+ - **F**: Below 60
34
+
35
+ ### 2. Summary Report Format
36
+
37
+ The summary report is generated in JSON format, designed for easy integration with CI/CD pipelines and management dashboards.
38
+
39
+ **Example Output:**
40
+ ```json
41
+ {
42
+ "metadata": {
43
+ "generated_at": "2025-10-07T03:48:00.636Z",
44
+ "tool": "SunLint",
45
+ "version": "1.3.9",
46
+ "analysis_duration_ms": 390
47
+ },
48
+ "repository": {
49
+ "repository_url": "https://github.com/org/repo",
50
+ "branch": "main",
51
+ "commit_hash": "abc123"
52
+ },
53
+ "quality": {
54
+ "score": 98.9,
55
+ "grade": "A+",
56
+ "metrics": {
57
+ "errors": 0,
58
+ "warnings": 520,
59
+ "rulesChecked": 1,
60
+ "linesOfCode": 319709,
61
+ "violationsPerKLOC": 1.6
62
+ }
63
+ },
64
+ "violations": {
65
+ "total": 520,
66
+ "by_severity": {
67
+ "errors": 0,
68
+ "warnings": 520
69
+ },
70
+ "by_rule": [
71
+ {
72
+ "rule_code": "C065",
73
+ "count": 520,
74
+ "severity": "warning"
75
+ }
76
+ ]
77
+ },
78
+ "analysis": {
79
+ "files_analyzed": 1000,
80
+ "rules_checked": 1,
81
+ "lines_of_code": 319709
82
+ }
83
+ }
84
+ ```
85
+
86
+ ## Usage
87
+
88
+ ### Basic Usage
89
+
90
+ ```bash
91
+ # Generate summary report only
92
+ node cli.js --input=src --rule=C065 --output-summary=summary.json
93
+
94
+ # Generate both detailed report and summary report
95
+ node cli.js --input=src --rule=C065 --output=report.txt --output-summary=summary.json
96
+ ```
97
+
98
+ ### CI/CD Integration
99
+
100
+ #### GitHub Actions Example
101
+
102
+ ```yaml
103
+ name: Code Quality Check
104
+
105
+ on:
106
+ pull_request:
107
+ branches: [ main ]
108
+ push:
109
+ branches: [ main ]
110
+
111
+ jobs:
112
+ quality-check:
113
+ runs-on: ubuntu-latest
114
+
115
+ steps:
116
+ - uses: actions/checkout@v3
117
+
118
+ - name: Setup Node.js
119
+ uses: actions/setup-node@v3
120
+ with:
121
+ node-version: '18'
122
+
123
+ - name: Install dependencies
124
+ run: npm install
125
+
126
+ - name: Run SunLint Analysis
127
+ run: |
128
+ node cli.js \
129
+ --input=src \
130
+ --rule=C001,C005,C015,C065 \
131
+ --output-summary=quality-report.json
132
+ env:
133
+ GITHUB_REPOSITORY: ${{ github.repository }}
134
+ GITHUB_REF_NAME: ${{ github.ref_name }}
135
+ GITHUB_SHA: ${{ github.sha }}
136
+
137
+ - name: Upload Quality Report
138
+ uses: actions/upload-artifact@v3
139
+ with:
140
+ name: quality-report
141
+ path: quality-report.json
142
+
143
+ - name: Post to Dashboard
144
+ run: |
145
+ curl -X POST https://your-dashboard.com/api/quality \
146
+ -H "Content-Type: application/json" \
147
+ -H "Authorization: Bearer ${{ secrets.DASHBOARD_TOKEN }}" \
148
+ -d @quality-report.json
149
+ ```
150
+
151
+ ### Environment Variables
152
+
153
+ The summary report automatically detects and uses Git information from environment variables (commonly available in CI/CD):
154
+
155
+ | Variable | Description | Source |
156
+ |----------|-------------|--------|
157
+ | `GITHUB_REPOSITORY` | Repository name (e.g., `owner/repo`) | GitHub Actions |
158
+ | `GITHUB_REF_NAME` | Branch or tag name | GitHub Actions |
159
+ | `GITHUB_SHA` | Commit hash | GitHub Actions |
160
+
161
+ If these variables are not available, the tool will automatically detect Git information from the local repository.
162
+
163
+ ## CLI Options
164
+
165
+ ### `--output-summary <file>`
166
+
167
+ Generate a summary report in JSON format suitable for CI/CD and management dashboards.
168
+
169
+ **Differences from `--output`:**
170
+
171
+ | Feature | `--output` | `--output-summary` |
172
+ |---------|-----------|-------------------|
173
+ | Format | Text/JSON (ESLint-compatible) | JSON (Custom format) |
174
+ | Detail Level | Full violation details | Summary only |
175
+ | File Size | Large (includes all details) | Small (summary only) |
176
+ | Purpose | Detailed debugging | CI/CD integration |
177
+ | Includes Score | ❌ No | ✅ Yes |
178
+ | Includes LOC | ❌ No | ✅ Yes |
179
+ | Git Info | ❌ No | ✅ Yes |
180
+ | Violation Count by Rule | ❌ No | ✅ Yes |
181
+
182
+ **Example:**
183
+
184
+ ```bash
185
+ # Detailed report: Shows every violation with line numbers and messages
186
+ node cli.js --input=src --rule=C065 --output=detailed-report.txt
187
+
188
+ # Summary report: Shows total count per rule, score, and metrics
189
+ node cli.js --input=src --rule=C065 --output-summary=summary.json
190
+
191
+ # Both: Get detailed report for debugging + summary for dashboard
192
+ node cli.js --input=src --rule=C065 \
193
+ --output=detailed-report.txt \
194
+ --output-summary=summary.json
195
+ ```
196
+
197
+ ## Integration Examples
198
+
199
+ ### 1. Send to Management Dashboard
200
+
201
+ ```javascript
202
+ const fs = require('fs');
203
+ const axios = require('axios');
204
+
205
+ const report = JSON.parse(fs.readFileSync('summary.json', 'utf8'));
206
+
207
+ await axios.post('https://dashboard.company.com/api/quality', {
208
+ project: 'my-project',
209
+ ...report
210
+ }, {
211
+ headers: {
212
+ 'Authorization': `Bearer ${process.env.DASHBOARD_TOKEN}`
213
+ }
214
+ });
215
+ ```
216
+
217
+ ### 2. Slack Notification
218
+
219
+ ```javascript
220
+ const fs = require('fs');
221
+ const axios = require('axios');
222
+
223
+ const report = JSON.parse(fs.readFileSync('summary.json', 'utf8'));
224
+ const { quality, violations, analysis } = report;
225
+
226
+ const message = {
227
+ text: `Code Quality Report: ${quality.grade} (${quality.score})`,
228
+ blocks: [
229
+ {
230
+ type: "section",
231
+ text: {
232
+ type: "mrkdwn",
233
+ text: `*Code Quality Analysis*\n` +
234
+ `Score: *${quality.score}* (${quality.grade})\n` +
235
+ `Violations: ${violations.total} (${violations.by_severity.errors} errors, ${violations.by_severity.warnings} warnings)\n` +
236
+ `Files: ${analysis.files_analyzed} | LOC: ${analysis.lines_of_code.toLocaleString()}`
237
+ }
238
+ }
239
+ ]
240
+ };
241
+
242
+ await axios.post(process.env.SLACK_WEBHOOK_URL, message);
243
+ ```
244
+
245
+ ### 3. Quality Gate in CI/CD
246
+
247
+ ```bash
248
+ #!/bin/bash
249
+
250
+ # Run analysis
251
+ node cli.js --input=src --rule=C001,C005,C015,C065 --output-summary=quality.json
252
+
253
+ # Extract score
254
+ SCORE=$(jq -r '.quality.score' quality.json)
255
+
256
+ # Set minimum score threshold
257
+ MIN_SCORE=80
258
+
259
+ # Check if score meets threshold
260
+ if (( $(echo "$SCORE < $MIN_SCORE" | bc -l) )); then
261
+ echo "❌ Quality score $SCORE is below threshold $MIN_SCORE"
262
+ exit 1
263
+ else
264
+ echo "✅ Quality score $SCORE meets threshold"
265
+ exit 0
266
+ fi
267
+ ```
268
+
269
+ ## Best Practices
270
+
271
+ ### 1. Rule Selection
272
+
273
+ Choose rules appropriate for your quality goals:
274
+
275
+ ```bash
276
+ # Security focus
277
+ node cli.js --input=src --rule=S001,S004,S010 --output-summary=security.json
278
+
279
+ # Code organization focus
280
+ node cli.js --input=src --rule=C001,C005,C006,C015 --output-summary=organization.json
281
+
282
+ # Testing focus
283
+ node cli.js --input=src --rule=C065,C066 --output-summary=testing.json
284
+
285
+ # Comprehensive check (slower)
286
+ node cli.js --input=src --rule=security,cleancode --output-summary=full.json
287
+ ```
288
+
289
+ ### 2. Performance Optimization
290
+
291
+ For large projects:
292
+
293
+ ```bash
294
+ # Analyze only changed files (PR mode)
295
+ node cli.js --input=src --rule=C001,C005 --changed-files --output-summary=pr-quality.json
296
+
297
+ # Set file limits for faster analysis
298
+ node cli.js --input=src --rule=C001,C005 --max-files=500 --output-summary=quick-check.json
299
+ ```
300
+
301
+ ### 3. Trending Analysis
302
+
303
+ Track quality over time:
304
+
305
+ ```javascript
306
+ const fs = require('fs');
307
+
308
+ // Save with timestamp
309
+ const report = JSON.parse(fs.readFileSync('summary.json', 'utf8'));
310
+ const timestamp = new Date().toISOString();
311
+
312
+ // Store in database or time-series storage
313
+ await db.collection('quality_reports').insertOne({
314
+ ...report,
315
+ timestamp
316
+ });
317
+
318
+ // Query trends
319
+ const last30Days = await db.collection('quality_reports')
320
+ .find({
321
+ timestamp: { $gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }
322
+ })
323
+ .sort({ timestamp: 1 })
324
+ .toArray();
325
+
326
+ // Calculate trend
327
+ const scores = last30Days.map(r => r.quality.score);
328
+ const trend = scores[scores.length - 1] - scores[0];
329
+ console.log(`Quality trend (30 days): ${trend > 0 ? '+' : ''}${trend.toFixed(1)}`);
330
+ ```
331
+
332
+ ## Scoring Interpretation
333
+
334
+ ### Score Ranges
335
+
336
+ - **95-100 (A+)**: Excellent code quality with minimal violations
337
+ - **90-94 (A)**: Very good quality, minor improvements possible
338
+ - **85-89 (B+)**: Good quality with some areas for improvement
339
+ - **80-84 (B)**: Acceptable quality, several improvements recommended
340
+ - **75-79 (C+)**: Below average, significant improvements needed
341
+ - **70-74 (C)**: Poor quality, requires immediate attention
342
+ - **60-69 (D)**: Very poor quality, major refactoring needed
343
+ - **0-59 (F)**: Critical quality issues
344
+
345
+ ### Metrics Understanding
346
+
347
+ **Violations per KLOC (Violations per 1000 Lines of Code)**
348
+
349
+ This metric normalizes violations by code size:
350
+
351
+ - **< 1**: Excellent
352
+ - **1-3**: Good
353
+ - **3-5**: Fair
354
+ - **5-10**: Poor
355
+ - **> 10**: Critical
356
+
357
+ ## Troubleshooting
358
+
359
+ ### LOC Calculation Issues
360
+
361
+ If LOC is 0 or incorrect:
362
+
363
+ ```bash
364
+ # Ensure input path is correct
365
+ node cli.js --input=./src --rule=C065 --output-summary=summary.json
366
+
367
+ # For multiple paths
368
+ node cli.js --input=./src,./lib --rule=C065 --output-summary=summary.json
369
+ ```
370
+
371
+ ### Git Information Not Detected
372
+
373
+ In CI/CD, ensure environment variables are set:
374
+
375
+ ```yaml
376
+ env:
377
+ GITHUB_REPOSITORY: ${{ github.repository }}
378
+ GITHUB_REF_NAME: ${{ github.ref_name }}
379
+ GITHUB_SHA: ${{ github.sha }}
380
+ ```
381
+
382
+ For local development:
383
+
384
+ ```bash
385
+ # Check if git is available
386
+ git rev-parse --git-dir
387
+
388
+ # Run from repository root
389
+ cd /path/to/repo
390
+ node /path/to/sunlint/cli.js --input=src --output-summary=summary.json
391
+ ```
392
+
393
+ ## See Also
394
+
395
+ - [Configuration Guide](CONFIGURATION.md)
396
+ - [CI/CD Integration Guide](CI-CD-GUIDE.md)
397
+ - [Command Examples](COMMAND-EXAMPLES.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sunlint",
3
- "version": "1.3.9",
3
+ "version": "1.3.11",
4
4
  "description": "☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -48,6 +48,14 @@ class C024SymbolBasedAnalyzer {
48
48
  }
49
49
 
50
50
  try {
51
+ // skip ignored files
52
+ if (this.isIgnoredFile(filePath)) {
53
+ if (verbose) {
54
+ console.log(`🔍 [C024 Symbol-Based] Skipping ignored file: ${filePath}`);
55
+ }
56
+ return violations;
57
+ }
58
+
51
59
  const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
52
60
  if (!sourceFile) {
53
61
  return violations;
@@ -99,9 +107,7 @@ class C024SymbolBasedAnalyzer {
99
107
  const kind = node.getKind();
100
108
  if (
101
109
  kind === SyntaxKind.StringLiteral ||
102
- kind === SyntaxKind.NumericLiteral ||
103
- kind === SyntaxKind.TrueKeyword ||
104
- kind === SyntaxKind.FalseKeyword
110
+ kind === SyntaxKind.NumericLiteral
105
111
  ) {
106
112
  const text = node.getText().replace(/['"`]/g, ""); // strip quotes
107
113
  if (this.isAllowedLiteral(node, text)) return;
@@ -120,9 +126,27 @@ class C024SymbolBasedAnalyzer {
120
126
  const kind = node.getKind();
121
127
  if (kind === SyntaxKind.VariableDeclaration) {
122
128
  const parentKind = node.getParent()?.getKind();
129
+ // Skip detection for `for ... of` loop variable
130
+ const loopAncestor = node.getFirstAncestor((ancestor) => {
131
+ const kind = ancestor.getKind?.();
132
+ return (
133
+ kind === SyntaxKind.ForOfStatement ||
134
+ kind === SyntaxKind.ForInStatement ||
135
+ kind === SyntaxKind.ForStatement ||
136
+ kind === SyntaxKind.WhileStatement ||
137
+ kind === SyntaxKind.DoStatement ||
138
+ kind === SyntaxKind.SwitchStatement
139
+ );
140
+ });
141
+
142
+ if (loopAncestor) {
143
+ return; // skip for all loop/switch contexts, no matter how nested
144
+ }
145
+
123
146
  if (
124
147
  parentKind === SyntaxKind.VariableDeclarationList &&
125
- node.getParent().getDeclarationKind() === "const"
148
+ node.getParent().getDeclarationKind() === "const" &&
149
+ !node.getInitializer()
126
150
  ) {
127
151
  this.pushViolation(
128
152
  violations,
@@ -154,27 +178,109 @@ class C024SymbolBasedAnalyzer {
154
178
 
155
179
  // --- helper: allow safe literals ---
156
180
  isAllowedLiteral(node, text) {
157
- // skip imports
158
- if (node.getParent()?.getKind() === SyntaxKind.ImportDeclaration) {
181
+ const parent = node.getParent();
182
+
183
+ // 1 Skip imports/exports
184
+ if (parent?.getKind() === SyntaxKind.ImportDeclaration) return true;
185
+ if (parent?.getKind() === SyntaxKind.ExportDeclaration) return true;
186
+
187
+ // 2 Skip literals that are inside call expressions (direct or nested)
188
+ if (
189
+ parent?.getKind() === SyntaxKind.CallExpression ||
190
+ parent?.getFirstAncestorByKind(SyntaxKind.CallExpression)
191
+ ) {
159
192
  return true;
160
193
  }
161
194
 
162
- // allow short strings
195
+ if (
196
+ parent?.getKind() === SyntaxKind.ElementAccessExpression &&
197
+ parent.getArgumentExpression?.() === node
198
+ ) {
199
+ return true; // skip array/object key
200
+ }
201
+
202
+ // 3 Allow short strings
163
203
  if (typeof text === "string" && text.length <= 1) return true;
164
204
 
165
- // allow sentinel numbers
205
+ // 4 Allow sentinel numbers
166
206
  if (text === "0" || text === "1" || text === "-1") return true;
167
207
 
168
- // allow known safe strings (like "UNKNOWN")
208
+ // 5 Allow known safe strings (like "UNKNOWN")
169
209
  if (this.safeStrings.includes(text)) return true;
170
210
 
211
+ // 6 Allow SQL-style placeholders (:variable) inside string/template
212
+ if (typeof text === "string" && /:\w+/.test(text)) {
213
+ return true;
214
+ }
215
+
171
216
  return false;
172
217
  }
173
218
 
174
219
  // helper to check if file is a constants file
175
220
  isConstantsFile(filePath) {
176
221
  const lower = filePath.toLowerCase();
177
- return lower.endsWith("constants.ts") || lower.includes("/constants/");
222
+
223
+ // common suffixes/patterns for utility or structural files
224
+ const ignoredSuffixes = [
225
+ ".constants.ts",
226
+ ".const.ts",
227
+ ".enum.ts",
228
+ ".interface.ts",
229
+ ".response.ts",
230
+ ".request.ts",
231
+ ".res.ts",
232
+ ".req.ts",
233
+ ];
234
+
235
+ // 1 direct suffix match
236
+ if (ignoredSuffixes.some(suffix => lower.endsWith(suffix))) {
237
+ return true;
238
+ }
239
+
240
+ // 2 matches dto.xxx.ts (multi-dot dto files)
241
+ if (/\.dto\.[^.]+\.ts$/.test(lower)) {
242
+ return true;
243
+ }
244
+
245
+ // 3 matches folder-based conventions
246
+ if (
247
+ lower.includes("/constants/") ||
248
+ lower.includes("/enums/") ||
249
+ lower.includes("/interfaces/")
250
+ ) {
251
+ return true;
252
+ }
253
+
254
+ return false;
255
+ }
256
+
257
+
258
+ isIgnoredFile(filePath) {
259
+ const ignoredPatterns = [
260
+ /\.test\./i,
261
+ /\.tests\./i,
262
+ /\.spec\./i,
263
+ /\.mock\./i,
264
+ /\.css$/i,
265
+ /\.scss$/i,
266
+ /\.html$/i,
267
+ /\.json$/i,
268
+ /\.md$/i,
269
+ /\.svg$/i,
270
+ /\.png$/i,
271
+ /\.jpg$/i,
272
+ /\.jpeg$/i,
273
+ /\.gif$/i,
274
+ /\.bmp$/i,
275
+ /\.ico$/i,
276
+ /\.lock$/i,
277
+ /\.log$/i,
278
+ /\/test\//i,
279
+ /\/tests\//i,
280
+ /\/spec\//i
281
+ ];
282
+
283
+ return ignoredPatterns.some((regex) => regex.test(filePath));
178
284
  }
179
285
  }
180
286