@sun-asterisk/sunlint 1.3.5 → 1.3.7

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,6 +2,73 @@
2
2
 
3
3
  ---
4
4
 
5
+ ## � **v1.3.7 - File Count Reporting & Performance Fixes (September 11, 2025)**
6
+
7
+ **Release Date**: September 11, 2025
8
+ **Type**: Bug Fix & Enhancement
9
+ **Branch**: `fix.sunlint.report`
10
+
11
+ ### 🐛 **Critical Bug Fixes**
12
+ - **FIXED**: File count reporting accuracy in summary
13
+ - **Issue**: Summary showed incorrect file counts when performance filtering applied
14
+ - **Before**: `Files loaded: 1322` but summary `Files: 1000` (misleading)
15
+ - **After**: Summary accurately reflects files actually analyzed
16
+ - **FIXED**: File count multiplication in batch processing
17
+ - **Issue**: Multiple batches incorrectly accumulated file counts
18
+ - **Before**: 1322 files → reported as 3000 files in batched analysis
19
+ - **After**: Consistent file count regardless of batch strategy
20
+
21
+ ### ⚡ **Performance Enhancements**
22
+ - **ENHANCED**: `--max-files=-1` unlimited file processing
23
+ - **Issue**: `-1` flag was ignored, still limited to 1000 files
24
+ - **Solution**: Proper unlimited file processing support
25
+ - **Usage**: `sunlint --max-files=-1` now analyzes all files without limits
26
+
27
+ ### 🎯 **Rule Improvements**
28
+ - **ENHANCED**: S057 UTC Logging rule precision (100% accuracy)
29
+ - Fixed false positive detection for `pino.stdTimeFunctions.isoTime`
30
+ - Added timezone indicator support: `'Z'`, `"Z"`, `+00:00`, `.l'Z'`
31
+ - Enhanced config variable tracing for complex logging setups
32
+ - Cleaned up test fixtures and moved to proper location
33
+
34
+ ### 📊 **Validation Results**
35
+ - **File Processing**: `--max-files=-1` → 1322 files analyzed ✅
36
+ - **Limited Analysis**: `--max-files=500` → 500 files analyzed ✅
37
+ - **Batch Analysis**: Multi-rule analysis maintains accurate counts ✅
38
+ - **S057 Precision**: 0 false positives on real projects ✅
39
+
40
+ ---
41
+
42
+ ## �🔧 **v1.3.6 - C067 False Positive Reduction (September 8, 2025)**
43
+
44
+ **Release Date**: September 8, 2025
45
+ **Type**: Bug Fix & Improvement
46
+
47
+ ### 🐛 **Bug Fixes**
48
+ - **FIXED**: C067 "no hardcoded config" rule - Massive false positive reduction
49
+ - **replace-fe**: From 296 → 2 violations (-99.3%)
50
+ - **replace-be**: From 171 → 3 violations (-98.2%)
51
+ - **jmb-app-be**: From 121 → 5 violations (-95.9%)
52
+ - **mdx-cycle-hack**: From 8 → 6 violations (-25%)
53
+
54
+ ### 🔧 **Technical Improvements**
55
+ - **ENHANCED**: C067 analyzer logic improvements
56
+ - Skip dummy/test files and entity files completely
57
+ - Exclude field mapping objects and ORM configurations
58
+ - Skip database constraint names (primaryKeyConstraintName, etc.)
59
+ - Focus only on truly environment-dependent configurations
60
+ - Exclude business logic constants and UI field mappings
61
+ - **IMPROVED**: Rule precision - Only flag real environment config issues
62
+ - API endpoints, AWS service URLs, application keys
63
+ - Credential values and connection strings
64
+ - Environment-dependent timeouts and ports
65
+
66
+ ### 📊 **Performance**
67
+ - **OPTIMIZED**: Reduced analysis noise by 95%+ on large projects
68
+ - **ENHANCED**: Better developer experience with fewer false alarms
69
+
70
+ ---
71
+
5
72
  ## 🔧 **v1.3.5 - Preset System Refactor (September 8, 2025)**
6
73
 
7
74
  **Release Date**: September 8, 2025
@@ -61,6 +61,11 @@ module.exports = {
61
61
  methods: ['regex'],
62
62
  accuracy: { regex: 90 }
63
63
  },
