@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,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C030 – Dùng custom error class thay vì dùng lỗi hệ thống trực tiếp
|
|
3
|
+
* Rule ID: custom/c030
|
|
4
|
+
* Purpose: Enforce using custom error classes instead of generic Error class for better error handling and categorization
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const c030Rule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Use custom error classes instead of generic Error class",
|
|
12
|
+
recommended: true
|
|
13
|
+
},
|
|
14
|
+
schema: [
|
|
15
|
+
{
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
allowGenericInTests: {
|
|
19
|
+
type: "boolean",
|
|
20
|
+
description: "Whether to allow generic Error in test files (default: true)"
|
|
21
|
+
},
|
|
22
|
+
allowedBuiltinErrors: {
|
|
23
|
+
type: "array",
|
|
24
|
+
items: { type: "string" },
|
|
25
|
+
description: "Built-in error types that are allowed (e.g., TypeError, RangeError)"
|
|
26
|
+
},
|
|
27
|
+
customErrorClasses: {
|
|
28
|
+
type: "array",
|
|
29
|
+
items: { type: "string" },
|
|
30
|
+
description: "Custom error class names that are recommended"
|
|
31
|
+
},
|
|
32
|
+
allowRethrow: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Whether to allow rethrowing caught errors (default: true)"
|
|
35
|
+
},
|
|
36
|
+
strictMode: {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description: "Enable strict mode - only custom errors allowed (default: false)"
|
|
39
|
+
},
|
|
40
|
+
requireErrorCode: {
|
|
41
|
+
type: "boolean",
|
|
42
|
+
description: "Require custom errors to have error codes (default: true)"
|
|
43
|
+
},
|
|
44
|
+
requireStatusCode: {
|
|
45
|
+
type: "boolean",
|
|
46
|
+
description: "Require custom errors for HTTP to have status codes (default: false)"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
additionalProperties: false
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
messages: {
|
|
53
|
+
useCustomError: "Use custom error class instead of generic 'Error'. Consider using specific error types like ValidationError, NotFoundError, BusinessRuleError, etc. Vietnamese: 'Dùng custom error class thay vì Error generic'",
|
|
54
|
+
useSpecificBuiltin: "Consider using a more specific built-in error type like TypeError, RangeError, or a custom error class. Vietnamese: 'Cân nhắc dùng built-in error cụ thể hơn hoặc custom error class'",
|
|
55
|
+
missingErrorCode: "Custom error class should include an error code property. Vietnamese: 'Custom error class nên có thuộc tính error code'",
|
|
56
|
+
missingStatusCode: "HTTP-related error class should include a status code property. Vietnamese: 'Error class liên quan HTTP nên có thuộc tính status code'",
|
|
57
|
+
preferCustomError: "Prefer custom error classes for better error categorization and handling. Vietnamese: 'Ưu tiên custom error classes để phân loại và xử lý lỗi tốt hơn'"
|
|
58
|
+
},
|
|
59
|
+
fixable: null
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
create(context) {
|
|
63
|
+
const options = context.options[0] || {};
|
|
64
|
+
|
|
65
|
+
// Default configuration
|
|
66
|
+
const allowGenericInTests = options.allowGenericInTests !== false;
|
|
67
|
+
const allowedBuiltinErrors = new Set(options.allowedBuiltinErrors || [
|
|
68
|
+
'TypeError', 'RangeError', 'SyntaxError', 'ReferenceError', 'URIError', 'EvalError'
|
|
69
|
+
]);
|
|
70
|
+
const customErrorClasses = new Set(options.customErrorClasses || [
|
|
71
|
+
'ValidationError', 'NotFoundError', 'BusinessRuleError', 'BusinessError',
|
|
72
|
+
'ExternalServiceError', 'AuthenticationError', 'AuthorizationError',
|
|
73
|
+
'NetworkError', 'DatabaseError', 'ConfigurationError', 'TimeoutError'
|
|
74
|
+
]);
|
|
75
|
+
const allowRethrow = options.allowRethrow !== false;
|
|
76
|
+
const strictMode = options.strictMode || false;
|
|
77
|
+
const requireErrorCode = options.requireErrorCode !== false;
|
|
78
|
+
const requireStatusCode = options.requireStatusCode || false;
|
|
79
|
+
|
|
80
|
+
const sourceCode = context.getSourceCode();
|
|
81
|
+
const filename = context.getFilename();
|
|
82
|
+
|
|
83
|
+
function isTestFile() {
|
|
84
|
+
if (!allowGenericInTests) return false;
|
|
85
|
+
|
|
86
|
+
const testPatterns = ['.test.', '.spec.', '__tests__', '/test/', '/tests/', '.e2e.', '.stories.'];
|
|
87
|
+
return testPatterns.some(pattern => filename.includes(pattern));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isRethrowStatement(node) {
|
|
91
|
+
if (!allowRethrow) return false;
|
|
92
|
+
|
|
93
|
+
// Check if throwing a caught error parameter
|
|
94
|
+
if (node.argument && node.argument.type === 'Identifier') {
|
|
95
|
+
// Look for catch clauses in parent scopes
|
|
96
|
+
let parent = node.parent;
|
|
97
|
+
while (parent) {
|
|
98
|
+
if (parent.type === 'CatchClause' &&
|
|
99
|
+
parent.param &&
|
|
100
|
+
parent.param.name === node.argument.name) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
parent = parent.parent;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getErrorClassName(node) {
|
|
111
|
+
if (!node.argument) return null;
|
|
112
|
+
|
|
113
|
+
if (node.argument.type === 'NewExpression' && node.argument.callee) {
|
|
114
|
+
if (node.argument.callee.type === 'Identifier') {
|
|
115
|
+
return node.argument.callee.name;
|
|
116
|
+
}
|
|
117
|
+
if (node.argument.callee.type === 'MemberExpression' &&
|
|
118
|
+
node.argument.callee.property &&
|
|
119
|
+
node.argument.callee.property.type === 'Identifier') {
|
|
120
|
+
return node.argument.callee.property.name;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isCustomErrorClass(className) {
|
|
128
|
+
if (!className) return false;
|
|
129
|
+
|
|
130
|
+
// Check if it's a known custom error class
|
|
131
|
+
if (customErrorClasses.has(className)) return true;
|
|
132
|
+
|
|
133
|
+
// Check if it follows custom error naming patterns
|
|
134
|
+
const customErrorPatterns = [
|
|
135
|
+
/Error$/, // Ends with 'Error'
|
|
136
|
+
/Exception$/, // Ends with 'Exception'
|
|
137
|
+
/Failure$/, // Ends with 'Failure'
|
|
138
|
+
/Fault$/ // Ends with 'Fault'
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
return customErrorPatterns.some(pattern =>
|
|
142
|
+
pattern.test(className) &&
|
|
143
|
+
!allowedBuiltinErrors.has(className) &&
|
|
144
|
+
className !== 'Error'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function checkErrorClassDefinition(node) {
|
|
149
|
+
// Check if custom error class has required properties
|
|
150
|
+
if (node.type === 'ClassDeclaration' &&
|
|
151
|
+
node.id &&
|
|
152
|
+
isCustomErrorClass(node.id.name)) {
|
|
153
|
+
|
|
154
|
+
const className = node.id.name;
|
|
155
|
+
const classBody = node.body.body;
|
|
156
|
+
|
|
157
|
+
if (requireErrorCode) {
|
|
158
|
+
const hasErrorCode = classBody.some(member => {
|
|
159
|
+
if (member.type === 'PropertyDefinition' || member.type === 'ClassProperty') {
|
|
160
|
+
return member.key && member.key.name === 'code';
|
|
161
|
+
}
|
|
162
|
+
if (member.type === 'MethodDefinition' && member.kind === 'constructor') {
|
|
163
|
+
// Check if constructor sets error code
|
|
164
|
+
const constructorBody = member.value.body.body;
|
|
165
|
+
return constructorBody.some(stmt => {
|
|
166
|
+
if (stmt.type === 'ExpressionStatement' &&
|
|
167
|
+
stmt.expression.type === 'AssignmentExpression' &&
|
|
168
|
+
stmt.expression.left.type === 'MemberExpression' &&
|
|
169
|
+
stmt.expression.left.property.name === 'code') {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (!hasErrorCode) {
|
|
179
|
+
context.report({
|
|
180
|
+
node: node.id,
|
|
181
|
+
messageId: "missingErrorCode"
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (requireStatusCode && /http|api|web|service/i.test(className.toLowerCase())) {
|
|
187
|
+
const hasStatusCode = classBody.some(member => {
|
|
188
|
+
if (member.type === 'PropertyDefinition' || member.type === 'ClassProperty') {
|
|
189
|
+
return member.key && (member.key.name === 'statusCode' || member.key.name === 'status');
|
|
190
|
+
}
|
|
191
|
+
if (member.type === 'MethodDefinition' && member.kind === 'constructor') {
|
|
192
|
+
// Check if constructor sets status code
|
|
193
|
+
const constructorBody = member.value.body.body;
|
|
194
|
+
return constructorBody.some(stmt => {
|
|
195
|
+
if (stmt.type === 'ExpressionStatement' &&
|
|
196
|
+
stmt.expression.type === 'AssignmentExpression' &&
|
|
197
|
+
stmt.expression.left.type === 'MemberExpression' &&
|
|
198
|
+
(stmt.expression.left.property.name === 'statusCode' ||
|
|
199
|
+
stmt.expression.left.property.name === 'status')) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (!hasStatusCode) {
|
|
209
|
+
context.report({
|
|
210
|
+
node: node.id,
|
|
211
|
+
messageId: "missingStatusCode"
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
ThrowStatement(node) {
|
|
220
|
+
// Skip test files if allowed
|
|
221
|
+
if (isTestFile()) return;
|
|
222
|
+
|
|
223
|
+
// Skip rethrow statements if allowed
|
|
224
|
+
if (isRethrowStatement(node)) return;
|
|
225
|
+
|
|
226
|
+
const errorClassName = getErrorClassName(node);
|
|
227
|
+
|
|
228
|
+
// Check for generic Error usage
|
|
229
|
+
if (errorClassName === 'Error') {
|
|
230
|
+
context.report({
|
|
231
|
+
node: node.argument,
|
|
232
|
+
messageId: "useCustomError"
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// In strict mode, only custom errors are allowed
|
|
238
|
+
if (strictMode && errorClassName) {
|
|
239
|
+
if (!isCustomErrorClass(errorClassName) && !allowedBuiltinErrors.has(errorClassName)) {
|
|
240
|
+
context.report({
|
|
241
|
+
node: node.argument,
|
|
242
|
+
messageId: "preferCustomError"
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Check for other built-in errors that could be more specific
|
|
248
|
+
if (allowedBuiltinErrors.has(errorClassName) && !strictMode) {
|
|
249
|
+
// Only suggest if it's a generic built-in error
|
|
250
|
+
if (['TypeError', 'RangeError'].includes(errorClassName)) {
|
|
251
|
+
context.report({
|
|
252
|
+
node: node.argument,
|
|
253
|
+
messageId: "useSpecificBuiltin"
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
ClassDeclaration(node) {
|
|
260
|
+
checkErrorClassDefinition(node);
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
// Check for Promise.reject with generic Error
|
|
264
|
+
'CallExpression[callee.type="MemberExpression"][callee.object.name="Promise"][callee.property.name="reject"]'(node) {
|
|
265
|
+
if (isTestFile()) return;
|
|
266
|
+
|
|
267
|
+
const arg = node.arguments[0];
|
|
268
|
+
if (arg && arg.type === 'NewExpression' &&
|
|
269
|
+
arg.callee && arg.callee.name === 'Error') {
|
|
270
|
+
context.report({
|
|
271
|
+
node: arg,
|
|
272
|
+
messageId: "useCustomError"
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
// Check for async function error throwing
|
|
278
|
+
'AwaitExpression > CallExpression[callee.type="MemberExpression"][callee.property.name="reject"]'(node) {
|
|
279
|
+
if (isTestFile()) return;
|
|
280
|
+
|
|
281
|
+
const arg = node.arguments[0];
|
|
282
|
+
if (arg && arg.type === 'NewExpression' &&
|
|
283
|
+
arg.callee && arg.callee.name === 'Error') {
|
|
284
|
+
context.report({
|
|
285
|
+
node: arg,
|
|
286
|
+
messageId: "useCustomError"
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
module.exports = c030Rule;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C034 – Do not use implicit return in arrow functions
|
|
3
|
+
* Rule ID: custom/c034
|
|
4
|
+
* Goal: Avoid using arrow functions with implicit return as they can be hard to read, use explicit return with braces
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Do not use implicit return in arrow functions",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
implicitReturn: "Do not use implicit return. Use explicit return with {{ braces }}."
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
return {
|
|
21
|
+
ArrowFunctionExpression(node) {
|
|
22
|
+
if (node.body && node.body.type !== "BlockStatement") {
|
|
23
|
+
context.report({
|
|
24
|
+
node,
|
|
25
|
+
messageId: "implicitReturn",
|
|
26
|
+
data: {
|
|
27
|
+
braces: "{ ... }"
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C035 – No empty catch blocks (without error handling or logging)
|
|
3
|
+
* Rule ID: custom/c035
|
|
4
|
+
* Purpose: Prevent silently swallowing errors in catch blocks without logging or handling them
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "No empty catch blocks (without error handling or logging)",
|
|
12
|
+
recommended: true
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
emptyCatch: "Empty catch blocks are not allowed. Errors should be logged or handled explicitly."
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
return {
|
|
21
|
+
CatchClause(node) {
|
|
22
|
+
const body = node.body && node.body.body;
|
|
23
|
+
if (Array.isArray(body) && body.length === 0) {
|
|
24
|
+
context.report({
|
|
25
|
+
node,
|
|
26
|
+
messageId: "emptyCatch"
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C041 – Do not hardcode constants scattered throughout logic
|
|
3
|
+
* Rule ID: custom/c041
|
|
4
|
+
* Purpose: Enforce centralized configuration management by preventing scattered constant declarations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Do not hardcode constants scattered throughout logic",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
inlineConfig: "Do not hardcode configuration values. Move them to a centralized config file."
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
const suspiciousPatterns = [
|
|
21
|
+
/password/i,
|
|
22
|
+
/secret/i,
|
|
23
|
+
/api[_-]?key/i,
|
|
24
|
+
/auth[_-]?token/i,
|
|
25
|
+
/access[_-]?token/i,
|
|
26
|
+
/localhost/i,
|
|
27
|
+
/127\.0\.0\.1/,
|
|
28
|
+
/http:\/\//i,
|
|
29
|
+
/https:\/\//i,
|
|
30
|
+
/db[_-]?(url|uri|name|conn)/i,
|
|
31
|
+
/conn(ect)?ion[_-]?string/i
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
function reportIfHardcoded(node, value) {
|
|
35
|
+
if (typeof value !== "string") return;
|
|
36
|
+
for (const pattern of suspiciousPatterns) {
|
|
37
|
+
if (pattern.test(value)) {
|
|
38
|
+
context.report({
|
|
39
|
+
node,
|
|
40
|
+
messageId: "inlineConfig",
|
|
41
|
+
data: { value }
|
|
42
|
+
});
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
Literal(node) {
|
|
50
|
+
if (typeof node.value === "string") {
|
|
51
|
+
reportIfHardcoded(node, node.value);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
TemplateLiteral(node) {
|
|
55
|
+
if (
|
|
56
|
+
node.quasis.length === 1 &&
|
|
57
|
+
typeof node.quasis[0].value.raw === "string"
|
|
58
|
+
) {
|
|
59
|
+
reportIfHardcoded(node, node.quasis[0].value.raw);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
};
|