@sun-asterisk/sunlint 1.0.5
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 +202 -0
- package/LICENSE +21 -0
- package/README.md +490 -0
- package/cli-legacy.js +355 -0
- package/cli.js +35 -0
- package/config/default.json +22 -0
- package/config/presets/beginner.json +36 -0
- package/config/presets/ci.json +46 -0
- package/config/presets/recommended.json +24 -0
- package/config/presets/strict.json +32 -0
- package/config/rules-registry.json +681 -0
- package/config/sunlint-schema.json +166 -0
- package/config/typescript/custom-rules-new.js +0 -0
- package/config/typescript/custom-rules.js +9 -0
- package/config/typescript/eslint.config.js +110 -0
- package/config/typescript/package-lock.json +1585 -0
- package/config/typescript/package.json +13 -0
- package/config/typescript/security-rules/index.js +90 -0
- package/config/typescript/security-rules/s005-no-origin-auth.js +95 -0
- package/config/typescript/security-rules/s006-activation-recovery-secret-not-plaintext.js +69 -0
- package/config/typescript/security-rules/s008-crypto-agility.js +62 -0
- package/config/typescript/security-rules/s009-no-insecure-crypto.js +103 -0
- package/config/typescript/security-rules/s010-no-insecure-random-in-sensitive-context.js +123 -0
- package/config/typescript/security-rules/s011-no-insecure-uuid.js +66 -0
- package/config/typescript/security-rules/s012-hardcode-secret.js +71 -0
- package/config/typescript/security-rules/s014-insecure-tls-version.js +50 -0
- package/config/typescript/security-rules/s015-insecure-tls-certificate.js +43 -0
- package/config/typescript/security-rules/s016-sensitive-query-parameter.js +59 -0
- package/config/typescript/security-rules/s017-no-sql-injection.js +193 -0
- package/config/typescript/security-rules/s018-positive-input-validation.js +56 -0
- package/config/typescript/security-rules/s019-no-raw-user-input-in-email.js +113 -0
- package/config/typescript/security-rules/s020-no-eval-dynamic-execution.js +89 -0
- package/config/typescript/security-rules/s022-output-encoding.js +78 -0
- package/config/typescript/security-rules/s023-no-json-injection.js +300 -0
- package/config/typescript/security-rules/s025-server-side-input-validation.js +217 -0
- package/config/typescript/security-rules/s026-json-schema-validation.js +68 -0
- package/config/typescript/security-rules/s027-no-hardcoded-secrets.js +80 -0
- package/config/typescript/security-rules/s029-require-csrf-protection.js +79 -0
- package/config/typescript/security-rules/s030-no-directory-browsing.js +78 -0
- package/config/typescript/security-rules/s033-require-samesite-cookie.js +80 -0
- package/config/typescript/security-rules/s034-require-host-cookie-prefix.js +77 -0
- package/config/typescript/security-rules/s035-cookie-specific-path.js +74 -0
- package/config/typescript/security-rules/s036-no-unsafe-file-include.js +68 -0
- package/config/typescript/security-rules/s037-require-anti-cache-headers.js +70 -0
- package/config/typescript/security-rules/s038-no-version-disclosure.js +74 -0
- package/config/typescript/security-rules/s039-no-session-token-in-url.js +63 -0
- package/config/typescript/security-rules/s041-require-session-invalidate-on-logout.js +211 -0
- package/config/typescript/security-rules/s042-require-periodic-reauthentication.js +294 -0
- package/config/typescript/security-rules/s043-terminate-sessions-on-password-change.js +254 -0
- package/config/typescript/security-rules/s044-require-full-session-for-sensitive-operations.js +292 -0
- package/config/typescript/security-rules/s045-anti-automation-controls.js +46 -0
- package/config/typescript/security-rules/s046-secure-notification-on-auth-change.js +44 -0
- package/config/typescript/security-rules/s048-password-credential-recovery.js +54 -0
- package/config/typescript/security-rules/s050-session-token-weak-hash.js +94 -0
- package/config/typescript/security-rules/s052-secure-random-authentication-code.js +66 -0
- package/config/typescript/security-rules/s054-verification-default-account.js +109 -0
- package/config/typescript/security-rules/s057-utc-logging.js +54 -0
- package/config/typescript/security-rules/s058-no-ssrf.js +73 -0
- package/config/typescript/test-s005-working.ts +22 -0
- package/config/typescript/tsconfig.json +29 -0
- package/core/ai-analyzer.js +169 -0
- package/core/analysis-orchestrator.js +705 -0
- package/core/cli-action-handler.js +230 -0
- package/core/cli-program.js +106 -0
- package/core/config-manager.js +396 -0
- package/core/config-merger.js +136 -0
- package/core/config-override-processor.js +74 -0
- package/core/config-preset-resolver.js +65 -0
- package/core/config-source-loader.js +152 -0
- package/core/config-validator.js +126 -0
- package/core/dependency-manager.js +105 -0
- package/core/eslint-engine-service.js +312 -0
- package/core/eslint-instance-manager.js +104 -0
- package/core/eslint-integration-service.js +363 -0
- package/core/git-utils.js +170 -0
- package/core/multi-rule-runner.js +239 -0
- package/core/output-service.js +250 -0
- package/core/report-generator.js +320 -0
- package/core/rule-mapping-service.js +309 -0
- package/core/rule-selection-service.js +121 -0
- package/core/sunlint-engine-service.js +23 -0
- package/core/typescript-analyzer.js +262 -0
- package/core/typescript-engine.js +313 -0
- package/docs/AI.md +163 -0
- package/docs/ARCHITECTURE.md +78 -0
- package/docs/CI-CD-GUIDE.md +315 -0
- package/docs/COMMAND-EXAMPLES.md +256 -0
- package/docs/DEBUG.md +86 -0
- package/docs/DISTRIBUTION.md +153 -0
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
- package/docs/ESLINT_INTEGRATION.md +238 -0
- package/docs/FOLDER_STRUCTURE.md +59 -0
- package/docs/HEURISTIC_VS_AI.md +113 -0
- package/docs/README.md +32 -0
- package/docs/RELEASE_GUIDE.md +230 -0
- package/docs/RULE-RESPONSIBILITY-MATRIX.md +204 -0
- package/eslint-integration/.eslintrc.js +98 -0
- package/eslint-integration/cli.js +35 -0
- package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +204 -0
- package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +246 -0
- package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +207 -0
- package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +90 -0
- package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +43 -0
- package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +38 -0
- package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +39 -0
- package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +335 -0
- package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +142 -0
- package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +50 -0
- package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +80 -0
- package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +294 -0
- package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +34 -0
- package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +32 -0
- package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +64 -0
- package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +406 -0
- package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +300 -0
- package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +239 -0
- package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +31 -0
- package/eslint-integration/eslint-plugin-custom/c076-one-assert-per-test.js +184 -0
- package/eslint-integration/eslint-plugin-custom/index.js +155 -0
- package/eslint-integration/eslint-plugin-custom/package.json +13 -0
- package/eslint-integration/eslint-plugin-custom/package.json.bak +9 -0
- package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +86 -0
- package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +95 -0
- package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +69 -0
- package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +62 -0
- package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +103 -0
- package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +123 -0
- package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +66 -0
- package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +71 -0
- package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +50 -0
- package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +43 -0
- package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +59 -0
- package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +193 -0
- package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +56 -0
- package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +113 -0
- package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +89 -0
- package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +78 -0
- package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +300 -0
- package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +217 -0
- package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +68 -0
- package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +80 -0
- package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +79 -0
- package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +78 -0
- package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +80 -0
- package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +77 -0
- package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +74 -0
- package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +68 -0
- package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +70 -0
- package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +74 -0
- package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +63 -0
- package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +211 -0
- package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +294 -0
- package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +254 -0
- package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +292 -0
- package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +46 -0
- package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +44 -0
- package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +108 -0
- package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +54 -0
- package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +94 -0
- package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +66 -0
- package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +109 -0
- package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +143 -0
- package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +54 -0
- package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +73 -0
- package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +42 -0
- package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +48 -0
- package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +160 -0
- package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +52 -0
- package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +175 -0
- package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +95 -0
- package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +48 -0
- package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +377 -0
- package/eslint-integration/eslint.config.js +125 -0
- package/eslint-integration/eslint.config.simple.js +24 -0
- package/eslint-integration/node_modules/eslint-plugin-custom/package.json +0 -0
- package/eslint-integration/package.json +23 -0
- package/eslint-integration/sample.ts +53 -0
- package/eslint-integration/test-s003.js +5 -0
- package/eslint-integration/tsconfig.json +27 -0
- package/examples/.github/workflows/code-quality.yml +111 -0
- package/examples/.sunlint.json +42 -0
- package/examples/README.md +47 -0
- package/examples/package.json +33 -0
- package/package.json +100 -0
- package/rules/C006_function_naming/analyzer.js +338 -0
- package/rules/C006_function_naming/config.json +86 -0
- package/rules/C019_log_level_usage/analyzer.js +359 -0
- package/rules/C019_log_level_usage/config.json +121 -0
- package/rules/C029_catch_block_logging/analyzer.js +339 -0
- package/rules/C029_catch_block_logging/config.json +59 -0
- package/rules/C031_validation_separation/README.md +72 -0
- package/rules/C031_validation_separation/analyzer.js +186 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const AIAnalyzer = require('../../core/ai-analyzer');
|
|
4
|
+
|
|
5
|
+
class C029Analyzer {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.ruleId = 'C029';
|
|
8
|
+
this.ruleName = 'Enhanced Catch Block Error Logging';
|
|
9
|
+
this.description = 'Mọi catch block phải log nguyên nhân lỗi đầy đủ và bảo toàn context (enhanced version replacing ESLint C028)';
|
|
10
|
+
this.aiAnalyzer = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async analyze(files, language, config) {
|
|
14
|
+
const violations = [];
|
|
15
|
+
|
|
16
|
+
// Initialize AI analyzer if enabled
|
|
17
|
+
if (config.ai && config.ai.enabled) {
|
|
18
|
+
this.aiAnalyzer = new AIAnalyzer(config.ai);
|
|
19
|
+
console.log('🤖 AI analysis enabled for C029');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const filePath of files) {
|
|
23
|
+
try {
|
|
24
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
25
|
+
const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
|
|
26
|
+
violations.push(...fileViolations);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(`Error analyzing file ${filePath}:`, error.message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return violations;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async analyzeFile(filePath, content, language, config) {
|
|
36
|
+
// Fallback to pattern-based analysis
|
|
37
|
+
console.log(`🔍 Running pattern analysis on ${path.basename(filePath)}`);
|
|
38
|
+
switch (language) {
|
|
39
|
+
case 'typescript':
|
|
40
|
+
case 'javascript':
|
|
41
|
+
return this.analyzeTypeScript(filePath, content, config);
|
|
42
|
+
default:
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async analyzeTypeScript(filePath, content, config) {
|
|
48
|
+
const violations = [];
|
|
49
|
+
const lines = content.split('\n');
|
|
50
|
+
|
|
51
|
+
// Focus on core functionality - only detect truly silent catch blocks
|
|
52
|
+
violations.push(...this.findSilentCatchBlocks(lines, filePath));
|
|
53
|
+
// Disabled strict checks to reduce false positives:
|
|
54
|
+
// violations.push(...this.findInadequateErrorLogging(lines, filePath));
|
|
55
|
+
// violations.push(...this.findMissingErrorContext(lines, filePath));
|
|
56
|
+
|
|
57
|
+
return violations;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Core functionality from ESLint C029 - detect silent catch blocks
|
|
62
|
+
*/
|
|
63
|
+
findSilentCatchBlocks(lines, filePath) {
|
|
64
|
+
const violations = [];
|
|
65
|
+
|
|
66
|
+
lines.forEach((line, index) => {
|
|
67
|
+
const lineNumber = index + 1;
|
|
68
|
+
const trimmedLine = line.trim();
|
|
69
|
+
|
|
70
|
+
// Detect catch block start
|
|
71
|
+
if (this.isCatchBlockStart(trimmedLine)) {
|
|
72
|
+
const catchBlockInfo = this.extractCatchBlockInfo(lines, index);
|
|
73
|
+
|
|
74
|
+
if (catchBlockInfo.isEmpty) {
|
|
75
|
+
violations.push({
|
|
76
|
+
ruleId: this.ruleId,
|
|
77
|
+
file: filePath,
|
|
78
|
+
line: lineNumber,
|
|
79
|
+
column: line.indexOf('catch') + 1,
|
|
80
|
+
message: 'Empty catch block - error is silently ignored',
|
|
81
|
+
severity: 'error',
|
|
82
|
+
code: trimmedLine,
|
|
83
|
+
type: 'empty_catch_block',
|
|
84
|
+
confidence: 1.0,
|
|
85
|
+
suggestion: 'Add error logging or rethrowing in catch block'
|
|
86
|
+
});
|
|
87
|
+
} else if (!catchBlockInfo.hasLoggingOrRethrow) {
|
|
88
|
+
violations.push({
|
|
89
|
+
ruleId: this.ruleId,
|
|
90
|
+
file: filePath,
|
|
91
|
+
line: lineNumber,
|
|
92
|
+
column: line.indexOf('catch') + 1,
|
|
93
|
+
message: 'Catch block must log error or rethrow - silent error handling hides bugs',
|
|
94
|
+
severity: 'error',
|
|
95
|
+
code: trimmedLine,
|
|
96
|
+
type: 'silent_catch_block',
|
|
97
|
+
confidence: 0.9,
|
|
98
|
+
suggestion: 'Add error logging (console.error, logger.error) or rethrow the error'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return violations;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* SunLint enhanced functionality - detect inadequate error logging
|
|
109
|
+
*/
|
|
110
|
+
findInadequateErrorLogging(lines, filePath) {
|
|
111
|
+
const violations = [];
|
|
112
|
+
|
|
113
|
+
lines.forEach((line, index) => {
|
|
114
|
+
const lineNumber = index + 1;
|
|
115
|
+
const trimmedLine = line.trim();
|
|
116
|
+
|
|
117
|
+
if (this.isCatchBlockStart(trimmedLine)) {
|
|
118
|
+
const catchBlockInfo = this.extractCatchBlockInfo(lines, index);
|
|
119
|
+
|
|
120
|
+
if (catchBlockInfo.hasLoggingOrRethrow && !catchBlockInfo.hasAdequateLogging) {
|
|
121
|
+
violations.push({
|
|
122
|
+
ruleId: this.ruleId,
|
|
123
|
+
file: filePath,
|
|
124
|
+
line: lineNumber,
|
|
125
|
+
column: line.indexOf('catch') + 1,
|
|
126
|
+
message: 'Error logging should include error message, stack trace, and context',
|
|
127
|
+
severity: 'warning',
|
|
128
|
+
code: trimmedLine,
|
|
129
|
+
type: 'inadequate_error_logging',
|
|
130
|
+
confidence: 0.8,
|
|
131
|
+
suggestion: 'Include error.message, error.stack, and relevant context in error logging'
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return violations;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* SunLint enhanced functionality - detect missing error context
|
|
142
|
+
* DISABLED: Too strict, causing false positives
|
|
143
|
+
*/
|
|
144
|
+
findMissingErrorContext(lines, filePath) {
|
|
145
|
+
// Disabled to reduce false positives
|
|
146
|
+
return [];
|
|
147
|
+
|
|
148
|
+
/* Original strict implementation:
|
|
149
|
+
const violations = [];
|
|
150
|
+
|
|
151
|
+
lines.forEach((line, index) => {
|
|
152
|
+
const lineNumber = index + 1;
|
|
153
|
+
const trimmedLine = line.trim();
|
|
154
|
+
|
|
155
|
+
if (this.isCatchBlockStart(trimmedLine)) {
|
|
156
|
+
const catchBlockInfo = this.extractCatchBlockInfo(lines, index);
|
|
157
|
+
|
|
158
|
+
if (catchBlockInfo.hasLoggingOrRethrow && !catchBlockInfo.hasContextualLogging) {
|
|
159
|
+
violations.push({
|
|
160
|
+
ruleId: this.ruleId,
|
|
161
|
+
file: filePath,
|
|
162
|
+
line: lineNumber,
|
|
163
|
+
column: line.indexOf('catch') + 1,
|
|
164
|
+
message: 'Error logging should include operational context (function name, input parameters)',
|
|
165
|
+
severity: 'info',
|
|
166
|
+
code: trimmedLine,
|
|
167
|
+
type: 'missing_error_context',
|
|
168
|
+
confidence: 0.7,
|
|
169
|
+
suggestion: 'Include function name, input parameters, and operational context in error logging'
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return violations;
|
|
176
|
+
*/
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
isCatchBlockStart(line) {
|
|
180
|
+
return line.includes('catch (') || line.includes('catch(');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
extractCatchBlockInfo(lines, startIndex) {
|
|
184
|
+
const catchBlockLines = [];
|
|
185
|
+
let braceDepth = 0;
|
|
186
|
+
let foundCatchBrace = false;
|
|
187
|
+
|
|
188
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
189
|
+
const line = lines[i];
|
|
190
|
+
catchBlockLines.push(line);
|
|
191
|
+
|
|
192
|
+
// Check if this is the catch line with opening brace
|
|
193
|
+
if (this.isCatchBlockStart(line.trim())) {
|
|
194
|
+
// Count braces in the catch line itself
|
|
195
|
+
for (const char of line) {
|
|
196
|
+
if (char === '{') {
|
|
197
|
+
foundCatchBrace = true;
|
|
198
|
+
braceDepth = 1; // Start counting from 1 for the catch block
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} else if (foundCatchBrace) {
|
|
202
|
+
// Count braces in subsequent lines
|
|
203
|
+
for (const char of line) {
|
|
204
|
+
if (char === '{') {
|
|
205
|
+
braceDepth++;
|
|
206
|
+
} else if (char === '}') {
|
|
207
|
+
braceDepth--;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// If we've closed all braces, we're done
|
|
212
|
+
if (braceDepth === 0) {
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const content = catchBlockLines.join('\n').toLowerCase();
|
|
219
|
+
const originalContent = catchBlockLines.join('\n');
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
isEmpty: this.isCatchBlockEmpty(catchBlockLines),
|
|
223
|
+
hasLoggingOrRethrow: this.hasLoggingOrRethrow(content, originalContent),
|
|
224
|
+
hasAdequateLogging: this.hasAdequateErrorLogging(content, originalContent),
|
|
225
|
+
hasContextualLogging: this.hasContextualErrorLogging(content, originalContent)
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
isCatchBlockEmpty(catchBlockLines) {
|
|
230
|
+
if (catchBlockLines.length === 0) return true;
|
|
231
|
+
|
|
232
|
+
// Join all lines and find the content between braces
|
|
233
|
+
const fullContent = catchBlockLines.join('\n');
|
|
234
|
+
|
|
235
|
+
// Find the opening brace and closing brace
|
|
236
|
+
let openBraceIndex = fullContent.indexOf('{');
|
|
237
|
+
let closeBraceIndex = fullContent.lastIndexOf('}');
|
|
238
|
+
|
|
239
|
+
if (openBraceIndex === -1 || closeBraceIndex === -1) {
|
|
240
|
+
return true; // Malformed catch block
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Extract body content between braces
|
|
244
|
+
const bodyContent = fullContent.substring(openBraceIndex + 1, closeBraceIndex).trim();
|
|
245
|
+
|
|
246
|
+
// Remove comments and whitespace
|
|
247
|
+
const cleanedBody = bodyContent
|
|
248
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
|
|
249
|
+
.replace(/\/\/.*$/gm, '') // Remove line comments
|
|
250
|
+
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
251
|
+
.trim();
|
|
252
|
+
|
|
253
|
+
return cleanedBody.length === 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
hasLoggingOrRethrow(content, originalContent) {
|
|
257
|
+
// Check for throw statements
|
|
258
|
+
if (content.includes('throw ') || content.includes('throw;')) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Check for logging patterns (expanded from original)
|
|
263
|
+
const loggingPatterns = [
|
|
264
|
+
'console.error',
|
|
265
|
+
'console.log',
|
|
266
|
+
'console.warn',
|
|
267
|
+
'logger.error',
|
|
268
|
+
'log.error',
|
|
269
|
+
'logger.warn',
|
|
270
|
+
'log.warn',
|
|
271
|
+
'winston.error',
|
|
272
|
+
'bunyan.error',
|
|
273
|
+
'pino.error',
|
|
274
|
+
'.error(',
|
|
275
|
+
'.warn(',
|
|
276
|
+
'.log('
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
// Accept basic error logging - don't require context
|
|
280
|
+
const hasBasicLogging = loggingPatterns.some(pattern =>
|
|
281
|
+
content.includes(pattern) || originalContent.includes(pattern)
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
return hasBasicLogging;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
hasAdequateErrorLogging(content, originalContent) {
|
|
288
|
+
// Check for error properties being logged
|
|
289
|
+
const errorProperties = [
|
|
290
|
+
'error.message',
|
|
291
|
+
'error.stack',
|
|
292
|
+
'err.message',
|
|
293
|
+
'err.stack',
|
|
294
|
+
'e.message',
|
|
295
|
+
'e.stack',
|
|
296
|
+
'error.name',
|
|
297
|
+
'error.cause'
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
const hasErrorProperties = errorProperties.some(prop =>
|
|
301
|
+
content.includes(prop) || originalContent.includes(prop)
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// Check for comprehensive error logging
|
|
305
|
+
const hasLogging = this.hasLoggingOrRethrow(content, originalContent);
|
|
306
|
+
|
|
307
|
+
return hasLogging && hasErrorProperties;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
hasContextualErrorLogging(content, originalContent) {
|
|
311
|
+
// Check for contextual information in logging
|
|
312
|
+
const contextualPatterns = [
|
|
313
|
+
'function',
|
|
314
|
+
'method',
|
|
315
|
+
'operation',
|
|
316
|
+
'input',
|
|
317
|
+
'parameters',
|
|
318
|
+
'context',
|
|
319
|
+
'state',
|
|
320
|
+
'request',
|
|
321
|
+
'response',
|
|
322
|
+
'userId',
|
|
323
|
+
'sessionId',
|
|
324
|
+
'transactionId'
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
// Check if logging includes contextual information
|
|
328
|
+
const hasContextualInfo = contextualPatterns.some(pattern =>
|
|
329
|
+
content.includes(pattern) || originalContent.includes(pattern)
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Check for template literals or string concatenation (indicates contextual logging)
|
|
333
|
+
const hasTemplateLogging = originalContent.includes('${') || originalContent.includes('" + ') || originalContent.includes("' + ");
|
|
334
|
+
|
|
335
|
+
return hasContextualInfo || hasTemplateLogging;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
module.exports = new C029Analyzer();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ruleId": "C029",
|
|
3
|
+
"name": "Catch Block Error Logging",
|
|
4
|
+
"description": "Mọi catch block phải log nguyên nhân lỗi đầy đủ",
|
|
5
|
+
"category": "error-handling",
|
|
6
|
+
"severity": "error",
|
|
7
|
+
"languages": ["typescript", "dart", "kotlin", "javascript"],
|
|
8
|
+
"version": "1.0.0",
|
|
9
|
+
"status": "activated",
|
|
10
|
+
"tags": ["error-handling", "logging", "debugging", "monitoring"],
|
|
11
|
+
"examples": {
|
|
12
|
+
"typescript": {
|
|
13
|
+
"violations": [
|
|
14
|
+
"try { riskyOperation(); } catch (error) { return null; }",
|
|
15
|
+
"catch (e) { /* empty */ }",
|
|
16
|
+
"catch (error) { throw new Error('Failed'); }"
|
|
17
|
+
],
|
|
18
|
+
"valid": [
|
|
19
|
+
"catch (error) { console.error('Operation failed:', error); }",
|
|
20
|
+
"catch (e) { logger.error('Error in process:', e); }",
|
|
21
|
+
"catch (error) { console.error(error); throw error; }"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"dart": {
|
|
25
|
+
"violations": [
|
|
26
|
+
"try { riskyOperation(); } catch (e) { return null; }",
|
|
27
|
+
"on Exception catch (e) { /* empty */ }"
|
|
28
|
+
],
|
|
29
|
+
"valid": [
|
|
30
|
+
"catch (e) { print('Error: $e'); }",
|
|
31
|
+
"on Exception catch (e) { log.severe('Failed:', e); }"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"kotlin": {
|
|
35
|
+
"violations": [
|
|
36
|
+
"try { riskyOperation() } catch (e: Exception) { return null }",
|
|
37
|
+
"catch (e: Exception) { /* empty */ }"
|
|
38
|
+
],
|
|
39
|
+
"valid": [
|
|
40
|
+
"catch (e: Exception) { Log.e(TAG, 'Error:', e) }",
|
|
41
|
+
"catch (e: Exception) { logger.error('Failed', e) }"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"configuration": {
|
|
46
|
+
"requiredLoggingMethods": [
|
|
47
|
+
"console.error",
|
|
48
|
+
"console.log",
|
|
49
|
+
"logger.error",
|
|
50
|
+
"log.error",
|
|
51
|
+
"print",
|
|
52
|
+
"log.severe",
|
|
53
|
+
"Log.e",
|
|
54
|
+
"timber.e"
|
|
55
|
+
],
|
|
56
|
+
"allowEmptyWhenRethrow": true,
|
|
57
|
+
"checkErrorParameter": true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Rule C031 - Validation Logic Separation
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
Logic kiểm tra dữ liệu (validate) phải nằm riêng biệt khỏi business logic.
|
|
5
|
+
|
|
6
|
+
## Rationale
|
|
7
|
+
Tách biệt validation logic giúp:
|
|
8
|
+
- Code dễ đọc và maintain
|
|
9
|
+
- Validation có thể reuse
|
|
10
|
+
- Testing dễ dàng hơn
|
|
11
|
+
- Tuân thủ Single Responsibility Principle
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### ❌ Bad - Validation mixed with business logic
|
|
16
|
+
```javascript
|
|
17
|
+
function processOrder(order) {
|
|
18
|
+
// Validation mixed with business logic
|
|
19
|
+
if (!order.customerId) {
|
|
20
|
+
throw new Error('Customer ID is required');
|
|
21
|
+
}
|
|
22
|
+
if (!order.items || order.items.length === 0) {
|
|
23
|
+
throw new Error('Order must have items');
|
|
24
|
+
}
|
|
25
|
+
if (order.total < 0) {
|
|
26
|
+
throw new Error('Total cannot be negative');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Business logic
|
|
30
|
+
const discount = calculateDiscount(order);
|
|
31
|
+
const tax = calculateTax(order);
|
|
32
|
+
return processPayment(order, discount, tax);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### ✅ Good - Separate validation
|
|
37
|
+
```javascript
|
|
38
|
+
function validateOrder(order) {
|
|
39
|
+
if (!order.customerId) {
|
|
40
|
+
throw new Error('Customer ID is required');
|
|
41
|
+
}
|
|
42
|
+
if (!order.items || order.items.length === 0) {
|
|
43
|
+
throw new Error('Order must have items');
|
|
44
|
+
}
|
|
45
|
+
if (order.total < 0) {
|
|
46
|
+
throw new Error('Total cannot be negative');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function processOrder(order) {
|
|
51
|
+
validateOrder(order);
|
|
52
|
+
|
|
53
|
+
// Pure business logic
|
|
54
|
+
const discount = calculateDiscount(order);
|
|
55
|
+
const tax = calculateTax(order);
|
|
56
|
+
return processPayment(order, discount, tax);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"C031": {
|
|
64
|
+
"enabled": true,
|
|
65
|
+
"severity": "warning",
|
|
66
|
+
"options": {
|
|
67
|
+
"maxValidationStatementsInFunction": 3,
|
|
68
|
+
"requireSeparateValidationFunction": true
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rule C031 - Validation Logic Separation
|
|
6
|
+
* Kiểm tra logic validation có bị trộn lẫn với business logic không
|
|
7
|
+
*/
|
|
8
|
+
class ValidationSeparationAnalyzer {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.ruleId = 'C031';
|
|
11
|
+
this.ruleName = 'Validation Logic Separation';
|
|
12
|
+
this.category = 'architecture';
|
|
13
|
+
this.severity = 'warning';
|
|
14
|
+
this.description = 'Logic kiểm tra dữ liệu (validate) phải nằm riêng biệt';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
analyzeFile(filePath, options = {}) {
|
|
18
|
+
const violations = [];
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
if (!fs.existsSync(filePath)) {
|
|
22
|
+
return violations;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
26
|
+
const lines = content.split('\n');
|
|
27
|
+
|
|
28
|
+
// Detect functions with mixed validation and business logic
|
|
29
|
+
const functions = this.extractFunctions(content);
|
|
30
|
+
|
|
31
|
+
for (const func of functions) {
|
|
32
|
+
const validationCount = this.countValidationStatements(func.body);
|
|
33
|
+
const businessLogicCount = this.countBusinessLogicStatements(func.body);
|
|
34
|
+
|
|
35
|
+
// If both validation and business logic exist in same function
|
|
36
|
+
if (validationCount > 0 && businessLogicCount > 0) {
|
|
37
|
+
const maxValidationAllowed = options.maxValidationStatementsInFunction || 3;
|
|
38
|
+
|
|
39
|
+
if (validationCount > maxValidationAllowed) {
|
|
40
|
+
violations.push({
|
|
41
|
+
line: func.startLine,
|
|
42
|
+
column: 1,
|
|
43
|
+
message: `Function '${func.name}' has ${validationCount} validation statements mixed with business logic. Consider separating validation logic.`,
|
|
44
|
+
ruleId: this.ruleId,
|
|
45
|
+
severity: this.severity,
|
|
46
|
+
source: lines[func.startLine - 1]?.trim() || ''
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(`Error analyzing ${filePath}:`, error.message);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return violations;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
extractFunctions(content) {
|
|
60
|
+
const functions = [];
|
|
61
|
+
const lines = content.split('\n');
|
|
62
|
+
|
|
63
|
+
// Simple function detection patterns
|
|
64
|
+
const functionPatterns = [
|
|
65
|
+
/function\s+(\w+)\s*\(/g,
|
|
66
|
+
/const\s+(\w+)\s*=\s*\(/g,
|
|
67
|
+
/(\w+)\s*\(\s*[^)]*\s*\)\s*=>/g,
|
|
68
|
+
/(\w+)\s*:\s*function\s*\(/g
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < lines.length; i++) {
|
|
72
|
+
const line = lines[i];
|
|
73
|
+
|
|
74
|
+
for (const pattern of functionPatterns) {
|
|
75
|
+
const matches = line.matchAll(pattern);
|
|
76
|
+
for (const match of matches) {
|
|
77
|
+
const functionName = match[1];
|
|
78
|
+
const startLine = i + 1;
|
|
79
|
+
|
|
80
|
+
// Extract function body (simple approach)
|
|
81
|
+
const body = this.extractFunctionBody(lines, i);
|
|
82
|
+
|
|
83
|
+
functions.push({
|
|
84
|
+
name: functionName,
|
|
85
|
+
startLine,
|
|
86
|
+
body
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return functions;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
extractFunctionBody(lines, startIndex) {
|
|
96
|
+
let body = '';
|
|
97
|
+
let braceCount = 0;
|
|
98
|
+
let inFunction = false;
|
|
99
|
+
|
|
100
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
101
|
+
const line = lines[i];
|
|
102
|
+
|
|
103
|
+
if (line.includes('{')) {
|
|
104
|
+
braceCount += (line.match(/\{/g) || []).length;
|
|
105
|
+
inFunction = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (inFunction) {
|
|
109
|
+
body += line + '\n';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (line.includes('}')) {
|
|
113
|
+
braceCount -= (line.match(/\}/g) || []).length;
|
|
114
|
+
if (braceCount <= 0 && inFunction) {
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return body;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
countValidationStatements(code) {
|
|
124
|
+
const validationPatterns = [
|
|
125
|
+
/if\s*\(\s*!.*\)\s*\{?\s*throw/g,
|
|
126
|
+
/if\s*\(.*\.\s*length\s*[<>=]\s*\d+\)/g,
|
|
127
|
+
/if\s*\(.*\s*==\s*null\s*\||\s*.*\s*==\s*undefined\)/g,
|
|
128
|
+
/if\s*\(.*\s*!\s*=\s*null\s*&&\s*.*\s*!\s*=\s*undefined\)/g,
|
|
129
|
+
/throw\s+new\s+Error\s*\(/g,
|
|
130
|
+
/assert\s*\(/g,
|
|
131
|
+
/validate\w*\s*\(/g,
|
|
132
|
+
/check\w*\s*\(/g
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
let count = 0;
|
|
136
|
+
for (const pattern of validationPatterns) {
|
|
137
|
+
const matches = code.match(pattern);
|
|
138
|
+
if (matches) {
|
|
139
|
+
count += matches.length;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return count;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
countBusinessLogicStatements(code) {
|
|
147
|
+
const businessLogicPatterns = [
|
|
148
|
+
/calculate\w*\s*\(/g,
|
|
149
|
+
/process\w*\s*\(/g,
|
|
150
|
+
/save\w*\s*\(/g,
|
|
151
|
+
/update\w*\s*\(/g,
|
|
152
|
+
/delete\w*\s*\(/g,
|
|
153
|
+
/send\w*\s*\(/g,
|
|
154
|
+
/return\s+\w+\s*\(/g,
|
|
155
|
+
/await\s+\w+\s*\(/g
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
let count = 0;
|
|
159
|
+
for (const pattern of businessLogicPatterns) {
|
|
160
|
+
const matches = code.match(pattern);
|
|
161
|
+
if (matches) {
|
|
162
|
+
count += matches.length;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return count;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Main analyze method expected by CLI
|
|
170
|
+
async analyze(files, language, config) {
|
|
171
|
+
const violations = [];
|
|
172
|
+
|
|
173
|
+
for (const filePath of files) {
|
|
174
|
+
try {
|
|
175
|
+
const fileViolations = this.analyzeFile(filePath, config);
|
|
176
|
+
violations.push(...fileViolations);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(`Error analyzing file ${filePath}:`, error.message);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return violations;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = new ValidationSeparationAnalyzer();
|