@sun-asterisk/sunlint 1.3.10 → 1.3.12

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/CHANGELOG.md CHANGED
@@ -2,7 +2,105 @@
2
2
 
3
3
  ---
4
4
 
5
- ## 🔧 **v1.3.9 - File Targeting Regression Fix (October 2, 2025)**
5
+ ## **v1.4.0 - Quality Scoring & Summary Reports (October 7, 2025)**
6
+
7
+ **Release Date**: October 7, 2025
8
+ **Type**: Major Feature Release
9
+ **Branch**: `fix.sunlint.cli_options`
10
+
11
+ ### ✨ **Major Features**
12
+
13
+ #### **Quality Scoring System** 🎯
14
+ - **NEW**: Automated quality score calculation (0-100) based on violations, LOC, and rules checked
15
+ - **NEW**: Grade system (A+ to F) for easy quality interpretation
16
+ - **Formula**: `Score = 100 - (errors × 5 + warnings × 1) × (1000 / LOC) + (rules × 0.5)`
17
+ - **Normalization**: Violations normalized per 1000 lines of code (KLOC)
18
+ - **Bonus System**: Up to 10 points bonus for comprehensive rule coverage
19
+
20
+ #### **Summary Report Generation** 📊
21
+ - **NEW**: `--output-summary <file>` option for API-compatible JSON summary reports
22
+ - **Format**: Direct dashboard API compatibility - **no transformation needed!**
23
+ - **Git Integration**: Auto-detects complete repository and commit information
24
+ - Repository: URL, name, project path (mono-repo support)
25
+ - Commit: hash, message, author name, author email
26
+ - PR tracking: Automatic PR number extraction from commit messages/branch names
27
+ - **Environment Variables**: Full GitHub Actions support
28
+ - `GITHUB_REPOSITORY`, `GITHUB_REF_NAME`, `GITHUB_SHA`
29
+ - `GITHUB_EVENT_PATH` for commit details
30
+ - Automatic fallback to git commands if env vars unavailable
31
+ - **Mono-Repo Support**: Automatic `project_path` detection for multi-project repositories
32
+ - **Violation Summary**: Aggregated violations by rule with count and severity
33
+ - **Metrics**: LOC, files analyzed, violations per KLOC, errors/warnings breakdown
34
+ - **API-Ready**: Flat structure format ready for direct POST to dashboard APIs
35
+
36
+ #### **New Services** 🛠️
37
+ - **ScoringService**: Calculate quality scores with customizable weights
38
+ - Error penalty: -5 points
39
+ - Warning penalty: -1 point
40
+ - Rule bonus: +0.5 points (max 10)
41
+ - LOC normalization factor: 0.001
42
+ - **SummaryReportService**: Generate and save summary reports
43
+ - Git information extraction
44
+ - Violation aggregation by rule
45
+ - Text summary formatting for console
46
+
47
+ ### 🔧 **CLI Enhancements**
48
+ - **FIXED**: Default semantic analysis now enabled for heuristic engine
49
+ - Removed default `'0'` from `--max-semantic-files` option
50
+ - ts-morph now works by default without explicit `--max-semantic-files=-1`
51
+ - **ENHANCED**: Display message now shows "TypeScript/JavaScript files (TS/TSX/JS/JSX)"
52
+ - Previously only showed "TS/JS files" causing confusion about TSX support
53
+ - **IMPROVED**: Semantic engine disabled message now more informative
54
+ - Clarifies when analysis is explicitly disabled vs. using default
55
+
56
+ ### 📝 **Documentation**
57
+ - **NEW**: `docs/QUALITY_SCORING_GUIDE.md` - Comprehensive scoring guide
58
+ - Scoring formula explanation
59
+ - Grade scale interpretation
60
+ - CI/CD integration examples (GitHub Actions, GitLab CI)
61
+ - Dashboard integration patterns
62
+ - Quality gate setup
63
+ - Trending analysis examples
64
+ - **NEW**: `examples/github-actions-quality-check.yml` - GitHub Actions workflow template
65
+ - **UPDATED**: README.md with Quality Scoring section
66
+
67
+ ### 🎯 **Use Cases**
68
+ - **CI/CD Integration**: Automated quality gates in pipelines
69
+ - **Management Dashboards**: Summary reports for project quality tracking
70
+ - **Quality Trending**: Track quality metrics over time
71
+ - **PR Comments**: Automated quality feedback on pull requests
72
+ - **Slack Notifications**: Quality score alerts to team channels
73
+
74
+ ### 📊 **Example Output**
75
+ ```json
76
+ {
77
+ "quality": {
78
+ "score": 92.6,
79
+ "grade": "A",
80
+ "metrics": {
81
+ "errors": 0,
82
+ "warnings": 39,
83
+ "linesOfCode": 4954,
84
+ "violationsPerKLOC": 7.9
85
+ }
86
+ },
87
+ "violations": {
88
+ "total": 39,
89
+ "by_rule": [
90
+ { "rule_code": "C065", "count": 39, "severity": "warning" }
91
+ ]
92
+ }
93
+ }
94
+ ```
95
+
96
+ ### 🔗 **Related Issues**
97
+ - Fixed semantic engine requiring explicit CLI options for ts-morph
98
+ - Enhanced TSX file visibility in console output
99
+ - Improved default configuration for heuristic engine
100
+
101
+ ---
102
+
103
+ ## �🔧 **v1.3.9 - File Targeting Regression Fix (October 2, 2025)**
6
104
 
