@sun-asterisk/sunlint 1.3.17 → 1.3.18

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.
@@ -547,9 +547,17 @@ class AnalysisOrchestrator {
547
547
  for (const engineResult of engineResults) {
548
548
  uniqueEngines.add(engineResult.engine);
549
549
 
550
- // Add engine-specific results
551
- if (engineResult.results) {
552
- mergedResults.results.push(...engineResult.results);
550
+ // Add engine-specific results with validation
551
+ if (engineResult.results && Array.isArray(engineResult.results)) {
552
+ // Filter out invalid entries (non-objects or config objects)
553
+ const validResults = engineResult.results.filter(result => {
554
+ if (!result || typeof result !== 'object') return false;
555
+ // Skip objects that look like metadata/config
556
+ if (result.semanticEngine || result.project || result._context) return false;
557
+ // Must have either file/filePath or be a valid result object
558
+ return result.file || result.filePath || result.violations || result.messages;
559
+ });
560
+ mergedResults.results.push(...validResults);
553
561
  }
554
562
 
555
563
  // Track engine statistics
@@ -28,9 +28,9 @@ class OutputService {
28
28
  try {
29
29
  const packageJsonPath = path.join(__dirname, '..', 'package.json');
30
30
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
31
- return packageJson.version || '1.3.16';
31
+ return packageJson.version || '1.3.18';
32
32
  } catch (error) {
33
- return '1.3.16'; // Fallback version
33
+ return '1.3.18'; // Fallback version
34
34
  }
35
35
  }
36
36
 
@@ -140,6 +140,17 @@ class OutputService {
140
140
  const allViolations = [];
141
141
  let totalFiles = results.filesAnalyzed || results.summary?.totalFiles || results.totalFiles || results.fileCount || 0;
142
142
 
143
+ // Helper function to validate violation object
144
+ const isValidViolation = (violation) => {
145
+ if (!violation || typeof violation !== 'object') return false;
146
+ // Skip config/metadata objects (have nested objects like semanticEngine, project, etc.)
147
+ if (violation.semanticEngine || violation.project || violation._context) return false;
148
+ // Must have ruleId as string
149
+ const ruleId = violation.ruleId || violation.rule;
150
+ if (!ruleId || typeof ruleId !== 'string') return false;
151
+ return true;
152
+ };
153
+
143
154
  // Collect all violations - handle both file-based and rule-based results
144
155
  if (results.results) {
145
156
  results.results.forEach(result => {
@@ -147,16 +158,20 @@ class OutputService {
147
158
  // Handle rule-based format (MultiRuleRunner)
148
159
  if (result.ruleId) {
149
160
  result.violations.forEach(violation => {
150
- allViolations.push(violation); // violation already has file path
161
+ if (isValidViolation(violation)) {
162
+ allViolations.push(violation); // violation already has file path
163
+ }
151
164
  });
152
165
  }
153
166
  // Handle file-based format (legacy)
154
167
  else {
155
168
  result.violations.forEach(violation => {
156
- allViolations.push({
157
- ...violation,
158
- file: result.filePath || result.file // Use filePath first, then file
159
- });
169
+ if (isValidViolation(violation)) {
170
+ allViolations.push({
171
+ ...violation,
172
+ file: result.filePath || result.file // Use filePath first, then file
173
+ });
174
+ }
160
175
  });
161
176
  }
162
177
  }
@@ -164,7 +179,7 @@ class OutputService {
164
179
  // Handle ESLint format (messages array)
165
180
  if (result.messages) {
166
181
  result.messages.forEach(message => {
167
- allViolations.push({
182
+ const violation = {
168
183
  file: result.filePath || message.file,
169
184
  ruleId: message.ruleId,
170
185
  severity: message.severity === 2 ? 'error' : 'warning',
@@ -172,7 +187,10 @@ class OutputService {
172
187
  line: message.line,
173
188
  column: message.column,
174
189
  source: message.source || 'eslint'
175
- });
190
+ };
191
+ if (isValidViolation(violation)) {
192
+ allViolations.push(violation);
193
+ }
176
194
  });
177
195
  }
178
196
  });
@@ -23,9 +23,9 @@ class SummaryReportService {
23
23
  try {
24
24
  const packageJsonPath = path.join(__dirname, '..', 'package.json');
25
25
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
26
- return packageJson.version || '1.3.16';
26
+ return packageJson.version || '1.3.18';
27
27
  } catch (error) {
28
- return '1.3.16'; // Fallback version
28
+ return '1.3.18'; // Fallback version
29
29
  }
30
30
  }
31
31
 
@@ -198,7 +198,25 @@ class SummaryReportService {
198
198
  // Count violations by rule
199
199
  const violationsByRule = {};
200
200
  violations.forEach(violation => {
201
- const ruleId = violation.ruleId || 'unknown';
201
+ // Validate that this is actually a violation object (not metadata/config)
202
+ // A valid violation should have ruleId/rule as string and message
203
+ if (!violation || typeof violation !== 'object') {
204
+ return; // Skip non-objects
205
+ }
206
+
207
+ // Skip objects that look like metadata/config (have nested objects like semanticEngine, project, etc.)
208
+ if (violation.semanticEngine || violation.project || violation.options) {
209
+ return; // Skip config objects
210
+ }
211
+
212
+ // Get ruleId from various possible fields
213
+ const ruleId = violation.ruleId || violation.rule || 'unknown';
214
+
215
+ // Ensure ruleId is a string (not an object)
216
+ if (typeof ruleId !== 'string') {
217
+ return; // Skip invalid ruleId
218
+ }
219
+
202
220
  if (!violationsByRule[ruleId]) {
203
221
  violationsByRule[ruleId] = {
204
222
  rule_code: ruleId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sunlint",
3
- "version": "1.3.17",
3
+ "version": "1.3.18",
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": {
@@ -47,7 +47,9 @@ class C047Analyzer {
47
47
 
48
48
  async analyze(files, language, options = {}) {
49
49
  const violations = [];
50
+ this.retryPatterns = []; // ✅ Reset once for entire analysis, not per file
50
51
 
52
+ // Phase 1: Collect all retry patterns from all files
51
53
  for (const filePath of files) {
52
54
  if (options.verbose) {
53
55
  console.log(`🔍 Running C047 analysis on ${require('path').basename(filePath)}`);
@@ -55,41 +57,65 @@ class C047Analyzer {
55
57
 
56
58
  try {
57
59
  const content = require('fs').readFileSync(filePath, 'utf8');
58
- this.retryPatterns = []; // Reset for each file
59
- const fileViolations = this.analyzeFile(content, filePath);
60
- violations.push(...fileViolations);
60
+ this.collectRetryPatterns(content, filePath);
61
61
  } catch (error) {
62
62
  console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
63
63
  }
64
64
  }
65
65
 
66
+ // ✅ Safety check: If no valid retry patterns found, return empty (no violations)
67
+ if (this.retryPatterns.length === 0) {
68
+ if (options.verbose) {
69
+ console.log('🔍 [C047] No retry patterns found - skipping');
70
+ }
71
+ return violations;
72
+ }
73
+
74
+ if (options.verbose) {
75
+ console.log(`🔍 [C047] Found ${this.retryPatterns.length} retry patterns:`);
76
+ this.retryPatterns.forEach((p, i) => {
77
+ console.log(` [${i}] ${p.filePath}:${p.line} - type: ${p.type}`);
78
+ });
79
+ }
80
+
81
+ // Phase 2: Detect duplicates across all collected patterns
82
+ const duplicateGroups = this.enhancedDuplicateDetection();
83
+
84
+ // ✅ Safety check: If no duplicate groups found, return empty (no violations)
85
+ if (duplicateGroups.length === 0) {
86
+ return violations;
87
+ }
88
+
89
+ // Phase 3: Generate violations for each file
90
+ for (const filePath of files) {
91
+ try {
92
+ const fileViolations = this.generateViolationsForFile(duplicateGroups, filePath);
93
+ violations.push(...fileViolations);
94
+ } catch (error) {
95
+ console.warn(`⚠️ Failed to generate violations for ${filePath}: ${error.message}`);
96
+ }
97
+ }
98
+
66
99
  return violations;
67
100
  }
68
-
69
- analyzeFile(content, filePath) {
70
- const violations = [];
101
+
102
+ collectRetryPatterns(content, filePath) {
71
103
  const lines = content.split('\n');
72
-
73
- // Find all retry patterns in the file
74
104
  this.findRetryPatterns(lines, filePath);
75
105
 
76
106
  // Add architectural context to patterns
77
107
  this.retryPatterns.forEach(pattern => {
78
- if (!pattern.context) {
79
- const contextLines = lines.slice(Math.max(0, pattern.line - 10), pattern.line + 10);
108
+ if (!pattern.context && pattern.filePath === filePath) {
109
+ const lineIndex = pattern.line - 1;
110
+ const contextLines = lines.slice(Math.max(0, lineIndex - 10), lineIndex + 10);
80
111
  const contextContent = contextLines.join('\n');
81
112
  pattern.context = this.analyzeArchitecturalContext(filePath, contextContent);
82
113
  }
83
114
  });
84
-
85
- // Enhanced duplicate detection with architectural intelligence
86
- const duplicateGroups = this.enhancedDuplicateDetection();
87
-
88
- // Generate violations with architectural context
89
- const enhancedViolations = this.generateEnhancedViolations(duplicateGroups, filePath);
90
- violations.push(...enhancedViolations);
91
-
92
- return violations;
115
+ }
116
+
117
+ generateViolationsForFile(duplicateGroups, filePath) {
118
+ return this.generateEnhancedViolations(duplicateGroups, filePath);
93
119
  }
94
120
 
95
121
  findRetryPatterns(lines, filePath) {
@@ -114,7 +140,8 @@ class C047Analyzer {
114
140
  this.retryPatterns.push({
115
141
  ...pattern,
116
142
  line: i + 1,
117
- column: line.indexOf(line.trim()) + 1
143
+ column: line.indexOf(line.trim()) + 1,
144
+ filePath: filePath // ✅ Store file path with pattern
118
145
  });
119
146
  }
120
147
  }
@@ -132,12 +159,26 @@ class C047Analyzer {
132
159
  continue;
133
160
  }
134
161
 
162
+ // Skip if it's just a data field assignment (not retry logic)
163
+ // e.g., "retryAttempt: payment.retryCount || 0" in logging context
164
+ if (line.includes(':') && !line.includes('const') && !line.includes('let') && !line.includes('var')) {
165
+ // This is likely an object property, not a variable declaration
166
+ continue;
167
+ }
168
+
169
+ // Skip if it's part of error logging object (common false positive)
170
+ if (contextText.includes('logger.error') || contextText.includes('console.error') ||
171
+ contextText.includes('log.error') || line.includes('error:') || line.includes('message:')) {
172
+ continue;
173
+ }
174
+
135
175
  const pattern = this.extractRetryPattern(lines, i, 'variable');
136
176
  if (pattern) {
137
177
  this.retryPatterns.push({
138
178
  ...pattern,
139
179
  line: i + 1,
140
- column: line.indexOf(line.trim()) + 1
180
+ column: line.indexOf(line.trim()) + 1,
181
+ filePath: filePath // ✅ Store file path with pattern
141
182
  });
142
183
  }
143
184
  }
@@ -159,7 +200,8 @@ class C047Analyzer {
159
200
  this.retryPatterns.push({
160
201
  ...pattern,
161
202
  line: i + 1,
162
- column: line.indexOf(line.trim()) + 1
203
+ column: line.indexOf(line.trim()) + 1,
204
+ filePath: filePath // ✅ Store file path with pattern
163
205
  });
164
206
  }
165
207
  }
@@ -205,10 +247,20 @@ class C047Analyzer {
205
247
  return false;
206
248
  }
207
249
 
250
+ // ✅ Skip object properties (data fields) - not variable declarations
251
+ // Pattern: "propertyName: value," or "propertyName: value }"
252
+ const trimmed = line.trim();
253
+ if (/^\w+\s*:\s*.+[,}]?\s*$/.test(trimmed) &&
254
+ !trimmed.startsWith('const') &&
255
+ !trimmed.startsWith('let') &&
256
+ !trimmed.startsWith('var')) {
257
+ return false;
258
+ }
259
+
208
260
  // Check for variable declarations with retry-related names that involve logic
209
261
  const declarationPatterns = [
210
- /(?:const|let|var)\s+.*(?:retry|attempt|tries|maxretries|maxattempts)/,
211
- /(?:retry|attempt|tries|maxretries|maxattempts)\s*[:=]/
262
+ /(?:const|let|var)\s+.*(?:retry|attempt|tries|maxretries|maxattempts)/
263
+ // ✅ Removed the object property pattern that causes false positives
212
264
  ];
213
265
 
214
266
  // Only consider it a retry pattern if it has logical complexity
@@ -560,25 +612,29 @@ class C047Analyzer {
560
612
  };
561
613
  }
562
614
 
563
- generateEnhancedViolations(duplicateGroups, filePath) {
615
+ generateEnhancedViolations(duplicateGroups, currentFilePath) {
564
616
  const violations = [];
565
617
 
566
618
  duplicateGroups.forEach(group => {
567
- const firstPattern = group.patterns[0];
568
-
569
- violations.push({
570
- file: filePath,
571
- line: firstPattern.line,
572
- column: firstPattern.column || 1,
573
- message: `${group.legitimacy.reason} (${group.patterns.length} similar patterns found). Consider using a centralized retry utility.`,
574
- severity: group.legitimacy.severity || 'warning',
575
- ruleId: this.ruleId,
576
- type: 'duplicate_retry_logic',
577
- duplicateCount: group.patterns.length,
578
- architecturalContext: {
579
- layers: [...new Set(group.patterns.map(p => p.context?.layer))],
580
- purposes: [...new Set(group.patterns.map(p => p.context?.purpose))],
581
- confidence: group.legitimacy.confidence
619
+ // Create violations for each pattern in the group
620
+ group.patterns.forEach(pattern => {
621
+ // Only create violation for patterns in the current file being analyzed
622
+ if (pattern.filePath === currentFilePath) {
623
+ violations.push({
624
+ file: pattern.filePath || currentFilePath,
625
+ line: pattern.line,
626
+ column: pattern.column || 1,
627
+ message: `${group.legitimacy.reason} (${group.patterns.length} similar patterns found). Consider using a centralized retry utility.`,
628
+ severity: group.legitimacy.severity || 'warning',
629
+ ruleId: this.ruleId,
630
+ type: 'duplicate_retry_logic',
631
+ duplicateCount: group.patterns.length,
632
+ architecturalContext: {
633
+ layers: [...new Set(group.patterns.map(p => p.context?.layer))],
634
+ purposes: [...new Set(group.patterns.map(p => p.context?.purpose))],
635
+ confidence: group.legitimacy.confidence
636
+ }
637
+ });
582
638
  }
583
639
  });
584
640
  });
@@ -600,6 +600,21 @@ class C047SymbolAnalyzerEnhanced {
600
600
  if (!catchClause) continue;
601
601
 
602
602
  const catchBlock = catchClause.getBlock();
603
+ const catchText = catchBlock.getText().toLowerCase();
604
+
605
+ // ✅ STRICT VALIDATION: Must have retry indicators (maxRetries, attempts, delay, backoff)
606
+ const hasRetryIndicators = /(?:maxretries|maxattempts|attempt|retry|backoff|delay|timeout)/i.test(catchText);
607
+ if (!hasRetryIndicators) {
608
+ // Skip try-catch without retry logic (e.g., just logging errors)
609
+ continue;
610
+ }
611
+
612
+ // ✅ Skip logging-only catch blocks (common false positive)
613
+ const isLoggingOnly = /(?:logger\.|console\.|log\.)(?:error|warn|info)/i.test(catchText) &&
614
+ !hasRetryIndicators;
615
+ if (isLoggingOnly) {
616
+ continue;
617
+ }
603
618
 
604
619
  // Look for retry calls in catch block
605
620
  const callExpressions = catchBlock.getDescendantsOfKind(require('ts-morph').SyntaxKind.CallExpression);
@@ -615,8 +630,8 @@ class C047SymbolAnalyzerEnhanced {
615
630
  );
616
631
  }
617
632
 
618
- // Pattern 2: Direct API retry
619
- if (this.isApiCall(callText)) {
633
+ // Pattern 2: Direct API retry (ONLY if has retry indicators)
634
+ if (this.isApiCall(callText) && hasRetryIndicators) {
620
635
  return this.createRetryPattern(
621
636
  functionName, filePath, 'exception_api_retry',
622
637
  func.getStartLineNumber(), 'try-catch with API re-call'
@@ -1,288 +0,0 @@
1
- # SunLint Constants Architecture
2
-
3
- ## Overview
4
-
5
- SunLint now uses a centralized constants sub-package located at `core/constants/` to manage all constants and configuration values. This improves code organization, maintainability, and extensibility.
6
-
7
- ## Structure
8
-
9
- ```
10
- core/
11
- constants/
12
- categories.js # Category-principle mappings and functions
13
- defaults.js # Default configurations and values
14
- engines.js # Engine capabilities and configurations
15
- rules.js # Rule-related constants and utilities
16
- index.js # Barrel export for all constants
17
- ```
18
-
19
- ## Migration Guide
20
-
21
- ### Before (Old approach)
22
- ```javascript
23
- // Scattered constants across multiple files
24
- const { getValidCategories } = require('./core/category-constants');
25
- const defaultRules = ['C006', 'C019']; // Hardcoded in various files
26
- const SUPPORTED_ENGINES = { /* scattered */ };
27
- ```
28
-
29
- ### After (New centralized approach)
30
- ```javascript
31
- // Option 1: Import specific module
32
- const { getValidCategories } = require('./core/constants/categories');
33
- const { getDefaultRuleSet } = require('./core/constants/defaults');
34
- const { SUPPORTED_ENGINES } = require('./core/constants/engines');
35
-
36
- // Option 2: Import from barrel export
37
- const {
38
- getValidCategories,
39
- getDefaultRuleSet,
40
- SUPPORTED_ENGINES
41
- } = require('./core/constants');
42
-
43
- // Option 3: Import entire module
44
- const constants = require('./core/constants');
45
- const categories = constants.getValidCategories();
46
- ```
47
-
48
- ## Modules
49
-
50
- ### 1. Categories (`core/constants/categories.js`)
51
-
52
- **Purpose**: Category-principle mappings, validation, and normalization.
53
-
54
- **Key Exports**:
55
- ```javascript
56
- // Constants
57
- SUNLINT_PRINCIPLES // Object with all principle constants
58
- CATEGORY_PRINCIPLE_MAP // Category to principle mapping
59
- CATEGORY_DESCRIPTIONS // Human-readable descriptions
60
-
61
- // Functions
62
- getValidCategories() // Get all valid categories
63
- getCategoryPrinciples(category) // Get principles for category
64
- isValidCategory(category) // Validate category
65
- normalizeCategory(category) // Normalize and validate
66
- getCategoryStats() // Get statistics
67
- ```
68
-
69
- **Example Usage**:
70
- ```javascript
71
- const { getValidCategories, normalizeCategory } = require('./core/constants/categories');
72
-
73
- const validCategories = getValidCategories();
74
- // ['security', 'quality', 'performance', ...]
75
-
76
- const normalized = normalizeCategory('QUALITY');
77
- // 'quality'
78
- ```
79
-
80
- ### 2. Defaults (`core/constants/defaults.js`)
81
-
82
- **Purpose**: Default configurations, rule sets, and standard values.
83
-
84
- **Key Exports**:
85
- ```javascript
86
- // Constants
87
- DEFAULT_RULE_SETS // Predefined rule sets (MINIMAL, ESSENTIAL, etc.)
88
- DEFAULT_CONFIG // Default configuration object
89
- DEFAULT_SEVERITIES // Severity levels
90
- DEFAULT_TIMEOUTS // Timeout configurations
91
- DEFAULT_LIMITS // File size and processing limits
92
-
93
- // Functions
94
- getDefaultRuleSet(name) // Get predefined rule set
95
- getDefaultConfig(overrides) // Get configuration with overrides
96
- getLanguageExtensions(lang) // Get file extensions for language
97
- isFileSizeValid(size) // Check file size limits
98
- ```
99
-
100
- **Example Usage**:
101
- ```javascript
102
- const { getDefaultRuleSet, getDefaultConfig } = require('./core/constants/defaults');
103
-
104
- const essentialRules = getDefaultRuleSet('ESSENTIAL');
105
- // ['C001', 'C002', 'C003', ...]
106
-
107
- const config = getDefaultConfig({ verbose: true });
108
- // { verbose: true, useRegistry: true, ... }
109
- ```
110
-
111
- ### 3. Engines (`core/constants/engines.js`)
112
-
113
- **Purpose**: Engine capabilities, configurations, and language support.
114
-
115
- **Key Exports**:
116
- ```javascript
117
- // Constants
118
- SUPPORTED_ENGINES // Object with all supported engines
119
- ENGINE_CAPABILITIES // Engine features and language support
120
- ENGINE_MODES // Execution modes (sequential, parallel, etc.)
121
- ENGINE_PERFORMANCE // Performance characteristics
122
-
123
- // Functions
124
- getEngineLanguages(engine) // Get supported languages
125
- getEnginesForLanguage(language) // Get engines for language
126
- getRecommendedEngine(language) // Get best engine for language
127
- isLanguageSupported(engine, lang) // Check support
128
- getEnginePerformance(engine) // Get performance info
129
- ```
130
-
131
- **Example Usage**:
132
- ```javascript
133
- const { getEnginesForLanguage, getRecommendedEngine } = require('./core/constants/engines');
134
-
135
- const jsEngines = getEnginesForLanguage('javascript');
136
- // [{ name: 'heuristic', priority: 1, features: [...] }, ...]
137
-
138
- const recommended = getRecommendedEngine('typescript');
139
- // 'heuristic'
140
- ```
141
-
142
- ### 4. Rules (`core/constants/rules.js`)
143
-
144
- **Purpose**: Rule-related constants, metadata, and utilities.
145
-
146
- **Key Exports**:
147
- ```javascript
148
- // Constants
149
- RULE_SEVERITIES // Severity levels (ERROR, WARNING, etc.)
150
- RULE_STATUS // Execution status values
151
- RULE_TYPES // Analysis types (HEURISTIC, AST, etc.)
152
- RULE_SCOPES // Operation scopes (FILE, PROJECT, etc.)
153
- RULE_LANGUAGE_PATTERNS // Regex patterns for rule IDs
154
- RULE_TIMEOUTS // Timeout values by rule type
155
-
156
- // Functions
157
- getLanguageFromRuleId(ruleId) // Extract language from rule ID
158
- isValidRuleId(ruleId) // Validate rule ID format
159
- getRuleTimeout(type) // Get timeout for rule type
160
- getDefaultRuleMetadata(overrides) // Get rule metadata template
161
- isValidSeverity(severity) // Validate severity level
162
- ```
163
-
164
- **Example Usage**:
165
- ```javascript
166
- const { getLanguageFromRuleId, isValidRuleId } = require('./core/constants/rules');
167
-
168
- const language = getLanguageFromRuleId('C001');
169
- // 'common'
170
-
171
- const isValid = isValidRuleId('CUSTOM_RULE');
172
- // true
173
- ```
174
-
175
- ## Backward Compatibility
176
-
177
- The following files are maintained for backward compatibility but are deprecated:
178
-
179
- - `core/category-constants.js` - Proxies to `core/constants/categories.js`
180
- - `core/categories.js` - Proxies to `core/constants/categories.js`
181
-
182
- **Migration Path**:
183
- 1. Update imports to use `core/constants/*` directly
184
- 2. Replace deprecated file imports gradually
185
- 3. Legacy files will be removed in future versions
186
-
187
- ## Benefits
188
-
189
- ### 1. **Better Organization**
190
- - Related constants grouped together
191
- - Clear separation of concerns
192
- - Easier to locate and modify constants
193
-
194
- ### 2. **Improved Maintainability**
195
- - Single source of truth for each type of constant
196
- - Centralized documentation and examples
197
- - Easier to add new constants or modify existing ones
198
-
199
- ### 3. **Enhanced Extensibility**
200
- - Modular structure supports new constant types
201
- - Barrel export provides flexible import options
202
- - Framework for adding new engines, rules, categories
203
-
204
- ### 4. **Developer Experience**
205
- - Clearer imports with specific module names
206
- - Better IDE support and autocomplete
207
- - Self-documenting code structure
208
-
209
- ## Best Practices
210
-
211
- ### 1. **Import Strategy**
212
- ```javascript
213
- // ✅ Good: Import specific functions
214
- const { getValidCategories, normalizeCategory } = require('./core/constants/categories');
215
-
216
- // ✅ Good: Import from barrel for multiple modules
217
- const { getValidCategories, getDefaultRuleSet } = require('./core/constants');
218
-
219
- // ❌ Avoid: Importing entire modules unnecessarily
220
- const allConstants = require('./core/constants');
221
- ```
222
-
223
- ### 2. **Adding New Constants**
224
- ```javascript
225
- // Add to appropriate module (e.g., categories.js)
226
- const NEW_CATEGORY_FEATURE = 'new-feature';
227
-
228
- // Export in module
229
- module.exports = {
230
- NEW_CATEGORY_FEATURE,
231
- // ... other exports
232
- };
233
-
234
- // Update barrel export (index.js) if needed
235
- ```
236
-
237
- ### 3. **Extending Functionality**
238
- ```javascript
239
- // Add utility functions to appropriate modules
240
- function getAdvancedCategoryInfo(category) {
241
- // Implementation
242
- }
243
-
244
- // Export with other functions
245
- module.exports = {
246
- // ... existing exports
247
- getAdvancedCategoryInfo
248
- };
249
- ```
250
-
251
- ## Testing
252
-
253
- Each constants module includes comprehensive tests:
254
-
255
- ```bash
256
- # Test entire constants structure
257
- node test-constants-structure.js
258
-
259
- # Test backward compatibility
260
- node test-centralized-categories.js
261
- ```
262
-
263
- ## Future Enhancements
264
-
265
- ### Planned Features
266
- 1. **Dynamic Configuration Loading** - Load constants from external files
267
- 2. **Environment-specific Constants** - Different values for dev/prod
268
- 3. **Validation Schemas** - JSON Schema validation for all constants
269
- 4. **Hot Reloading** - Update constants without restarting
270
-
271
- ### Extension Points
272
- - Add new constant modules (e.g., `integrations.js`, `plugins.js`)
273
- - Extend barrel export for new modules
274
- - Add validation functions for new constant types
275
-
276
- ---
277
-
278
- ## Quick Reference
279
-
280
- | Module | Purpose | Key Functions |
281
- |--------|---------|---------------|
282
- | `categories.js` | Category management | `getValidCategories()`, `normalizeCategory()` |
283
- | `defaults.js` | Default values | `getDefaultConfig()`, `getDefaultRuleSet()` |
284
- | `engines.js` | Engine configuration | `getEnginesForLanguage()`, `getRecommendedEngine()` |
285
- | `rules.js` | Rule utilities | `getLanguageFromRuleId()`, `isValidRuleId()` |
286
- | `index.js` | Barrel export | All functions from all modules |
287
-
288
- For detailed API documentation, see the JSDoc comments in each module file.