@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.
- package/CHANGELOG.md +85 -0
- package/CONTRIBUTING.md +210 -1691
- package/README.md +5 -3
- package/config/rule-analysis-strategies.js +17 -1
- package/config/rules/enhanced-rules-registry.json +506 -1161
- package/config/rules/rules-registry-generated.json +1 -1
- package/core/analysis-orchestrator.js +167 -42
- package/core/auto-performance-manager.js +243 -0
- package/core/cli-action-handler.js +9 -1
- package/core/cli-program.js +19 -5
- package/core/constants/defaults.js +56 -0
- package/core/enhanced-rules-registry.js +2 -1
- package/core/performance-optimizer.js +271 -0
- package/core/semantic-engine.js +15 -3
- package/core/semantic-rule-base.js +4 -2
- package/docs/FILE_LIMITS_COMPLETION_REPORT.md +151 -0
- package/docs/FILE_LIMITS_EXPLANATION.md +190 -0
- package/docs/PERFORMANCE.md +311 -0
- package/docs/PERFORMANCE_MIGRATION_GUIDE.md +368 -0
- package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +255 -0
- package/docs/QUICK_FILE_LIMITS.md +64 -0
- package/docs/SIMPLIFIED_USAGE_GUIDE.md +208 -0
- package/engines/heuristic-engine.js +247 -9
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
- package/origin-rules/common-en.md +11 -7
- package/package.json +2 -1
- package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
- package/rules/common/C006_function_naming/analyzer.js +29 -3
- package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
- package/rules/common/C010_limit_block_nesting/config.json +64 -0
- package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
- package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
- package/rules/common/C013_no_dead_code/analyzer.js +75 -177
- package/rules/common/C013_no_dead_code/config.json +61 -0
- package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
- package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
- package/rules/common/C014_dependency_injection/analyzer.js +48 -313
- package/rules/common/C014_dependency_injection/config.json +26 -0
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
- package/rules/common/C018_no_throw_generic_error/config.json +50 -0
- package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +110 -317
- package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
- package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
- package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
- package/rules/common/C023_no_duplicate_variable/config.json +50 -0
- package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
- package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
- package/rules/common/C035_error_logging_context/analyzer.js +3 -1
- package/rules/common/C048_no_bypass_architectural_layers/analyzer.js +180 -0
- package/rules/common/C048_no_bypass_architectural_layers/config.json +50 -0
- package/rules/common/C048_no_bypass_architectural_layers/symbol-based-analyzer.js +235 -0
- package/rules/common/C052_parsing_or_data_transformation/analyzer.js +180 -0
- package/rules/common/C052_parsing_or_data_transformation/config.json +50 -0
- package/rules/common/C052_parsing_or_data_transformation/symbol-based-analyzer.js +132 -0
- package/rules/index.js +7 -1
- package/rules/security/S009_no_insecure_encryption/README.md +158 -0
- package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
- package/rules/security/S009_no_insecure_encryption/config.json +55 -0
- package/rules/security/S010_no_insecure_encryption/README.md +224 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
- package/rules/security/S010_no_insecure_encryption/config.json +48 -0
- package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
- package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
- package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
- package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
- package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
- package/rules/security/S017_use_parameterized_queries/README.md +128 -0
- package/rules/security/S017_use_parameterized_queries/analyzer.js +286 -0
- package/rules/security/S017_use_parameterized_queries/config.json +109 -0
- package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +541 -0
- package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +777 -0
- package/rules/security/S031_secure_session_cookies/README.md +127 -0
- package/rules/security/S031_secure_session_cookies/analyzer.js +245 -0
- package/rules/security/S031_secure_session_cookies/config.json +86 -0
- package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +196 -0
- package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +1084 -0
- package/rules/security/S032_httponly_session_cookies/FRAMEWORK_SUPPORT.md +209 -0
- package/rules/security/S032_httponly_session_cookies/README.md +184 -0
- package/rules/security/S032_httponly_session_cookies/analyzer.js +282 -0
- package/rules/security/S032_httponly_session_cookies/config.json +96 -0
- package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +715 -0
- package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +1348 -0
- package/rules/security/S033_samesite_session_cookies/README.md +227 -0
- package/rules/security/S033_samesite_session_cookies/analyzer.js +242 -0
- package/rules/security/S033_samesite_session_cookies/config.json +87 -0
- package/rules/security/S033_samesite_session_cookies/regex-based-analyzer.js +703 -0
- package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +732 -0
- package/rules/security/S034_host_prefix_session_cookies/README.md +204 -0
- package/rules/security/S034_host_prefix_session_cookies/analyzer.js +290 -0
- package/rules/security/S034_host_prefix_session_cookies/config.json +62 -0
- package/rules/security/S034_host_prefix_session_cookies/regex-based-analyzer.js +478 -0
- package/rules/security/S034_host_prefix_session_cookies/symbol-based-analyzer.js +277 -0
- package/rules/security/S035_path_session_cookies/README.md +257 -0
- package/rules/security/S035_path_session_cookies/analyzer.js +316 -0
- package/rules/security/S035_path_session_cookies/config.json +99 -0
- package/rules/security/S035_path_session_cookies/regex-based-analyzer.js +724 -0
- package/rules/security/S035_path_session_cookies/symbol-based-analyzer.js +373 -0
- package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
- package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
- package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
- package/rules/security/S055_content_type_validation/README.md +176 -0
- package/rules/security/S055_content_type_validation/analyzer.js +312 -0
- package/rules/security/S055_content_type_validation/config.json +48 -0
- package/rules/utils/rule-helpers.js +140 -1
- package/scripts/batch-processing-demo.js +334 -0
- package/scripts/consolidate-config.js +116 -0
- package/scripts/performance-test.js +541 -0
- package/scripts/quick-performance-test.js +108 -0
- package/config/rules/S027-categories.json +0 -122
- package/config/rules/rules-registry.json +0 -777
- 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
|
+
|
|
@@ -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
|
+
}
|