7
105
  **Release Date**: October 2, 2025
8
106
  **Type**: Bug Fix
package/README.md CHANGED
@@ -17,6 +17,8 @@ Sun Lint is a universal coding standards checker providing comprehensive code qu
17
17
  - ✅ **Zero Config**: Works immediately after `npm install`
18
18
  - ✅ **CI/CD Ready**: Baseline comparison, fail-on-new-violations, timeout protection
19
19
  - ✅ **Advanced File Targeting**: Include/exclude patterns, language filtering
20
+ - ✅ **Quality Scoring System**: Automated quality score (0-100) with grade (A+ to F)
21
+ - ✅ **Summary Reports**: JSON format for CI/CD dashboards and management reports
20
22
 
21
23
  ### **🏗️ Architecture**
22
24
 
@@ -64,8 +66,68 @@ sunlint --rules=C010,C006 --eslint-integration --input=src
64
66
 
65
67
  # Git integration
66
68
  sunlint --all --changed-files
69
+
70
+ # Quality scoring and summary report (for CI/CD)
71
+ sunlint --all --input=src --output-summary=quality-report.json
72
+ ```
73
+
74
+ ## 📊 **Quality Scoring & Summary Reports** 🆕
75
+
76
+ Generate comprehensive quality reports with automated scoring for CI/CD integration and management dashboards.
77
+
78
+ ### **Features**
79
+ - **Quality Score (0-100)**: Calculated based on violations, LOC, and rules checked
80
+ - **Grade System**: A+ to F grade for easy interpretation
81
+ - **JSON Format**: Ready for CI/CD pipelines and dashboard integration
82
+ - **Git Integration**: Automatically includes repository info, branch, and commit hash
83
+ - **Metrics**: Violations per KLOC, errors/warnings count, LOC analysis
84
+
85
+ ### **Quick Usage**
86
+ ```bash
87
+ # Generate summary report
88
+ sunlint --all --input=src --output-summary=quality.json
89
+
90
+ # Both detailed and summary reports
91
+ sunlint --all --input=src --output=detailed.txt --output-summary=quality.json
92
+
93
+ # In GitHub Actions (auto-detects git info)
94
+ sunlint --all --input=src --output-summary=quality.json
67
95
  ```
68
96
 
97
+ ### **Example Output**
98
+ ```json
99
+ {
100
+ "quality": {
101
+ "score": 92.6,
102
+ "grade": "A",
103
+ "metrics": {
104
+ "errors": 0,
105
+ "warnings": 39,
106
+ "linesOfCode": 4954,
107
+ "violationsPerKLOC": 7.9
108
+ }
109
+ },
110
+ "violations": {
111
+ "total": 39,
112
+ "by_rule": [
113
+ { "rule_code": "C065", "count": 39 }
114
+ ]
115
+ },
116
+ "repository": {
117
+ "repository_url": "https://github.com/org/repo",
118
+ "branch": "main",
119
+ "commit_hash": "abc123"
120
+ }
121
+ }
122
+ ```
123
+
124
+ **📚 Full Documentation**: See [docs/QUALITY_SCORING_GUIDE.md](docs/QUALITY_SCORING_GUIDE.md) for:
125
+ - Scoring formula and interpretation
126
+ - CI/CD integration examples (GitHub Actions, GitLab CI)
127
+ - Dashboard integration
128
+ - Quality gate setup
129
+ - Trending analysis
130
+
69
131
  ## 📦 **Installation**
70
132
 
71
133
  ### **Global Installation (Recommended)**
@@ -85,7 +85,8 @@ class CliActionHandler {
85
85
  // Output results
86
86
  await this.outputService.outputResults(results, this.options, {
87
87
  duration,
88
- rulesRun: rulesToRun.length
88
+ rulesRun: rulesToRun.length,
89
+ rulesChecked: rulesToRun.length
89
90
  });
90
91
 
91
92
  // Exit with appropriate code
@@ -295,6 +296,34 @@ class CliActionHandler {
295
296
  }
296
297
  }
297
298
 
299
+ // Validate upload-report option - requires output-summary to be set
300
+ if (this.options.uploadReport !== undefined) {
301
+ if (!this.options.outputSummary) {
302
+ throw new Error(
303
+ chalk.red(`❌ --upload-report requires --output-summary to be specified\n`) +
304
+ chalk.gray('Example: sunlint --all --output-summary=report.json --upload-report --input=src')
305
+ );
306
+ }
307
+
308
+ // Set default URL if no URL provided (when uploadReport is true)
309
+ if (typeof this.options.uploadReport === 'boolean' || !this.options.uploadReport) {
310
+ this.options.uploadReport = 'https://coding-standards-report.sun-asterisk.vn/api/reports';
311
+ if (this.options.verbose) {
312
+ console.log(chalk.gray(`ℹ️ Using default upload URL: ${this.options.uploadReport}`));
313
+ }
314
+ }
315
+
316
+ // Basic URL validation
317
+ try {
318
+ new URL(this.options.uploadReport);
319
+ } catch (error) {
320
+ throw new Error(
321
+ chalk.red(`❌ Invalid upload URL format: ${this.options.uploadReport}\n`) +
322
+ chalk.gray('Please provide a valid HTTP/HTTPS URL')
323
+ );
324
+ }
325
+ }
326
+
298
327
  // Priority 1: CLI --input parameter (highest priority)
299
328
  if (this.options.input) {
300
329
  // Validate CLI input path exists
@@ -34,6 +34,8 @@ function createCliProgram() {
34
34
  .option('-i, --input <path>', 'Input file or directory to analyze (REQUIRED)')
35
35
  .option('-f, --format <format>', 'Output format (eslint,json,summary,table)', 'eslint')
36
36
  .option('-o, --output <file>', 'Output file path')
37
+ .option('--output-summary <file>', 'Output summary report file path (JSON format for CI/CD)')
38
+ .option('--upload-report [url]', 'Upload summary report to API endpoint after analysis (default: Sun* Coding Standards API)')
37
39
  .option('--config <file>', 'Configuration file path (default: auto-discover)');
38
40
 
39
41
  // File targeting options
@@ -129,6 +131,8 @@ CI/CD Integration:
129
131
  $ sunlint --all --changed-files --diff-base=origin/main --fail-on-new-violations
130
132
  $ sunlint --all --staged-files --format=summary
131
133
  $ sunlint --all --pr-mode --diff-base=origin/main
134
+ $ sunlint --all --output-summary=report.json --upload-report
135
+ $ sunlint --all --output-summary=report.json --upload-report=https://custom-api.com/reports
132
136
 
133
137
  ESLint Integration:
134
138
  $ sunlint --typescript --eslint-integration --input=src
@@ -1,14 +1,20 @@
1
1
  /**
2
2
  * Output Service
3
- * Following Rule C005: Single responsibility - handle output operations
4
3
  */
5
4
 
6
5
  const fs = require('fs');
7
6
  const path = require('path');
8
7
  const chalk = require('chalk');
8
+ const ScoringService = require('./scoring-service');
9
+ const SummaryReportService = require('./summary-report-service');
10
+ const UploadService = require('./upload-service');
9
11
 
10
12
  class OutputService {
11
- constructor() {}
13
+ constructor() {
14
+ this.scoringService = new ScoringService();
15
+ this.summaryReportService = new SummaryReportService();
16
+ this.uploadService = new UploadService();
17
+ }
12
18
 
13
19
  async outputResults(results, options, metadata = {}) {
14
20
  // Generate report based on format
@@ -27,12 +33,91 @@ class OutputService {
27
33
  console.log(chalk.green(`📄 Report saved to: ${options.output}`));
28
34
  }
29
35
 
36
+ // Summary report output (new feature for CI/CD)
37
+ if (options.outputSummary) {
38
+ const summaryReport = this.generateAndSaveSummaryReport(
39
+ report.violations,
40
+ results,
41
+ options,
42
+ metadata
43
+ );
44
+ console.log(chalk.green(`📊 Summary report saved to: ${options.outputSummary}`));
45
+
46
+ // Display summary in console
47
+ if (!options.quiet) {
48
+ console.log(this.summaryReportService.formatTextSummary(summaryReport));
49
+ }
50
+
51
+ // Upload report if upload URL is provided
52
+ if (options.uploadReport) {
53
+ await this.handleUploadReport(options.outputSummary, options.uploadReport, options);
54
+ }
55
+ }
56
+
30
57
  // Summary (skip for JSON format)
31
58
  if (!options.quiet && options.format !== 'json') {
32
59
  console.log(report.summary);
33
60
  }
34
61
  }
35
62
 
63
+ generateAndSaveSummaryReport(violations, results, options, metadata) {
64
+ const totalFiles = results.filesAnalyzed || results.summary?.totalFiles || results.totalFiles || results.fileCount || 0;
65
+
66
+ // Calculate LOC
67
+ let loc = 0;
68
+ if (options.input) {
69
+ const inputPaths = Array.isArray(options.input) ? options.input : [options.input];
70
+ for (const inputPath of inputPaths) {
71
+ if (fs.existsSync(inputPath)) {
72
+ const stat = fs.statSync(inputPath);
73
+ if (stat.isDirectory()) {
74
+ loc += this.scoringService.calculateDirectoryLOC(inputPath);
75
+ } else {
76
+ loc += this.scoringService.calculateLOC([inputPath]);
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ // Count violations
83
+ const errorCount = violations.filter(v => v.severity === 'error').length;
84
+ const warningCount = violations.filter(v => v.severity === 'warning').length;
85
+
86
+ // Get number of rules checked - use metadata first, then parse from options
87
+ let rulesChecked = metadata.rulesChecked;
88
+ if (!rulesChecked && options.rule) {
89
+ rulesChecked = typeof options.rule === 'string'
90
+ ? options.rule.split(',').filter(r => r.trim()).length
91
+ : Array.isArray(options.rule)
92
+ ? options.rule.length
93
+ : 1;
94
+ }
95
+ rulesChecked = rulesChecked || 1;
96
+
97
+ // Calculate score
98
+ const scoringSummary = this.scoringService.generateScoringSummary({
99
+ errorCount,
100
+ warningCount,
101
+ rulesChecked,
102
+ loc
103
+ });
104
+
105
+ // Generate and save summary report
106
+ const summaryReport = this.summaryReportService.saveSummaryReport(
107
+ violations,
108
+ scoringSummary,
109
+ options.outputSummary,
110
+ {
111
+ cwd: process.cwd(),
112
+ filesAnalyzed: totalFiles,
113
+ duration: metadata.duration,
114
+ version: metadata.version || '1.3.12'
115
+ }
116
+ );
117
+
118
+ return summaryReport;
119
+ }
120
+
36
121
  generateReport(results, metadata, options = {}) {
37
122
  const allViolations = [];
38
123
  let totalFiles = results.filesAnalyzed || results.summary?.totalFiles || results.totalFiles || results.fileCount || 0;
@@ -98,7 +183,8 @@ class OutputService {
98
183
  return {
99
184
  formatted,
100
185
  summary,
101
- raw
186
+ raw,
187
+ violations: allViolations // Add violations for summary report
102
188
  };
103
189
  }
104
190
 
@@ -244,6 +330,77 @@ class OutputService {
244
330
 
245
331
  return jsonResults;
246
332
  }
333
+
334
+ /**
335
+ * Handle uploading report to API endpoint
336
+ */
337
+ async handleUploadReport(filePath, apiUrl, options = {}) {
338
+ try {
339
+ if (!filePath) {
340
+ throw new Error('Summary report file path is required for upload');
341
+ }
342
+
343
+ if (!apiUrl) {
344
+ throw new Error('API URL is required for upload');
345
+ }
346
+
347
+ // Check if curl is available
348
+ if (!this.uploadService.checkCurlAvailability()) {
349
+ console.warn(chalk.yellow('⚠️ curl is not available. Skipping report upload.'));
350
+ return;
351
+ }
352
+
353
+ // Validate API endpoint if not in quiet mode
354
+ if (!options.quiet) {
355
+ console.log(chalk.blue('🔍 Checking API endpoint accessibility...'));
356
+ const endpointCheck = await this.uploadService.validateApiEndpoint(apiUrl, {
357
+ timeout: options.uploadTimeout || 10
358
+ });
359
+
360
+ if (!endpointCheck.accessible) {
361
+ console.warn(chalk.yellow(`⚠️ API endpoint may not be accessible: ${endpointCheck.error}`));
362
+ console.warn(chalk.yellow('Proceeding with upload attempt...'));
363
+ }
364
+ }
365
+
366
+ // Upload the report
367
+ const uploadResult = await this.uploadService.uploadReportToApi(filePath, apiUrl, {
368
+ timeout: options.uploadTimeout || 30,
369
+ verbose: options.verbose,
370
+ debug: options.debug
371
+ });
372
+
373
+ if (uploadResult.success) {
374
+ if (!options.quiet) {
375
+ console.log(chalk.green(`✅ Report successfully uploaded to: ${apiUrl}`));
376
+ if (uploadResult.statusCode) {
377
+ console.log(chalk.green(`📡 HTTP Status: ${uploadResult.statusCode}`));
378
+ }
379
+ }
380
+ } else {
381
+ console.warn(chalk.yellow(`⚠️ Failed to upload report: ${uploadResult.error}`));
382
+
383
+ if (options.verbose && uploadResult.errorContext) {
384
+ console.warn('Upload error details:', uploadResult.errorContext);
385
+ }
386
+ }
387
+
388
+ return uploadResult;
389
+
390
+ } catch (error) {
391
+ console.error(chalk.red('❌ Upload process failed:'), error.message);
392
+
393
+ if (options.debug) {
394
+ console.error('Upload error stack:', error.stack);
395
+ }
396
+
397
+ // Don't throw error to prevent interrupting the main analysis flow
398
+ return {
399
+ success: false,
400
+ error: error.message
401
+ };
402
+ }
403
+ }
247
404
  }
248
405
 
249
406
  module.exports = OutputService;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Scoring Service
3
+ * Calculate quality score based on violations, rules, and LOC
4
+ * Following Rule C005: Single responsibility - handle scoring operations
5
+ */
6
+
7
+ const { execSync } = require('child_process');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ class ScoringService {
12
+ constructor() {
13
+ // Scoring weights
14
+ this.weights = {
15
+ errorPenalty: 5, // Each error reduces score by 5 points
16
+ warningPenalty: 1, // Each warning reduces score by 1 point
17
+ ruleBonus: 0.5, // Bonus for each rule checked
18
+ locFactor: 0.001 // Factor for normalizing by LOC
19
+ };
20
+ }
21
+
22
+ /**
23
+ * Calculate quality score
24
+ * Score formula: 100 - (errorCount * 5 + warningCount * 1) * (1000 / LOC) + (rulesChecked * 0.5)
25
+ *
26
+ * @param {Object} params
27
+ * @param {number} params.errorCount - Number of errors found
28
+ * @param {number} params.warningCount - Number of warnings found
29
+ * @param {number} params.rulesChecked - Number of rules checked
30
+ * @param {number} params.loc - Total lines of code
31
+ * @returns {number} Score between 0-100
32
+ */
33
+ calculateScore({ errorCount, warningCount, rulesChecked, loc }) {
34
+ // Base score starts at 100
35
+ let score = 100;
36
+
37
+ // Calculate violations penalty
38
+ const totalPenalty = (errorCount * this.weights.errorPenalty) +
39
+ (warningCount * this.weights.warningPenalty);
40
+
41
+ // Normalize penalty by LOC (per 1000 lines)
42
+ const locNormalization = loc > 0 ? 1000 / loc : 1;
43
+ const normalizedPenalty = totalPenalty * locNormalization;
44
+
45
+ // Apply penalty
46
+ score -= normalizedPenalty;
47
+
48
+ // Add bonus for rules checked (more rules = more thorough check)
49
+ const ruleBonus = Math.min(rulesChecked * this.weights.ruleBonus, 10); // Max 10 points bonus
50
+ score += ruleBonus;
51
+
52
+ // Ensure score is between 0-100
53
+ score = Math.max(0, Math.min(100, score));
54
+
55
+ // Round to 1 decimal place
56
+ return Math.round(score * 10) / 10;
57
+ }
58
+
59
+ /**
60
+ * Calculate Lines of Code (LOC) for given files
61
+ * @param {string[]} files - Array of file paths
62
+ * @returns {number} Total lines of code
63
+ */
64
+ calculateLOC(files) {
65
+ let totalLines = 0;
66
+
67
+ for (const file of files) {
68
+ try {
69
+ if (fs.existsSync(file)) {
70
+ const content = fs.readFileSync(file, 'utf8');
71
+ const lines = content.split('\n').length;
72
+ totalLines += lines;
73
+ }
74
+ } catch (error) {
75
+ // Ignore files that can't be read
76
+ console.warn(`Warning: Could not read file ${file}`);
77
+ }
78
+ }
79
+
80
+ return totalLines;
81
+ }
82
+
83
+ /**
84
+ * Calculate LOC for a directory
85
+ * @param {string} directory - Directory path
86
+ * @param {string[]} extensions - File extensions to include (e.g., ['.ts', '.tsx', '.js', '.jsx'])
87
+ * @returns {number} Total lines of code
88
+ */
89
+ calculateDirectoryLOC(directory, extensions = ['.ts', '.tsx', '.js', '.jsx']) {
90
+ let totalLines = 0;
91
+
92
+ const processDirectory = (dir) => {
93
+ try {
94
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
95
+
96
+ for (const entry of entries) {
97
+ const fullPath = path.join(dir, entry.name);
98
+
99
+ if (entry.isDirectory()) {
100
+ // Skip common directories
101
+ if (!['node_modules', '.git', 'dist', 'build', 'coverage'].includes(entry.name)) {
102
+ processDirectory(fullPath);
103
+ }
104
+ } else if (entry.isFile()) {
105
+ const ext = path.extname(entry.name);
106
+ if (extensions.includes(ext)) {
107
+ try {
108
+ const content = fs.readFileSync(fullPath, 'utf8');
109
+ totalLines += content.split('\n').length;
110
+ } catch (error) {
111
+ // Ignore files that can't be read
112
+ }
113
+ }
114
+ }
115
+ }
116
+ } catch (error) {
117
+ // Ignore directories that can't be read
118
+ }
119
+ };
120
+
121
+ if (fs.existsSync(directory)) {
122
+ processDirectory(directory);
123
+ }
124
+
125
+ return totalLines;
126
+ }
127
+
128
+ /**
129
+ * Get score grade based on score value
130
+ * @param {number} score - Score value (0-100)
131
+ * @returns {string} Grade (A+, A, B+, B, C+, C, D, F)
132
+ */
133
+ getGrade(score) {
134
+ if (score >= 95) return 'A+';
135
+ if (score >= 90) return 'A';
136
+ if (score >= 85) return 'B+';
137
+ if (score >= 80) return 'B';
138
+ if (score >= 75) return 'C+';
139
+ if (score >= 70) return 'C';
140
+ if (score >= 60) return 'D';
141
+ return 'F';
142
+ }
143
+
144
+ /**
145
+ * Generate scoring summary
146
+ * @param {Object} params
147
+ * @returns {Object} Scoring summary with score, grade, and metrics
148
+ */
149
+ generateScoringSummary(params) {
150
+ const score = this.calculateScore(params);
151
+ const grade = this.getGrade(score);
152
+
153
+ return {
154
+ score,
155
+ grade,
156
+ metrics: {
157
+ errors: params.errorCount,
158
+ warnings: params.warningCount,
159
+ rulesChecked: params.rulesChecked,
160
+ linesOfCode: params.loc,
161
+ violationsPerKLOC: params.loc > 0
162
+ ? Math.round(((params.errorCount + params.warningCount) / params.loc * 1000) * 10) / 10
163
+ : 0
164
+ }
165
+ };
166
+ }
167
+ }
168
+
169
+ module.exports = ScoringService;