@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,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C018 – Do not throw generic errors, always use specific messages
|
|
3
|
+
* Rule ID: custom/c018
|
|
4
|
+
* Purpose: Enforce specific error messages when throwing errors to improve debugging and error handling
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const c018Rule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Do not throw generic errors, always use specific messages",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [
|
|
15
|
+
{
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
allowGenericInTests: {
|
|
19
|
+
type: "boolean",
|
|
20
|
+
description: "Whether to allow generic throws in test files (default: true)"
|
|
21
|
+
},
|
|
22
|
+
allowRethrow: {
|
|
23
|
+
type: "boolean",
|
|
24
|
+
description: "Whether to allow rethrowing caught errors (default: true)"
|
|
25
|
+
},
|
|
26
|
+
allowThrowVariable: {
|
|
27
|
+
type: "boolean",
|
|
28
|
+
description: "Whether to allow throwing variables/identifiers (default: false)"
|
|
29
|
+
},
|
|
30
|
+
requiredMessagePatterns: {
|
|
31
|
+
type: "array",
|
|
32
|
+
items: { type: "string" },
|
|
33
|
+
description: "Regex patterns that error messages must match"
|
|
34
|
+
},
|
|
35
|
+
minimumMessageLength: {
|
|
36
|
+
type: "number",
|
|
37
|
+
description: "Minimum length for error messages (default: 10)"
|
|
38
|
+
},
|
|
39
|
+
allowedGenericMessages: {
|
|
40
|
+
type: "array",
|
|
41
|
+
items: { type: "string" },
|
|
42
|
+
description: "List of allowed generic messages"
|
|
43
|
+
},
|
|
44
|
+
customErrorClasses: {
|
|
45
|
+
type: "array",
|
|
46
|
+
items: { type: "string" },
|
|
47
|
+
description: "Custom error class names that are allowed"
|
|
48
|
+
},
|
|
49
|
+
strictMode: {
|
|
50
|
+
type: "boolean",
|
|
51
|
+
description: "Enable strict mode with additional checks (default: false)"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
additionalProperties: false
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
messages: {
|
|
58
|
+
genericError: "Do not throw generic errors. Provide a specific error message.",
|
|
59
|
+
emptyError: "Error message cannot be empty. Provide a specific error message.",
|
|
60
|
+
messageToolShort: "Error message too short (minimum {{minLength}} characters). Vietnamese: 'Message error quá ngắn (tối thiểu {{minLength}} ký tự)'",
|
|
61
|
+
genericErrorMessage: "Generic error message '{{message}}' should be more specific. Vietnamese: 'Message error generic nên cụ thể hơn'",
|
|
62
|
+
throwWithoutMessage: "Throw statement must include a specific error message. Vietnamese: 'Throw statement phải có message error cụ thể'",
|
|
63
|
+
useSpecificErrorClass: "Use specific error class instead of generic Error. Vietnamese: 'Dùng error class cụ thể thay vì Error generic'",
|
|
64
|
+
invalidMessagePattern: "Error message doesn't match required patterns. Vietnamese: 'Message error không khớp pattern yêu cầu'",
|
|
65
|
+
throwBareString: "Throwing bare string is not recommended, use Error object with message. Vietnamese: 'Throw string trực tiếp không khuyến khích, dùng Error object với message'"
|
|
66
|
+
},
|
|
67
|
+
fixable: null
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
create(context) {
|
|
71
|
+
const options = context.options[0] || {};
|
|
72
|
+
|
|
73
|
+
// Default configuration
|
|
74
|
+
const allowGenericInTests = options.allowGenericInTests !== false;
|
|
75
|
+
const allowRethrow = options.allowRethrow !== false;
|
|
76
|
+
const allowThrowVariable = options.allowThrowVariable || false;
|
|
77
|
+
const requiredMessagePatterns = options.requiredMessagePatterns || [];
|
|
78
|
+
const minimumMessageLength = options.minimumMessageLength || 10;
|
|
79
|
+
const allowedGenericMessages = new Set(options.allowedGenericMessages || []);
|
|
80
|
+
const customErrorClasses = new Set(options.customErrorClasses || ['ValidationError', 'BusinessError', 'NetworkError', 'AuthenticationError', 'AuthorizationError']);
|
|
81
|
+
const strictMode = options.strictMode || false;
|
|
82
|
+
|
|
83
|
+
const sourceCode = context.getSourceCode();
|
|
84
|
+
const filename = context.getFilename();
|
|
85
|
+
|
|
86
|
+
// Generic error messages to detect
|
|
87
|
+
const genericMessages = new Set([
|
|
88
|
+
'error',
|
|
89
|
+
'Error',
|
|
90
|
+
'ERROR',
|
|
91
|
+
'something went wrong',
|
|
92
|
+
'something failed',
|
|
93
|
+
'operation failed',
|
|
94
|
+
'invalid',
|
|
95
|
+
'invalid input',
|
|
96
|
+
'bad input',
|
|
97
|
+
'error occurred',
|
|
98
|
+
'an error occurred',
|
|
99
|
+
'failed',
|
|
100
|
+
'failure',
|
|
101
|
+
'exception',
|
|
102
|
+
'unexpected error',
|
|
103
|
+
'internal error',
|
|
104
|
+
'system error',
|
|
105
|
+
'unknown error'
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
function isTestFile() {
|
|
109
|
+
if (!allowGenericInTests) return false;
|
|
110
|
+
|
|
111
|
+
const testPatterns = ['.test.', '.spec.', '__tests__', '/test/', '/tests/', '.e2e.'];
|
|
112
|
+
return testPatterns.some(pattern => filename.includes(pattern));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isRethrowStatement(node) {
|
|
116
|
+
if (!allowRethrow) return false;
|
|
117
|
+
|
|
118
|
+
// Check if throwing a caught error parameter
|
|
119
|
+
if (node.argument && node.argument.type === 'Identifier') {
|
|
120
|
+
// Look for catch clauses in parent scopes
|
|
121
|
+
let parent = node.parent;
|
|
122
|
+
while (parent) {
|
|
123
|
+
if (parent.type === 'CatchClause' &&
|
|
124
|
+
parent.param &&
|
|
125
|
+
parent.param.name === node.argument.name) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
parent = parent.parent;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getErrorMessage(node) {
|
|
136
|
+
if (!node.argument) return null;
|
|
137
|
+
|
|
138
|
+
// Direct string literal
|
|
139
|
+
if (node.argument.type === 'Literal') {
|
|
140
|
+
return node.argument.value;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// new Error("message")
|
|
144
|
+
if (node.argument.type === 'NewExpression' &&
|
|
145
|
+
node.argument.callee &&
|
|
146
|
+
node.argument.arguments &&
|
|
147
|
+
node.argument.arguments.length > 0) {
|
|
148
|
+
const firstArg = node.argument.arguments[0];
|
|
149
|
+
if (firstArg.type === 'Literal') {
|
|
150
|
+
return firstArg.value;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getErrorClassName(node) {
|
|
158
|
+
if (node.argument &&
|
|
159
|
+
node.argument.type === 'NewExpression' &&
|
|
160
|
+
node.argument.callee) {
|
|
161
|
+
if (node.argument.callee.type === 'Identifier') {
|
|
162
|
+
return node.argument.callee.name;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function isGenericErrorMessage(message) {
|
|
169
|
+
if (typeof message !== 'string') return false;
|
|
170
|
+
|
|
171
|
+
const normalizedMessage = message.toLowerCase().trim();
|
|
172
|
+
return genericMessages.has(normalizedMessage) ||
|
|
173
|
+
genericMessages.has(message.trim());
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isMessageTooShort(message) {
|
|
177
|
+
if (typeof message !== 'string') return false;
|
|
178
|
+
return message.trim().length < minimumMessageLength;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function matchesRequiredPatterns(message) {
|
|
182
|
+
if (requiredMessagePatterns.length === 0) return true;
|
|
183
|
+
if (typeof message !== 'string') return false;
|
|
184
|
+
|
|
185
|
+
return requiredMessagePatterns.some(pattern => {
|
|
186
|
+
try {
|
|
187
|
+
const regex = new RegExp(pattern);
|
|
188
|
+
return regex.test(message);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isAllowedGenericMessage(message) {
|
|
196
|
+
if (typeof message !== 'string') return false;
|
|
197
|
+
return allowedGenericMessages.has(message.trim());
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function checkThrowStatement(node) {
|
|
201
|
+
// Skip if in test file and allowed
|
|
202
|
+
if (isTestFile()) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Skip if this is a rethrow
|
|
207
|
+
if (isRethrowStatement(node)) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check for throw without argument
|
|
212
|
+
if (!node.argument) {
|
|
213
|
+
context.report({
|
|
214
|
+
node,
|
|
215
|
+
messageId: 'throwWithoutMessage'
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check for throwing variables/identifiers
|
|
221
|
+
if (node.argument.type === 'Identifier' && !allowThrowVariable) {
|
|
222
|
+
context.report({
|
|
223
|
+
node,
|
|
224
|
+
messageId: 'genericError'
|
|
225
|
+
});
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check for throwing bare strings
|
|
230
|
+
if (node.argument.type === 'Literal' && typeof node.argument.value === 'string') {
|
|
231
|
+
if (strictMode) {
|
|
232
|
+
context.report({
|
|
233
|
+
node,
|
|
234
|
+
messageId: 'throwBareString'
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const message = node.argument.value;
|
|
240
|
+
validateMessage(node, message);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Check for new Error() constructions
|
|
245
|
+
if (node.argument.type === 'NewExpression') {
|
|
246
|
+
const errorClassName = getErrorClassName(node);
|
|
247
|
+
const errorMessage = getErrorMessage(node);
|
|
248
|
+
|
|
249
|
+
// Check error class
|
|
250
|
+
if (strictMode && errorClassName === 'Error') {
|
|
251
|
+
context.report({
|
|
252
|
+
node,
|
|
253
|
+
messageId: 'useSpecificErrorClass'
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check error message
|
|
258
|
+
if (errorMessage !== null) {
|
|
259
|
+
validateMessage(node, errorMessage);
|
|
260
|
+
} else if (node.argument.arguments.length === 0) {
|
|
261
|
+
context.report({
|
|
262
|
+
node,
|
|
263
|
+
messageId: 'throwWithoutMessage'
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Generic throw statement
|
|
270
|
+
context.report({
|
|
271
|
+
node,
|
|
272
|
+
messageId: 'genericError'
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function validateMessage(node, message) {
|
|
277
|
+
if (!message || typeof message !== 'string') {
|
|
278
|
+
context.report({
|
|
279
|
+
node,
|
|
280
|
+
messageId: 'throwWithoutMessage'
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Check for empty or whitespace-only message
|
|
286
|
+
if (message.trim() === '') {
|
|
287
|
+
context.report({
|
|
288
|
+
node,
|
|
289
|
+
messageId: 'emptyError'
|
|
290
|
+
});
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Check if message is allowed generic
|
|
295
|
+
if (isAllowedGenericMessage(message)) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Check for generic messages
|
|
300
|
+
if (isGenericErrorMessage(message)) {
|
|
301
|
+
context.report({
|
|
302
|
+
node,
|
|
303
|
+
messageId: 'genericErrorMessage',
|
|
304
|
+
data: { message: message.trim() }
|
|
305
|
+
});
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Check message length
|
|
310
|
+
if (isMessageTooShort(message)) {
|
|
311
|
+
context.report({
|
|
312
|
+
node,
|
|
313
|
+
messageId: 'messageToolShort',
|
|
314
|
+
data: { minLength: minimumMessageLength }
|
|
315
|
+
});
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Check required patterns
|
|
320
|
+
if (!matchesRequiredPatterns(message)) {
|
|
321
|
+
context.report({
|
|
322
|
+
node,
|
|
323
|
+
messageId: 'invalidMessagePattern'
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
ThrowStatement: checkThrowStatement
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
module.exports = c018Rule;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C023 – Do not use duplicate variable names in the same scope
|
|
3
|
+
* Rule ID: custom/c023
|
|
4
|
+
* Purpose: Prevent variable name shadowing and maintain clear variable scoping
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const c023Rule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Do not use duplicate variable names in the same scope",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
duplicateVariable: "Variable '{{name}}' is already declared in this scope"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
const scopeStack = [];
|
|
21
|
+
const variableMap = new Map();
|
|
22
|
+
|
|
23
|
+
function enterScope() {
|
|
24
|
+
scopeStack.push(new Map());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function exitScope() {
|
|
28
|
+
const scope = scopeStack.pop();
|
|
29
|
+
// Clean up variables from the exited scope
|
|
30
|
+
for (const [name, info] of scope) {
|
|
31
|
+
const globalInfo = variableMap.get(name);
|
|
32
|
+
if (globalInfo) {
|
|
33
|
+
globalInfo.scopes.delete(scope);
|
|
34
|
+
if (globalInfo.scopes.size === 0) {
|
|
35
|
+
variableMap.delete(name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function checkVariable(node, name) {
|
|
42
|
+
if (scopeStack.length === 0) return;
|
|
43
|
+
|
|
44
|
+
const currentScope = scopeStack[scopeStack.length - 1];
|
|
45
|
+
|
|
46
|
+
// Check if variable is already declared in current scope
|
|
47
|
+
if (currentScope.has(name)) {
|
|
48
|
+
context.report({
|
|
49
|
+
node,
|
|
50
|
+
messageId: "duplicateVariable",
|
|
51
|
+
data: { name }
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Add variable to current scope
|
|
57
|
+
currentScope.set(name, {
|
|
58
|
+
node,
|
|
59
|
+
scopes: new Set([currentScope])
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Update global variable map
|
|
63
|
+
if (!variableMap.has(name)) {
|
|
64
|
+
variableMap.set(name, {
|
|
65
|
+
scopes: new Set([currentScope])
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
variableMap.get(name).scopes.add(currentScope);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
Program() {
|
|
74
|
+
enterScope();
|
|
75
|
+
},
|
|
76
|
+
"Program:exit"() {
|
|
77
|
+
exitScope();
|
|
78
|
+
},
|
|
79
|
+
FunctionDeclaration() {
|
|
80
|
+
enterScope();
|
|
81
|
+
},
|
|
82
|
+
"FunctionDeclaration:exit"() {
|
|
83
|
+
exitScope();
|
|
84
|
+
},
|
|
85
|
+
FunctionExpression() {
|
|
86
|
+
enterScope();
|
|
87
|
+
},
|
|
88
|
+
"FunctionExpression:exit"() {
|
|
89
|
+
exitScope();
|
|
90
|
+
},
|
|
91
|
+
ArrowFunctionExpression() {
|
|
92
|
+
enterScope();
|
|
93
|
+
},
|
|
94
|
+
"ArrowFunctionExpression:exit"() {
|
|
95
|
+
exitScope();
|
|
96
|
+
},
|
|
97
|
+
BlockStatement() {
|
|
98
|
+
enterScope();
|
|
99
|
+
},
|
|
100
|
+
"BlockStatement:exit"() {
|
|
101
|
+
exitScope();
|
|
102
|
+
},
|
|
103
|
+
CatchClause() {
|
|
104
|
+
enterScope();
|
|
105
|
+
},
|
|
106
|
+
"CatchClause:exit"() {
|
|
107
|
+
exitScope();
|
|
108
|
+
},
|
|
109
|
+
ForStatement() {
|
|
110
|
+
enterScope();
|
|
111
|
+
},
|
|
112
|
+
"ForStatement:exit"() {
|
|
113
|
+
exitScope();
|
|
114
|
+
},
|
|
115
|
+
ForInStatement() {
|
|
116
|
+
enterScope();
|
|
117
|
+
},
|
|
118
|
+
"ForInStatement:exit"() {
|
|
119
|
+
exitScope();
|
|
120
|
+
},
|
|
121
|
+
ForOfStatement() {
|
|
122
|
+
enterScope();
|
|
123
|
+
},
|
|
124
|
+
"ForOfStatement:exit"() {
|
|
125
|
+
exitScope();
|
|
126
|
+
},
|
|
127
|
+
SwitchStatement() {
|
|
128
|
+
enterScope();
|
|
129
|
+
},
|
|
130
|
+
"SwitchStatement:exit"() {
|
|
131
|
+
exitScope();
|
|
132
|
+
},
|
|
133
|
+
VariableDeclarator(node) {
|
|
134
|
+
if (node.id.type === "Identifier") {
|
|
135
|
+
checkVariable(node, node.id.name);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
module.exports = c023Rule;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C027 – Avoid deeply nested functions (maximum 2 levels)
|
|
3
|
+
* Rule ID: custom/c027
|
|
4
|
+
* Goal: Limit function nesting to maximum 2 levels to improve readability and maintainability
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Avoid deeply nested functions (maximum 2 levels)",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
tooDeep: "Function is nested too deeply (level {{depth}}). Should be maximum 2 levels."
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
let functionStack = [];
|
|
21
|
+
|
|
22
|
+
function enterFunction(node) {
|
|
23
|
+
functionStack.push(node);
|
|
24
|
+
const depth = functionStack.length;
|
|
25
|
+
|
|
26
|
+
if (depth > 2) {
|
|
27
|
+
context.report({
|
|
28
|
+
node,
|
|
29
|
+
messageId: "tooDeep",
|
|
30
|
+
data: {
|
|
31
|
+
depth
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function exitFunction() {
|
|
38
|
+
functionStack.pop();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
FunctionDeclaration: enterFunction,
|
|
43
|
+
FunctionExpression: enterFunction,
|
|
44
|
+
ArrowFunctionExpression: enterFunction,
|
|
45
|
+
'FunctionDeclaration:exit': exitFunction,
|
|
46
|
+
'FunctionExpression:exit': exitFunction,
|
|
47
|
+
'ArrowFunctionExpression:exit': exitFunction
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C029 – Every `catch` block must log the error cause
|
|
3
|
+
* Rule ID: custom/c029
|
|
4
|
+
* Goal: Catching errors without logging/rethrowing can hide bugs
|
|
5
|
+
*
|
|
6
|
+
* NOTE: This ESLint rule provides basic catch block validation.
|
|
7
|
+
* For enhanced analysis with context validation and multi-level severity,
|
|
8
|
+
* use SunLint C029 which offers superior detection capabilities.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
docs: {
|
|
15
|
+
description: "Every `catch` block must log the error cause (C029)",
|
|
16
|
+
recommended: true,
|
|
17
|
+
url: "https://coding-standards.sun.com/rules/c029"
|
|
18
|
+
},
|
|
19
|
+
schema: [],
|
|
20
|
+
messages: {
|
|
21
|
+
silentCatch: "Catch block must log error or rethrow - silent error handling hides bugs (C029)",
|
|
22
|
+
emptyCatch: "Empty catch block - error is silently ignored (C029)"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
return {
|
|
27
|
+
CatchClause(node) {
|
|
28
|
+
const body = node.body && node.body.body;
|
|
29
|
+
|
|
30
|
+
// Check for empty catch blocks
|
|
31
|
+
if (!Array.isArray(body) || body.length === 0) {
|
|
32
|
+
context.report({
|
|
33
|
+
node,
|
|
34
|
+
messageId: "emptyCatch"
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const hasLogOrThrow = body.some(stmt => {
|
|
40
|
+
// Check for throw statements
|
|
41
|
+
if (stmt.type === "ThrowStatement") {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check for console.log, console.error, console.warn
|
|
46
|
+
if (stmt.type === "ExpressionStatement" &&
|
|
47
|
+
stmt.expression.type === "CallExpression" &&
|
|
48
|
+
stmt.expression.callee &&
|
|
49
|
+
stmt.expression.callee.type === "MemberExpression" &&
|
|
50
|
+
stmt.expression.callee.object.name === "console" &&
|
|
51
|
+
(stmt.expression.callee.property.name === "log" ||
|
|
52
|
+
stmt.expression.callee.property.name === "error" ||
|
|
53
|
+
stmt.expression.callee.property.name === "warn")) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check for custom logger calls (logger.error, log.error, etc.)
|
|
58
|
+
if (stmt.type === "ExpressionStatement" &&
|
|
59
|
+
stmt.expression.type === "CallExpression" &&
|
|
60
|
+
stmt.expression.callee &&
|
|
61
|
+
stmt.expression.callee.type === "MemberExpression" &&
|
|
62
|
+
(stmt.expression.callee.property.name === "error" ||
|
|
63
|
+
stmt.expression.callee.property.name === "warn" ||
|
|
64
|
+
stmt.expression.callee.property.name === "log")) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return false;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!hasLogOrThrow) {
|
|
72
|
+
context.report({
|
|
73
|
+
node,
|
|
74
|
+
messageId: "silentCatch"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|