@sun-asterisk/sunlint 1.3.1 → 1.3.3

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.
Files changed (120) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/CONTRIBUTING.md +210 -1691
  3. package/README.md +5 -3
  4. package/config/rule-analysis-strategies.js +17 -1
  5. package/config/rules/enhanced-rules-registry.json +506 -1161
  6. package/config/rules/rules-registry-generated.json +1 -1
  7. package/core/analysis-orchestrator.js +167 -42
  8. package/core/auto-performance-manager.js +243 -0
  9. package/core/cli-action-handler.js +9 -1
  10. package/core/cli-program.js +19 -5
  11. package/core/constants/defaults.js +56 -0
  12. package/core/enhanced-rules-registry.js +2 -1
  13. package/core/performance-optimizer.js +271 -0
  14. package/core/semantic-engine.js +15 -3
  15. package/core/semantic-rule-base.js +4 -2
  16. package/docs/FILE_LIMITS_COMPLETION_REPORT.md +151 -0
  17. package/docs/FILE_LIMITS_EXPLANATION.md +190 -0
  18. package/docs/PERFORMANCE.md +311 -0
  19. package/docs/PERFORMANCE_MIGRATION_GUIDE.md +368 -0
  20. package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +255 -0
  21. package/docs/QUICK_FILE_LIMITS.md +64 -0
  22. package/docs/SIMPLIFIED_USAGE_GUIDE.md +208 -0
  23. package/engines/heuristic-engine.js +247 -9
  24. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  25. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  26. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  27. package/origin-rules/common-en.md +11 -7
  28. package/package.json +2 -1
  29. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  30. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  31. package/rules/common/C006_function_naming/analyzer.js +29 -3
  32. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  33. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  34. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  35. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  36. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  37. package/rules/common/C013_no_dead_code/config.json +61 -0
  38. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  39. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  40. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  41. package/rules/common/C014_dependency_injection/config.json +26 -0
  42. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  43. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  44. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  45. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  46. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  47. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  48. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  49. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  50. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  51. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  52. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  53. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  54. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  55. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  56. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  57. package/rules/common/C035_error_logging_context/analyzer.js +3 -1
  58. package/rules/common/C048_no_bypass_architectural_layers/analyzer.js +180 -0
  59. package/rules/common/C048_no_bypass_architectural_layers/config.json +50 -0
  60. package/rules/common/C048_no_bypass_architectural_layers/symbol-based-analyzer.js +235 -0
  61. package/rules/common/C052_parsing_or_data_transformation/analyzer.js +180 -0
  62. package/rules/common/C052_parsing_or_data_transformation/config.json +50 -0
  63. package/rules/common/C052_parsing_or_data_transformation/symbol-based-analyzer.js +132 -0
  64. package/rules/index.js +7 -1
  65. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  66. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  67. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  68. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  69. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  70. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  71. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  72. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  73. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  74. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  75. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  76. package/rules/security/S017_use_parameterized_queries/README.md +128 -0
  77. package/rules/security/S017_use_parameterized_queries/analyzer.js +286 -0
  78. package/rules/security/S017_use_parameterized_queries/config.json +109 -0
  79. package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +541 -0
  80. package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +777 -0
  81. package/rules/security/S031_secure_session_cookies/README.md +127 -0
  82. package/rules/security/S031_secure_session_cookies/analyzer.js +245 -0
  83. package/rules/security/S031_secure_session_cookies/config.json +86 -0
  84. package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +196 -0
  85. package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +1084 -0
  86. package/rules/security/S032_httponly_session_cookies/FRAMEWORK_SUPPORT.md +209 -0
  87. package/rules/security/S032_httponly_session_cookies/README.md +184 -0
  88. package/rules/security/S032_httponly_session_cookies/analyzer.js +282 -0
  89. package/rules/security/S032_httponly_session_cookies/config.json +96 -0
  90. package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +715 -0
  91. package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +1348 -0
  92. package/rules/security/S033_samesite_session_cookies/README.md +227 -0
  93. package/rules/security/S033_samesite_session_cookies/analyzer.js +242 -0
  94. package/rules/security/S033_samesite_session_cookies/config.json +87 -0
  95. package/rules/security/S033_samesite_session_cookies/regex-based-analyzer.js +703 -0
  96. package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +732 -0
  97. package/rules/security/S034_host_prefix_session_cookies/README.md +204 -0
  98. package/rules/security/S034_host_prefix_session_cookies/analyzer.js +290 -0
  99. package/rules/security/S034_host_prefix_session_cookies/config.json +62 -0
  100. package/rules/security/S034_host_prefix_session_cookies/regex-based-analyzer.js +478 -0
  101. package/rules/security/S034_host_prefix_session_cookies/symbol-based-analyzer.js +277 -0
  102. package/rules/security/S035_path_session_cookies/README.md +257 -0
  103. package/rules/security/S035_path_session_cookies/analyzer.js +316 -0
  104. package/rules/security/S035_path_session_cookies/config.json +99 -0
  105. package/rules/security/S035_path_session_cookies/regex-based-analyzer.js +724 -0
  106. package/rules/security/S035_path_session_cookies/symbol-based-analyzer.js +373 -0
  107. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  108. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  109. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  110. package/rules/security/S055_content_type_validation/README.md +176 -0
  111. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  112. package/rules/security/S055_content_type_validation/config.json +48 -0
  113. package/rules/utils/rule-helpers.js +140 -1
  114. package/scripts/batch-processing-demo.js +334 -0
  115. package/scripts/consolidate-config.js +116 -0
  116. package/scripts/performance-test.js +541 -0
  117. package/scripts/quick-performance-test.js +108 -0
  118. package/config/rules/S027-categories.json +0 -122
  119. package/config/rules/rules-registry.json +0 -777
  120. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -0,0 +1,181 @@