64
+ 'C070': {
65
+ reason: 'Real-time dependencies detection via timer/sleep patterns',
66
+ methods: ['regex'],
67
+ accuracy: { regex: 95 }
68
+ },
64
69
  'S001': {
65
70
  reason: 'Security patterns are often string-based',
66
71
  methods: ['regex', 'ast'],
@@ -1107,28 +1107,36 @@
1107
1107
  "tags": ["security", "rest", "content-type"]
1108
1108
  },
1109
1109
  "S057": {
1110
- "name": "UTC Logging",
1111
- "description": "Enforce UTC usage in time formatting and logging",
1110
+ "name": "Log with UTC Timestamps",
1111
+ "description": "Ensure all logs use synchronized UTC time with ISO 8601/RFC3339 format to avoid timezone discrepancies across systems",
1112
1112
  "category": "security",
1113
1113
  "severity": "warning",
1114
1114
  "languages": ["typescript", "javascript"],
1115
- "analyzer": "eslint",
1116
- "eslintRule": "custom/typescript_s057",
1115
+ "analyzer": "./rules/security/S057_utc_logging/analyzer.js",
1116
+ "config": "./rules/security/S057_utc_logging/config.json",
1117
1117
  "version": "1.0.0",
1118
1118
  "status": "stable",
1119
- "tags": ["security", "logging", "timezone"]
1119
+ "tags": ["security", "logging", "timezone", "utc"],
1120
+ "engineMappings": {
1121
+ "eslint": ["custom/typescript_s057"],
1122
+ "heuristic": ["./rules/security/S057_utc_logging/analyzer.js"]
1123
+ }
1120
1124
  },
1121
1125
  "S058": {
1122
- "name": "No SSRF",
1123
- "description": "Detect SSRF vulnerabilities via unvalidated user-controlled URLs",
1126
+ "name": "No SSRF (Server-Side Request Forgery)",
1127
+ "description": "Prevent SSRF attacks by validating URLs from user input before making HTTP requests",
1124
1128
  "category": "security",
1125
1129
  "severity": "error",
1126
1130
  "languages": ["typescript", "javascript"],
1127
- "analyzer": "eslint",
1128
- "eslintRule": "custom/typescript_s058",
1131
+ "analyzer": "./rules/security/S058_no_ssrf/analyzer.js",
1132
+ "config": "./rules/security/S058_no_ssrf/config.json",
1129
1133
  "version": "1.0.0",
1130
1134
  "status": "stable",
1131
- "tags": ["security", "ssrf", "url-validation"]
1135
+ "tags": ["security", "ssrf", "url-validation", "http-requests"],
1136
+ "engineMappings": {
1137
+ "heuristic": ["./rules/security/S058_no_ssrf/analyzer.js"],
1138
+ "eslint": ["custom/typescript_s058"]
1139
+ }
1132
1140
  },
1133
1141
  "C002": {
1134
1142
  "id": "C002",
@@ -1357,6 +1365,29 @@
1357
1365
  "heuristic": ["rules/common/C067_no_hardcoded_config/analyzer.js"]
1358
1366
  }
1359
1367
  },
1368
+ "C070": {
1369
+ "name": "No Real Time Tests",
1370
+ "description": "Tests should not depend on real time delays or sleeps. Use fake timers, clock injection, or condition-based waits to improve test reliability and speed.",
1371
+ "category": "testing",
1372
+ "severity": "error",
1373
+ "languages": ["typescript", "javascript"],
1374
+ "analyzer": "../rules/common/C070_no_real_time_tests/regex-analyzer.js",
1375
+ "config": "../rules/common/C070_no_real_time_tests/config.json",
1376
+ "version": "1.0.0",
1377
+ "status": "stable",
1378
+ "tags": ["testing", "flaky-tests", "timing", "fake-timers", "reliability"],
1379
+ "strategy": {
1380
+ "preferred": "ast",
1381
+ "fallbacks": ["regex"],
1382
+ "accuracy": {
1383
+ "ast": 95,
1384
+ "regex": 88
1385
+ }
1386
+ },
1387
+ "engineMappings": {
1388
+ "heuristic": ["../rules/common/C070_no_real_time_tests/regex-analyzer.js"]
1389
+ }
1390
+ },
1360
1391
  "C072": {
1361
1392
  "id": "C072",
1362
1393
  "name": "Single Test Behavior",
@@ -1377,6 +1408,29 @@
1377
1408
  "accuracy": {}
1378
1409
  }
1379
1410
  },
1411
+ "C073": {
1412
+ "id": "C073",
1413
+ "name": "Validate Required Configuration on Startup",
1414
+ "description": "C073 - Validate mandatory configuration at startup and fail fast on invalid/missing values",
1415
+ "category": "configuration",
1416
+ "severity": "error",
1417
+ "languages": ["typescript", "javascript", "java", "go"],
1418
+ "version": "1.0.0",
1419
+ "status": "stable",
1420
+ "tags": ["configuration", "validation", "startup", "fail-fast"],
1421
+ "engineMappings": {
1422
+ "heuristic": ["rules/common/C073_validate_required_config_on_startup/analyzer.js"],
1423
+ "semantic": ["rules/common/C073_validate_required_config_on_startup/symbol-based-analyzer.js"]
1424
+ },
1425
+ "strategy": {
1426
+ "preferred": "semantic",
1427
+ "fallbacks": ["heuristic"],
1428
+ "accuracy": {
1429
+ "semantic": 0.9,
1430
+ "heuristic": 0.7
1431
+ }
1432
+ }
1433
+ },
1380
1434
  "C075": {
1381
1435
  "id": "C075",
1382
1436
  "name": "Rule C075",
@@ -1784,6 +1838,7 @@
1784
1838
  "C048",
1785
1839
  "C052",
1786
1840
  "C072",
1841
+ "C073",
1787
1842
  "C075",
1788
1843
  "T002",
1789
1844
  "T003",
@@ -264,7 +264,7 @@ class AnalysisOrchestrator {
264
264
  }
265
265
 
266
266
  // Merge results and add performance metrics
267
- const mergedResults = this.mergeEngineResults(results, options);
267
+ const mergedResults = this.mergeEngineResults(results, options, optimizedFiles.length);
268
268
  mergedResults.performance = performanceMetrics;
269
269
 
270
270
  return mergedResults;
@@ -523,7 +523,7 @@ class AnalysisOrchestrator {
523
523
  * @param {Object} options - Analysis options
524
524
  * @returns {Object} Merged results
525
525
  */
526
- mergeEngineResults(engineResults, options) {
526
+ mergeEngineResults(engineResults, options, actualFilesCount = 0) {
527
527
  const mergedResults = {
528
528
  results: [],
529
529
  summary: {
@@ -568,15 +568,19 @@ class AnalysisOrchestrator {
568
568
  // Accumulate engine statistics across batches
569
569
  mergedResults.summary.engines[engineName].rules.push(...(engineResult.rules || []));
570
570
  mergedResults.summary.engines[engineName].violations += violationCount;
571
- mergedResults.summary.engines[engineName].files += engineResult.filesAnalyzed || 0;
571
+ // Don't accumulate filesAnalyzed for each batch - use actual unique file count
572
+ if (!mergedResults.summary.engines[engineName].filesSet) {
573
+ mergedResults.summary.engines[engineName].files = actualFilesCount;
574
+ mergedResults.summary.engines[engineName].filesSet = true;
575
+ }
572
576
  mergedResults.summary.engines[engineName].batches += 1;
573
577
 
574
578
  mergedResults.summary.totalViolations += violationCount;
575
- mergedResults.summary.totalFiles += engineResult.filesAnalyzed || 0;
576
579
  }
577
580
 
578
- // Update unique engine count
581
+ // Update unique engine count and correct total files count
579
582
  mergedResults.summary.totalEngines = uniqueEngines.size;
583
+ mergedResults.summary.totalFiles = actualFilesCount;
580
584
 
581
585
  return mergedResults;
582
586
  }
@@ -26,6 +26,12 @@ class PerformanceOptimizer {
26
26
  ...DEFAULT_PERFORMANCE,
27
27
  ...config
28
28
  };
29
+
30
+ // Override maxTotalFiles if provided in config
31
+ if (config.maxFiles !== undefined) {
32
+ this.fileSizeLimits.maxTotalFiles = config.maxFiles;
33
+ }
34
+
29
35
  this.initialized = true;
30
36
  }
31
37
 
@@ -106,8 +112,8 @@ class PerformanceOptimizer {
106
112
  break;
107
113
  }
108
114
 
109
- // Check file count limit
110
- if (filtered.length >= this.fileSizeLimits.maxTotalFiles) {
115
+ // Check file count limit (skip if unlimited -1)
116
+ if (this.fileSizeLimits.maxTotalFiles > 0 && filtered.length >= this.fileSizeLimits.maxTotalFiles) {
111
117
  if (this.config.verbose) {
112
118
  console.log(`⚠️ Reached file count limit: ${this.fileSizeLimits.maxTotalFiles} files`);
113
119
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sunlint",
3
- "version": "1.3.5",
3
+ "version": "1.3.7",
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": {
@@ -246,7 +246,7 @@ class C067SymbolBasedAnalyzer {
246
246
  }
247
247
 
248
248
  isConfigOrTestFile(filePath) {
249
- // Skip config files themselves and test files (NOT dummy files used in production)
249
+ // Skip config files themselves and test files, including dummy/test data files
250
250
  const fileName = filePath.toLowerCase();
251
251
  const configPatterns = [
252
252
  /config\.(ts|js|json)$/,
@@ -264,9 +264,12 @@ class C067SymbolBasedAnalyzer {
264
264
  /\/test\//,
265
265
  /\/tests\//,
266
266
  /\.stories\.(ts|tsx|js|jsx)$/,
267
- /\.mock\.(ts|tsx|js|jsx)$/
268
- // NOTE: Deliberately NOT including /dummy/ because dummy files
269
- // in production code often contain hardcoded config that should be flagged
267
+ /\.mock\.(ts|tsx|js|jsx)$/,
268
+ /\/dummy\//, // Skip dummy data files
269
+ /dummy\.(ts|js)$/, // Skip dummy files
270
+ /test-fixtures\//, // Skip test fixture files
271
+ /\.fixture\.(ts|js)$/, // Skip fixture files
272
+ /entity\.(ts|js)$/ // Skip entity/ORM files (contain DB constraints)
270
273
  ];
271
274
 
272
275
  return configPatterns.some(pattern => pattern.test(fileName)) ||
@@ -451,66 +454,97 @@ class C067SymbolBasedAnalyzer {
451
454
  const propertyName = nameNode.getText();
452
455
  const position = sourceFile.getLineAndColumnAtPos(node.getStart());
453
456
 
454
- // Skip field mapping objects
457
+ // Skip ALL field mapping objects and ORM/database entity configurations
455
458
  const ancestorObj = node.getParent();
456
459
  if (ancestorObj && Node.isObjectLiteralExpression(ancestorObj)) {
457
460
  const objParent = ancestorObj.getParent();
458
461
  if (objParent && Node.isVariableDeclaration(objParent)) {
459
462
  const varName = objParent.getName();
460
- if (/mapping|map|field|column|decode/i.test(varName)) {
461
- return null; // Skip field mapping objects
463
+ // Skip field mappings, database schemas, etc.
464
+ if (/mapping|map|field|column|decode|schema|entity|constraint|table/i.test(varName)) {
465
+ return null;
462
466
  }
463
467
  }
468
+
469
+ // Check if this looks like a table column definition or field mapping
470
+ const objText = ancestorObj.getText();
471
+ if (/primaryKeyConstraintName|foreignKeyConstraintName|key.*may contain/i.test(objText)) {
472
+ return null; // Skip database constraint definitions
473
+ }
464
474
  }
465
475
 
466
- // Skip common business logic properties that aren't environment-dependent
476
+ // Skip properties that are clearly field mappings or business data
467
477
  const businessLogicProperties = [
468
- 'endpoint', 'path', 'route', // API routing
469
- 'limit', 'pageSize', 'batchSize', // Pagination (usually business logic)
470
- 'retry', 'retries', 'maxRetries', // Retry logic (usually business logic)
471
- 'count', 'max', 'min' // Common limits
478
+ // Field mappings
479
+ 'key', 'field', 'dataKey', 'valueKey', 'labelKey', 'sortKey',
480
+ // Business logic
481
+ 'endpoint', 'path', 'route', 'method',
482
+ 'limit', 'pageSize', 'batchSize', 'maxResults',
483
+ 'retry', 'retries', 'maxRetries', 'attempts',
484
+ 'count', 'max', 'min', 'size', 'length',
485
+ // UI properties
486
+ 'className', 'style', 'disabled', 'readonly',
487
+ // Database/ORM
488
+ 'primaryKeyConstraintName', 'foreignKeyConstraintName', 'constraintName',
489
+ 'tableName', 'columnName', 'schemaName'
472
490
  ];
473
491
 
474
492
  const lowerPropertyName = propertyName.toLowerCase();
475
493
  if (businessLogicProperties.some(prop => lowerPropertyName.includes(prop))) {
476
- // Only flag if it's clearly environment-dependent
477
- let value = null;
478
- if (valueNode.getKind() === SyntaxKind.StringLiteral) {
479
- value = valueNode.getLiteralValue();
480
- // Only flag URLs or clearly environment-dependent strings
481
- if (!this.configPatterns.urls.regex.test(value) || !this.isEnvironmentDependentUrl(value)) {
482
- return null;
483
- }
484
- } else if (valueNode.getKind() === SyntaxKind.NumericLiteral) {
485
- value = valueNode.getLiteralValue();
486
- const parentContext = this.getParentContext(node);
487
- // Only flag if it's clearly environment-dependent (like ports, large timeouts)
488
- if (!this.configPatterns.environmentNumbers.isEnvironmentDependent(value, parentContext)) {
489
- return null;
490
- }
491
- }
494
+ return null; // Skip these completely
492
495
  }
493
496
 
494
- // Check if property name suggests environment-dependent configuration
495
- if (this.isEnvironmentDependentProperty(propertyName)) {
496
- let value = null;
497
+ // Only check for CLEARLY environment-dependent properties
498
+ const trulyEnvironmentDependentProps = [
499
+ 'baseurl', 'baseURL', 'host', 'hostname', 'server', 'endpoint',
500
+ 'apikey', 'api_key', 'secret_key', 'client_secret',
501
+ 'database', 'connectionstring', 'dbhost', 'dbport',
502
+ 'port', 'timeout', // Only when they have suspicious values
503
+ 'bucket', 'region', // Cloud-specific
504
+ 'clientid', 'tenantid' // OAuth-specific
505
+ ];
506
+
507
+ if (!trulyEnvironmentDependentProps.some(prop => lowerPropertyName.includes(prop))) {
508
+ return null; // Not clearly environment-dependent
509
+ }
510
+
511
+ let value = null;
512
+ let configType = null;
513
+
514
+ if (valueNode.getKind() === SyntaxKind.StringLiteral) {
515
+ value = valueNode.getLiteralValue();
497
516
 
498
- if (valueNode.getKind() === SyntaxKind.StringLiteral) {
499
- value = valueNode.getLiteralValue();
500
- } else if (valueNode.getKind() === SyntaxKind.NumericLiteral) {
501
- value = valueNode.getLiteralValue();
517
+ // Only flag URLs or clearly sensitive values
518
+ if (this.configPatterns.urls.regex.test(value) && this.isEnvironmentDependentUrl(value)) {
519
+ configType = 'url';
520
+ } else if (this.isRealCredential(value, propertyName)) {
521
+ configType = 'credential';
522
+ } else {
523
+ return null; // Skip other string values
502
524
  }
525
+ } else if (valueNode.getKind() === SyntaxKind.NumericLiteral) {
526
+ value = valueNode.getLiteralValue();
527
+ const parentContext = this.getParentContext(node);
503
528
 
504
- if (value !== null && this.looksLikeEnvironmentConfig(propertyName, value)) {
505
- return {
506
- type: 'property_config',
507
- value: value,
508
- line: position.line,
509
- column: position.column,
510
- node: node,
511
- propertyName: propertyName
512
- };
529
+ // Only flag numbers that are clearly environment-dependent
530
+ if (this.configPatterns.environmentNumbers.isEnvironmentDependent(value, parentContext)) {
531
+ configType = 'environment_config';
532
+ } else {
533
+ return null;
513
534
  }
535
+ } else {
536
+ return null; // Skip other value types
537
+ }
538
+
539
+ if (configType) {
540
+ return {
541
+ type: configType,
542
+ value: value,
543
+ line: position.line,
544
+ column: position.column,
545
+ node: node,
546
+ propertyName: propertyName
547
+ };
514
548
  }
515
549
 
516
550
  return null;