1
+ /**
2
+ * C024 Symbol-based Analyzer - Advanced Do not scatter hardcoded constants throughout the logic
3
+ * Purpose: The rule prevents scattering hardcoded constants throughout the logic. Instead, constants should be defined in a single place to improve maintainability and readability.
4
+ */
5
+
6
+ const { SyntaxKind } = require('ts-morph');
7
+
8
+ class C024SymbolBasedAnalyzer {
9
+ constructor(semanticEngine = null) {
10
+ this.ruleId = 'C024';
11
+ this.ruleName = 'Error Scatter hardcoded constants throughout the logic (Symbol-Based)';
12
+ this.semanticEngine = semanticEngine;
13
+ this.verbose = false;
14
+ this.safeStrings = ["UNKNOWN", "N/A"]; // allowlist of special fallback values
15
+ }
16
+
17
+ async initialize(semanticEngine = null) {
18
+ if (semanticEngine) {
19
+ this.semanticEngine = semanticEngine;
20
+ }
21
+ this.verbose = semanticEngine?.verbose || false;
22
+
23
+ if (process.env.SUNLINT_DEBUG) {
24
+ console.log(`🔧 [C024 Symbol-Based] Analyzer initialized, verbose: ${this.verbose}`);
25
+ }
26
+ }
27
+
28
+ async analyzeFileBasic(filePath, options = {}) {
29
+ // This is the main entry point called by the hybrid analyzer
30
+ return await this.analyzeFileWithSymbols(filePath, options);
31
+ }
32
+
33
+ async analyzeFileWithSymbols(filePath, options = {}) {
34
+ const violations = [];
35
+
36
+ // Enable verbose mode if requested
37
+ const verbose = options.verbose || this.verbose;
38
+
39
+ if (!this.semanticEngine?.project) {
40
+ if (verbose) {
41
+ console.warn('[C024 Symbol-Based] No semantic engine available, skipping analysis');
42
+ }
43
+ return violations;
44
+ }
45
+
46
+ if (verbose) {
47
+ console.log(`🔍 [C024 Symbol-Based] Starting analysis for ${filePath}`);
48
+ }
49
+
50
+ try {
51
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
52
+ if (!sourceFile) {
53
+ return violations;
54
+ }
55
+
56
+ // skip constants files
57
+ if (this.isConstantsFile(filePath)) return violations;
58
+ // Detect hardcoded constants
59
+ sourceFile.forEachDescendant((node) => {
60
+ this.checkLiterals(node, sourceFile, violations);
61
+ this.checkConstDeclaration(node, sourceFile, violations);
62
+ this.checkStaticReadonly(node, sourceFile, violations);
63
+ });
64
+
65
+
66
+ if (verbose) {
67
+ console.log(`🔍 [C024 Symbol-Based] Total violations found: ${violations.length}`);
68
+ }
69
+
70
+ return violations;
71
+ } catch (error) {
72
+ if (verbose) {
73
+ console.warn(`[C024 Symbol-Based] Analysis failed for ${filePath}:`, error.message);
74
+ }
75
+
76
+ return violations;
77
+ }
78
+ }
79
+
80
+ // --- push violation object ---
81
+ pushViolation(violations, node, filePath, text, message) {
82
+ violations.push({
83
+ ruleId: this.ruleId,
84
+ severity: "warning",
85
+ message: message || `Hardcoded constant found: "${text}"`,
86
+ source: this.ruleId,
87
+ file: filePath,
88
+ line: node.getStartLineNumber(),
89
+ column: node.getStart() - node.getStartLinePos(),
90
+ description:
91
+ "[SYMBOL-BASED] Hardcoded constants should be defined in a single place to improve maintainability.",
92
+ suggestion: "Define constants in a dedicated file or section",
93
+ category: "constants",
94
+ });
95
+ }
96
+
97
+ // --- check literals like "ADMIN", 123, true ---
98
+ checkLiterals(node, sourceFile, violations) {
99
+ const kind = node.getKind();
100
+ if (
101
+ kind === SyntaxKind.StringLiteral ||
102
+ kind === SyntaxKind.NumericLiteral ||
103
+ kind === SyntaxKind.TrueKeyword ||
104
+ kind === SyntaxKind.FalseKeyword
105
+ ) {
106
+ const text = node.getText().replace(/['"`]/g, ""); // strip quotes
107
+ if (this.isAllowedLiteral(node, text)) return;
108
+
109
+ this.pushViolation(
110
+ violations,
111
+ node,
112
+ sourceFile.getFilePath(),
113
+ node.getText()
114
+ );
115
+ }
116
+ }
117
+
118
+ // --- check const declarations outside constants.ts ---
119
+ checkConstDeclaration(node, sourceFile, violations) {
120
+ const kind = node.getKind();
121
+ if (kind === SyntaxKind.VariableDeclaration) {
122
+ const parentKind = node.getParent()?.getKind();
123
+ if (
124
+ parentKind === SyntaxKind.VariableDeclarationList &&
125
+ node.getParent().getDeclarationKind() === "const"
126
+ ) {
127
+ this.pushViolation(
128
+ violations,
129
+ node,
130
+ sourceFile.getFilePath(),
131
+ node.getName(),
132
+ `Const declaration "${node.getName()}" should be moved into constants file`
133
+ );
134
+ }
135
+ }
136
+ }
137
+
138
+ // --- check static readonly properties inside classes ---
139
+ checkStaticReadonly(node, sourceFile, violations) {
140
+ const kind = node.getKind();
141
+ if (kind === SyntaxKind.PropertyDeclaration) {
142
+ const modifiers = node.getModifiers().map((m) => m.getText());
143
+ if (modifiers.includes("static") && modifiers.includes("readonly")) {
144
+ this.pushViolation(
145
+ violations,
146
+ node,
147
+ sourceFile.getFilePath(),
148
+ node.getName(),
149
+ `Static readonly property "${node.getName()}" should be moved into constants file`
150
+ );
151
+ }
152
+ }
153
+ }
154
+
155
+ // --- helper: allow safe literals ---
156
+ isAllowedLiteral(node, text) {
157
+ // skip imports
158
+ if (node.getParent()?.getKind() === SyntaxKind.ImportDeclaration) {
159
+ return true;
160
+ }
161
+
162
+ // allow short strings
163
+ if (typeof text === "string" && text.length <= 1) return true;
164
+
165
+ // allow sentinel numbers
166
+ if (text === "0" || text === "1" || text === "-1") return true;
167
+
168
+ // allow known safe strings (like "UNKNOWN")
169
+ if (this.safeStrings.includes(text)) return true;
170
+
171
+ return false;
172
+ }
173
+
174
+ // helper to check if file is a constants file
175
+ isConstantsFile(filePath) {
176
+ const lower = filePath.toLowerCase();
177
+ return lower.endsWith("constants.ts") || lower.includes("/constants/");
178
+ }
179
+ }
180
+
181
+ module.exports = C024SymbolBasedAnalyzer;
@@ -0,0 +1,200 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Rule C030 - Use Custom Error Classes
6
+ * Enforce using application-specific error classes instead of generic system errors
7
+ * Examples to flag: throw new Error(), throw new TypeError(), Promise.reject(new Error(...))
8
+ */
9
+ class C030UseCustomErrorClassesAnalyzer {
10
+ constructor(options = {}) {
11
+ this.ruleId = 'C030';
12
+ this.ruleName = 'Use Custom Error Classes';
13
+ this.description = 'Use custom error classes instead of generic system errors';
14
+ this.severity = 'warning';
15
+ this.verbose = options.verbose || false;
16
+
17
+ this.builtinErrorNames = [
18
+ 'Error',
19
+ 'TypeError',
20
+ 'RangeError',
21
+ 'ReferenceError',
22
+ 'SyntaxError',
23
+ 'URIError',
24
+ 'EvalError'
25
+ ];
26
+
27
+ // Precompile regexes for speed
28
+ const namesGroup = this.builtinErrorNames.join('|');
29
+ this.patterns = [
30
+ // throw new Error(...)
31
+ new RegExp(`\\bthrow\\s+new\\s+(${namesGroup})\\s*\\(`),
32
+ // throw Error(...)
33
+ new RegExp(`\\bthrow\\s+(${namesGroup})\\s*\\(`),
34
+ // Promise.reject(new Error(...))
35
+ new RegExp(`Promise\\.reject\\s*\\(\\s*new\\s+(${namesGroup})\\s*\\(`),
36
+ // reject(new Error(...))
37
+ new RegExp(`\\breject\\s*\\(\\s*new\\s+(${namesGroup})\\s*\\(`),
38
+ // Throwing string literals (single, double quotes)
39
+ /\bthrow\s+['"][^'"]*['"]/,
40
+ // Throwing template literals
41
+ /\bthrow\s+`[^`]*`/,
42
+ // Throwing numbers
43
+ /\bthrow\s+\d+/,
44
+ // Throwing variables (simple identifiers) - remove $ anchor to allow comments
45
+ /\bthrow\s+[a-zA-Z_$][a-zA-Z0-9_$]*(?:\s*;|\s*\/\/|\s*$)/
46
+ ];
47
+ }
48
+
49
+ async analyze(files, language, config = {}) {
50
+ const violations = [];
51
+
52
+ for (const filePath of files) {
53
+ try {
54
+ // Handle both file paths and direct content
55
+ let content;
56
+ if (typeof filePath === 'string' && fs.existsSync(filePath)) {
57
+ content = fs.readFileSync(filePath, 'utf8');
58
+ } else if (typeof filePath === 'object' && filePath.content) {
59
+ // Handle test cases with direct content
60
+ content = filePath.content;
61
+ filePath = filePath.path || 'test.js';
62
+ } else {
63
+ if (this.verbose) {
64
+ console.warn(`C030: Skipping invalid file path: ${filePath}`);
65
+ }
66
+ continue;
67
+ }
68
+
69
+ const fileViolations = await this.analyzeFile(filePath, content, language, config);
70
+ violations.push(...fileViolations);
71
+ } catch (error) {
72
+ if (this.verbose) {
73
+ console.warn(`C030 analysis error for ${path.basename(filePath)}: ${error.message}`);
74
+ }
75
+ }
76
+ }
77
+
78
+ return violations;
79
+ }
80
+
81
+ async analyzeFile(filePath, content, language, config = {}) {
82
+ const violations = [];
83
+
84
+ // Only target JS/TS for now
85
+ if (!this.isJsLike(filePath)) {
86
+ return violations;
87
+ }
88
+
89
+ const lines = content.split('\n');
90
+
91
+ for (let i = 0; i < lines.length; i++) {
92
+ const line = lines[i];
93
+ const trimmed = line.trim();
94
+
95
+ // Skip comments-only lines quickly
96
+ if (this.isCommentOnly(trimmed)) {
97
+ continue;
98
+ }
99
+
100
+ for (const pattern of this.patterns) {
101
+ const match = trimmed.match(pattern);
102
+ if (match) {
103
+ const column = line.indexOf(match[0]) + 1;
104
+ const builtInName = this.extractBuiltinName(match);
105
+ const violationType = this.getViolationType(pattern, trimmed);
106
+
107
+ const suggestion = this.getSuggestion(builtInName, violationType);
108
+
109
+ violations.push({
110
+ ruleId: this.ruleId,
111
+ file: filePath,
112
+ line: i + 1,
113
+ column: Math.max(column, 1),
114
+ message: this.getMessage(violationType),
115
+ severity: this.severity,
116
+ code: trimmed,
117
+ type: violationType,
118
+ suggestion
119
+ });
120
+
121
+ // Avoid double-reporting same line on multiple patterns
122
+ break;
123
+ }
124
+ }
125
+ }
126
+
127
+ return violations;
128
+ }
129
+
130
+ isJsLike(filePath) {
131
+ return /\.(js|jsx|ts|tsx|mjs|cjs)$/.test(filePath);
132
+ }
133
+
134
+ isCommentOnly(trimmedLine) {
135
+ return trimmedLine.startsWith('//') || trimmedLine.startsWith('/*') || trimmedLine === '';
136
+ }
137
+
138
+ extractBuiltinName(regexMatch) {
139
+ if (!regexMatch || regexMatch.length < 2) return null;
140
+ const candidate = regexMatch[1];
141
+ return this.builtinErrorNames.includes(candidate) ? candidate : null;
142
+ }
143
+
144
+ getViolationType(pattern, line) {
145
+ if (pattern.source.includes('new\\s+(')) {
146
+ return 'generic_system_error_constructor';
147
+ } else if (pattern.source.includes('\\s+(') && pattern.source.includes('\\(')) {
148
+ return 'generic_system_error_call';
149
+ } else if (pattern.source.includes('Promise\\.reject')) {
150
+ return 'promise_reject_generic_error';
151
+ } else if (pattern.source.includes('reject\\s*\\(')) {
152
+ return 'reject_generic_error';
153
+ } else if (pattern.source.includes('`[^`]*`')) {
154
+ return 'throw_template_literal';
155
+ } else if (pattern.source.includes("['\"")) {
156
+ return 'throw_string_literal';
157
+ } else if (pattern.source.includes('\\d+')) {
158
+ return 'throw_number';
159
+ } else if (pattern.source.includes('[a-zA-Z_$]')) {
160
+ return 'throw_variable';
161
+ }
162
+ return 'generic_system_error_usage';
163
+ }
164
+
165
+ getMessage(violationType) {
166
+ const messages = {
167
+ 'generic_system_error_constructor': 'Use custom error classes instead of generic system error constructors',
168
+ 'generic_system_error_call': 'Use custom error classes instead of generic system error calls',
169
+ 'promise_reject_generic_error': 'Use custom error classes instead of rejecting with generic errors',
170
+ 'reject_generic_error': 'Use custom error classes instead of rejecting with generic errors',
171
+ 'throw_string_literal': 'Use custom error classes instead of throwing string literals',
172
+ 'throw_template_literal': 'Use custom error classes instead of throwing template literals',
173
+ 'throw_number': 'Use custom error classes instead of throwing numbers',
174
+ 'throw_variable': 'Use custom error classes instead of throwing variables',
175
+ 'generic_system_error_usage': 'Use custom error classes instead of generic system errors'
176
+ };
177
+ return messages[violationType] || messages['generic_system_error_usage'];
178
+ }
179
+
180
+ getSuggestion(builtInName, violationType) {
181
+ if (builtInName) {
182
+ return `Define and throw a custom error class (e.g., DomainError extends Error) instead of ${builtInName}`;
183
+ }
184
+
185
+ const suggestions = {
186
+ 'throw_string_literal': 'Define and throw a custom error class (e.g., DomainError extends Error) instead of throwing string literals',
187
+ 'throw_template_literal': 'Define and throw a custom error class (e.g., DomainError extends Error) instead of throwing template literals',
188
+ 'throw_number': 'Define and throw a custom error class (e.g., DomainError extends Error) instead of throwing numbers',
189
+ 'throw_variable': 'Define and throw a custom error class (e.g., DomainError extends Error) instead of throwing variables',
190
+ 'promise_reject_generic_error': 'Define and throw a custom error class (e.g., DomainError extends Error) instead of rejecting with generic errors',
191
+ 'reject_generic_error': 'Define and throw a custom error class (e.g., DomainError extends Error) instead of rejecting with generic errors'
192
+ };
193
+
194
+ return suggestions[violationType] || 'Define and throw a custom error class (e.g., DomainError extends Error)';
195
+ }
196
+ }
197
+
198
+ module.exports = new C030UseCustomErrorClassesAnalyzer();
199
+
200
+
@@ -171,7 +171,9 @@ class C035Analyzer {
171
171
  }
172
172
  }
173
173
 
174
- console.log(`🔧 [C035] No analysis methods succeeded, returning empty`);
174
+ if (options?.verbose) {
175
+ console.log(`🔧 [C035] No analysis methods succeeded, returning empty`);
176
+ }
175
177
  return [];
176
178
  }
177
179
 
@@ -0,0 +1,180 @@
1
+ /**
2
+ * C048 Main Analyzer - Do not bypass architectural layers (controller/service/repository)
3
+ * Primary: Maintain a clear layered architecture, ensuring logic and data flow are well-structured and maintainable.
4
+ * Fallback: Regex-based for all other cases
5
+ */
6
+
7
+ const C048SymbolBasedAnalyzer = require('./symbol-based-analyzer');
8
+
9
+ class C048Analyzer {
10
+ constructor(options = {}) {
11
+ if (process.env.SUNLINT_DEBUG) {
12
+ console.log(`🔧 [C048] Constructor called with options:`, !!options);
13
+ console.log(`🔧 [C048] Options type:`, typeof options, Object.keys(options || {}));
14
+ }
15
+
16
+ this.ruleId = 'C048';
17
+ this.ruleName = 'Do not bypass architectural layers (controller/service/repository)';
18
+ this.description = 'Maintain a clear layered architecture, ensuring logic and data flow are well-structured and maintainable.';
19
+ this.semanticEngine = options.semanticEngine || null;
20
+ this.verbose = options.verbose || false;
21
+
22
+ // Configuration
23
+ this.config = {
24
+ useSymbolBased: true, // Primary approach
25
+ fallbackToRegex: false, // Only when symbol fails completely
26
+ symbolBasedOnly: false // Can be set to true for pure mode
27
+ };
28
+
29
+ // Initialize both analyzers
30
+ try {
31
+ this.symbolAnalyzer = new C048SymbolBasedAnalyzer(this.semanticEngine);
32
+ if (process.env.SUNLINT_DEBUG) {
33
+ console.log(`🔧 [C048] Symbol analyzer created successfully`);
34
+ }
35
+ } catch (error) {
36
+ console.error(`🔧 [C048] Error creating symbol analyzer:`, error);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Initialize with semantic engine
42
+ */
43
+ async initialize(semanticEngine = null) {
44
+ if (semanticEngine) {
45
+ this.semanticEngine = semanticEngine;
46
+ }
47
+ this.verbose = semanticEngine?.verbose || false;
48
+
49
+ // Initialize both analyzers
50
+ await this.symbolAnalyzer.initialize(semanticEngine);
51
+
52
+ // Ensure verbose flag is propagated
53
+ this.symbolAnalyzer.verbose = this.verbose;
54
+
55
+ if (this.verbose) {
56
+ console.log(`🔧 [C048 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
57
+ }
58
+ }
59
+
60
+ async analyze(files, language, options = {}) {
61
+ if (process.env.SUNLINT_DEBUG) {
62
+ console.log(`🔧 [C048] analyze() method called with ${files.length} files, language: ${language}`);
63
+ }
64
+
65
+ const violations = [];
66
+
67
+ for (const filePath of files) {
68
+ try {
69
+ if (process.env.SUNLINT_DEBUG) {
70
+ console.log(`🔧 [C048] Processing file: ${filePath}`);
71
+ }
72
+
73
+ const fileViolations = await this.analyzeFile(filePath, options);
74
+ violations.push(...fileViolations);
75
+
76
+ if (process.env.SUNLINT_DEBUG) {
77
+ console.log(`🔧 [C048] File ${filePath}: Found ${fileViolations.length} violations`);
78
+ }
79
+ } catch (error) {
80
+ console.warn(`❌ [C048] Analysis failed for ${filePath}:`, error.message);
81
+ }
82
+ }
83
+
84
+ if (process.env.SUNLINT_DEBUG) {
85
+ console.log(`🔧 [C048] Total violations found: ${violations.length}`);
86
+ }
87
+
88
+ return violations;
89
+ }
90
+
91
+ async analyzeFile(filePath, options = {}) {
92
+ if (process.env.SUNLINT_DEBUG) {
93
+ console.log(`🔧 [C048] analyzeFile() called for: ${filePath}`);
94
+ }
95
+
96
+ // 1. Try Symbol-based analysis first (primary)
97
+ if (this.config.useSymbolBased &&
98
+ this.semanticEngine?.project &&
99
+ this.semanticEngine?.initialized) {
100
+ try {
101
+ if (process.env.SUNLINT_DEBUG) {
102
+ console.log(`🔧 [C048] Trying symbol-based analysis...`);
103
+ }
104
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
105
+ if (sourceFile) {
106
+ if (process.env.SUNLINT_DEBUG) {
107
+ console.log(`🔧 [C048] Source file found, analyzing with symbol-based...`);
108
+ }
109
+ const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
110
+
111
+ // Mark violations with analysis strategy
112
+ violations.forEach(v => v.analysisStrategy = 'symbol-based');
113
+
114
+ if (process.env.SUNLINT_DEBUG) {
115
+ console.log(`✅ [C048] Symbol-based analysis: ${violations.length} violations`);
116
+ }
117
+ return violations; // Return even if 0 violations - symbol analysis completed successfully
118
+ } else {
119
+ if (process.env.SUNLINT_DEBUG) {
120
+ console.log(`⚠️ [C048] Source file not found in project`);
121
+ }
122
+ }
123
+ } catch (error) {
124
+ console.warn(`⚠️ [C048] Symbol analysis failed: ${error.message}`);
125
+ // Continue to fallback
126
+ }
127
+ } else {
128
+ if (process.env.SUNLINT_DEBUG) {
129
+ console.log(`🔄 [C048] Symbol analysis conditions check:`);
130
+ console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
131
+ console.log(` - semanticEngine: ${!!this.semanticEngine}`);
132
+ console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
133
+ console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
134
+ console.log(`🔄 [C048] Symbol analysis unavailable, using regex fallback`);
135
+ }
136
+ }
137
+
138
+ if (options?.verbose) {
139
+ console.log(`🔧 [C048] No analysis methods succeeded, returning empty`);
140
+ }
141
+ return [];
142
+ }
143
+
144
+ async analyzeFileBasic(filePath, options = {}) {
145
+ console.log(`🔧 [C048] analyzeFileBasic() called for: ${filePath}`);
146
+ console.log(`🔧 [C048] semanticEngine exists: ${!!this.semanticEngine}`);
147
+ console.log(`🔧 [C048] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
148
+
149
+ try {
150
+ // Try symbol-based analysis first
151
+ if (this.semanticEngine?.isSymbolEngineReady?.() &&
152
+ this.semanticEngine.project) {
153
+
154
+ if (this.verbose) {
155
+ console.log(`🔍 [C048] Using symbol-based analysis for ${filePath}`);
156
+ }
157
+
158
+ const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
159
+ return violations;
160
+ }
161
+ } catch (error) {
162
+ if (this.verbose) {
163
+ console.warn(`⚠️ [C048] Symbol analysis failed: ${error.message}`);
164
+ }
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Methods for compatibility with different engine invocation patterns
170
+ */
171
+ async analyzeFileWithSymbols(filePath, options = {}) {
172
+ return this.analyzeFile(filePath, options);
173
+ }
174
+
175
+ async analyzeWithSemantics(filePath, options = {}) {
176
+ return this.analyzeFile(filePath, options);
177
+ }
178
+ }
179
+
180
+ module.exports = C048Analyzer;
@@ -0,0 +1,50 @@
1
+ {
2
+ "id": "C048",
3
+ "name": "C048_do_not_bypass_architectural_layers",
4
+ "category": "architecture",
5
+ "description": "C048 - Do not bypass architectural layers (controller/service/repository)",
6
+ "severity": "warning",
7
+ "enabled": true,
8
+ "semantic": {
9
+ "enabled": true,
10
+ "priority": "high",
11
+ "fallback": "heuristic"
12
+ },
13
+ "patterns": {
14
+ "include": [
15
+ "**/*.js",
16
+ "**/*.ts",
17
+ "**/*.jsx",
18
+ "**/*.tsx"
19
+ ],
20
+ "exclude": [
21
+ "**/*.test.*",
22
+ "**/*.spec.*",
23
+ "**/*.mock.*",
24
+ "**/test/**",
25
+ "**/tests/**",
26
+ "**/spec/**"
27
+ ]
28
+ },
29
+ "options": {
30
+ "strictMode": false,
31
+ "allowedDbMethods": [],
32
+ "repositoryPatterns": [
33
+ "*Repository*",
34
+ "*Repo*",
35
+ "*DAO*",
36
+ "*Store*"
37
+ ],
38
+ "servicePatterns": [
39
+ "*Service*",
40
+ "*UseCase*",
41
+ "*Handler*",
42
+ "*Manager*"
43
+ ],
44
+ "complexityThreshold": {
45
+ "methodLength": 200,
46
+ "cyclomaticComplexity": 5,
47
+ "nestedDepth": 3
48
+ }
49
+ }
50
+ }