@sun-asterisk/sunlint 1.0.7 → 1.1.4
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/.sunlint.json +35 -0
- package/CHANGELOG.md +30 -3
- package/CONTRIBUTING.md +235 -0
- package/PROJECT_STRUCTURE.md +60 -0
- package/README.md +146 -58
- package/cli.js +1 -0
- package/config/README.md +88 -0
- package/config/defaults/ai-rules-context.json +231 -0
- package/config/engines/engines.json +49 -0
- package/config/engines/eslint-rule-mapping.json +74 -0
- package/config/eslint-rule-mapping.json +126 -0
- package/config/integrations/eslint/base.config.js +125 -0
- package/config/integrations/eslint/simple.config.js +24 -0
- package/config/presets/strict.json +0 -1
- package/config/rule-analysis-strategies.js +74 -0
- package/config/{rules-registry.json → rules/rules-registry.json} +30 -7
- package/core/analysis-orchestrator.js +383 -591
- package/core/ast-modules/README.md +103 -0
- package/core/ast-modules/base-parser.js +90 -0
- package/core/ast-modules/index.js +97 -0
- package/core/ast-modules/package.json +37 -0
- package/core/ast-modules/parsers/eslint-js-parser.js +153 -0
- package/core/ast-modules/parsers/eslint-ts-parser.js +98 -0
- package/core/ast-modules/parsers/javascript-parser.js +187 -0
- package/core/ast-modules/parsers/typescript-parser.js +187 -0
- package/core/cli-action-handler.js +271 -255
- package/core/cli-program.js +18 -4
- package/core/config-manager.js +9 -3
- package/core/config-merger.js +40 -1
- package/core/config-validator.js +2 -2
- package/core/dependency-checker.js +125 -0
- package/core/enhanced-rules-registry.js +331 -0
- package/core/file-targeting-service.js +92 -23
- package/core/interfaces/analysis-engine.interface.js +100 -0
- package/core/multi-rule-runner.js +0 -221
- package/core/output-service.js +1 -1
- package/core/rule-mapping-service.js +1 -1
- package/core/rule-selection-service.js +10 -2
- package/core/smart-installer.js +164 -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/CONFIGURATION.md +414 -0
- package/docs/DEBUG.md +86 -0
- package/docs/DEPENDENCIES.md +90 -0
- package/docs/DEPLOYMENT-STRATEGIES.md +270 -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/FUTURE_PACKAGES.md +83 -0
- package/docs/HEURISTIC_VS_AI.md +113 -0
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +112 -0
- package/docs/PRODUCTION_SIZE_IMPACT.md +183 -0
- package/docs/README.md +32 -0
- package/docs/RELEASE_GUIDE.md +230 -0
- package/engines/eslint-engine.js +610 -0
- package/engines/heuristic-engine.js +864 -0
- package/engines/openai-engine.js +374 -0
- package/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- package/integrations/eslint/README.md +99 -0
- package/integrations/eslint/configs/.eslintrc.js +98 -0
- package/integrations/eslint/configs/eslint.config.js +133 -0
- package/integrations/eslint/configs/eslint.config.simple.js +24 -0
- package/integrations/eslint/package.json +23 -0
- package/integrations/eslint/plugin/index.js +164 -0
- package/integrations/eslint/plugin/package.json +13 -0
- package/integrations/eslint/plugin/rules/common/c002-no-duplicate-code.js +204 -0
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +246 -0
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +216 -0
- package/integrations/eslint/plugin/rules/common/c010-limit-block-nesting.js +90 -0
- package/integrations/eslint/plugin/rules/common/c013-no-dead-code.js +78 -0
- package/integrations/eslint/plugin/rules/common/c014-abstract-dependency-preferred.js +38 -0
- package/integrations/eslint/plugin/rules/common/c017-limit-constructor-logic.js +146 -0
- package/integrations/eslint/plugin/rules/common/c018-no-generic-throw.js +335 -0
- package/integrations/eslint/plugin/rules/common/c023-no-duplicate-variable-name-in-scope.js +142 -0
- package/integrations/eslint/plugin/rules/common/c029-catch-block-logging.js +115 -0
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +294 -0
- package/integrations/eslint/plugin/rules/common/c035-no-empty-catch.js +162 -0
- package/integrations/eslint/plugin/rules/common/c041-no-config-inline.js +122 -0
- package/integrations/eslint/plugin/rules/common/c042-boolean-name-prefix.js +406 -0
- package/integrations/eslint/plugin/rules/common/c043-no-console-or-print.js +300 -0
- package/integrations/eslint/plugin/rules/common/c047-no-duplicate-retry-logic.js +239 -0
- package/integrations/eslint/plugin/rules/common/c072-one-assert-per-test.js +184 -0
- package/integrations/eslint/plugin/rules/common/c075-explicit-function-return-types.js +168 -0
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +254 -0
- package/integrations/eslint/plugin/rules/security/s001-fail-securely.js +381 -0
- package/integrations/eslint/plugin/rules/security/s002-idor-check.js +945 -0
- package/integrations/eslint/plugin/rules/security/s003-no-unvalidated-redirect.js +86 -0
- package/integrations/eslint/plugin/rules/security/s007-no-plaintext-otp.js +74 -0
- package/integrations/eslint/plugin/rules/security/s013-verify-tls-connection.js +47 -0
- package/integrations/eslint/plugin/rules/security/s047-secure-random-passwords.js +108 -0
- package/integrations/eslint/plugin/rules/security/s055-verification-rest-check-the-incoming-content-type.js +143 -0
- package/integrations/eslint/plugin/rules/typescript/t002-interface-prefix-i.js +42 -0
- package/integrations/eslint/plugin/rules/typescript/t003-ts-ignore-reason.js +48 -0
- package/integrations/eslint/plugin/rules/typescript/t004-no-empty-type.js +95 -0
- package/integrations/eslint/plugin/rules/typescript/t007-no-fn-in-constructor.js +52 -0
- package/integrations/eslint/plugin/rules/typescript/t010-no-nested-union-tuple.js +48 -0
- package/integrations/eslint/plugin/rules/typescript/t019-no-this-assign.js +81 -0
- package/integrations/eslint/plugin/rules/typescript/t020-no-default-multi-export.js +127 -0
- package/integrations/eslint/plugin/rules/typescript/t021-limit-nested-generics.js +150 -0
- package/integrations/eslint/tsconfig.json +27 -0
- package/package.json +61 -21
- package/rules/README.md +252 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
- package/rules/common/C002_no_duplicate_code/config.json +23 -0
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
- package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
- package/rules/{C006_function_naming → common/C006_function_naming}/analyzer.js +13 -2
- package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
- package/rules/common/C013_no_dead_code/analyzer.js +206 -0
- package/rules/common/C014_dependency_injection/analyzer.js +338 -0
- package/rules/common/C017_constructor_logic/analyzer.js +314 -0
- package/rules/{C019_log_level_usage → common/C019_log_level_usage}/analyzer.js +5 -2
- package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/analyzer.js +49 -15
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
- package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
- package/rules/common/C043_no_console_or_print/analyzer.js +304 -0
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +351 -0
- package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
- package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
- package/rules/docs/C002_no_duplicate_code.md +57 -0
- package/rules/index.js +149 -0
- package/rules/migration/converter.js +385 -0
- package/rules/migration/mapping.json +164 -0
- package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
- package/rules/security/S026_json_schema_validation/config.json +27 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +263 -0
- package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
- package/rules/security/S029_csrf_protection/analyzer.js +264 -0
- package/rules/tests/C002_no_duplicate_code.test.js +50 -0
- package/rules/universal/C010/generic.js +0 -0
- package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
- package/rules/utils/ast-utils.js +191 -0
- package/rules/utils/base-analyzer.js +98 -0
- package/rules/utils/pattern-matchers.js +239 -0
- package/rules/utils/rule-helpers.js +264 -0
- package/rules/utils/severity-constants.js +93 -0
- package/scripts/build-release.sh +117 -0
- package/scripts/ci-report.js +179 -0
- package/scripts/install.sh +196 -0
- package/scripts/manual-release.sh +338 -0
- package/scripts/merge-reports.js +424 -0
- package/scripts/pre-release-test.sh +175 -0
- package/scripts/prepare-release.sh +202 -0
- package/scripts/setup-github-registry.sh +42 -0
- package/scripts/test-scripts/README.md +22 -0
- package/scripts/test-scripts/test-c041-comparison.js +114 -0
- package/scripts/test-scripts/test-c041-eslint.js +67 -0
- package/scripts/test-scripts/test-eslint-rules.js +146 -0
- package/scripts/test-scripts/test-real-world.js +44 -0
- package/scripts/test-scripts/test-rules-on-real-projects.js +86 -0
- package/scripts/trigger-release.sh +285 -0
- package/scripts/validate-rule-structure.js +148 -0
- package/scripts/verify-install.sh +82 -0
- package/config/sunlint-schema.json +0 -159
- package/config/typescript/custom-rules.js +0 -9
- package/config/typescript/package-lock.json +0 -1585
- package/config/typescript/package.json +0 -13
- package/config/typescript/security-rules/index.js +0 -90
- package/config/typescript/tsconfig.json +0 -29
- package/core/ai-analyzer.js +0 -169
- package/core/eslint-engine-service.js +0 -312
- package/core/eslint-instance-manager.js +0 -104
- package/core/eslint-integration-service.js +0 -363
- package/core/sunlint-engine-service.js +0 -23
- package/core/typescript-analyzer.js +0 -262
- package/core/typescript-engine.js +0 -313
- /package/config/{default.json → defaults/default.json} +0 -0
- /package/config/{typescript/eslint.config.js → integrations/eslint/typescript.config.js} +0 -0
- /package/config/{typescript/custom-rules-new.js → schemas/sunlint-schema.json} +0 -0
- /package/config/{typescript → testing}/test-s005-working.ts +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s005-no-origin-auth.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s006-activation-recovery-secret-not-plaintext.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s008-crypto-agility.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s009-no-insecure-crypto.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s010-no-insecure-random-in-sensitive-context.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s011-no-insecure-uuid.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s012-hardcode-secret.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s014-insecure-tls-version.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s015-insecure-tls-certificate.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s016-sensitive-query-parameter.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s017-no-sql-injection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s018-positive-input-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s019-no-raw-user-input-in-email.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s020-no-eval-dynamic-execution.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s022-output-encoding.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s023-no-json-injection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s025-server-side-input-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s026-json-schema-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s027-no-hardcoded-secrets.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s029-require-csrf-protection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s030-no-directory-browsing.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s033-require-samesite-cookie.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s034-require-host-cookie-prefix.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s035-cookie-specific-path.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s036-no-unsafe-file-include.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s037-require-anti-cache-headers.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s038-no-version-disclosure.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s039-no-session-token-in-url.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s041-require-session-invalidate-on-logout.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s042-require-periodic-reauthentication.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s043-terminate-sessions-on-password-change.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s044-require-full-session-for-sensitive-operations.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s045-anti-automation-controls.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s046-secure-notification-on-auth-change.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s048-password-credential-recovery.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s050-session-token-weak-hash.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s052-secure-random-authentication-code.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s054-verification-default-account.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s057-utc-logging.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s058-no-ssrf.js +0 -0
- /package/rules/{C006_function_naming → common/C006_function_naming}/config.json +0 -0
- /package/rules/{C019_log_level_usage → common/C019_log_level_usage}/config.json +0 -0
- /package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/config.json +0 -0
- /package/rules/{C031_validation_separation → common/C031_validation_separation}/analyzer.js +0 -0
- /package/rules/{C031_validation_separation/README.md → docs/C031_validation_separation.md} +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C017 – Limit constructor logic
|
|
3
|
+
* Rule ID: custom/c017
|
|
4
|
+
* Purpose: Enforce minimal logic in constructors to maintain clean initialization
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Limit constructor logic",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
constructorLogic: "Constructor contains complex logic: {{description}}. Move to initialization methods",
|
|
17
|
+
tooManyStatements: "Constructor has too many statements ({{count}}). Consider simplifying or moving logic to initialization methods"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
function isSimpleAssignment(node) {
|
|
22
|
+
// Simple property assignments like this.prop = value
|
|
23
|
+
if (node.type === "ExpressionStatement" &&
|
|
24
|
+
node.expression.type === "AssignmentExpression" &&
|
|
25
|
+
node.expression.left.type === "MemberExpression" &&
|
|
26
|
+
node.expression.left.object.type === "ThisExpression") {
|
|
27
|
+
|
|
28
|
+
// Check if right side is a simple value (not complex computation)
|
|
29
|
+
const right = node.expression.right;
|
|
30
|
+
return right.type === "Literal" ||
|
|
31
|
+
right.type === "Identifier" ||
|
|
32
|
+
(right.type === "MemberExpression" && right.property.name === "length") ||
|
|
33
|
+
(right.type === "CallExpression" && right.callee.type === "Identifier" &&
|
|
34
|
+
['require', 'process'].includes(right.callee.name));
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isSimpleDeclaration(node) {
|
|
40
|
+
// Simple variable declarations
|
|
41
|
+
return node.type === "VariableDeclaration";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isSuperCall(node) {
|
|
45
|
+
// super() calls
|
|
46
|
+
return node.type === "ExpressionStatement" &&
|
|
47
|
+
node.expression.type === "CallExpression" &&
|
|
48
|
+
node.expression.callee.type === "Super";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isComplexLogic(node) {
|
|
52
|
+
// Complex patterns that shouldn't be in constructor
|
|
53
|
+
switch (node.type) {
|
|
54
|
+
case "IfStatement":
|
|
55
|
+
case "WhileStatement":
|
|
56
|
+
case "ForStatement":
|
|
57
|
+
case "SwitchStatement":
|
|
58
|
+
case "TryStatement":
|
|
59
|
+
return { type: "control_flow", description: "control flow statements" };
|
|
60
|
+
|
|
61
|
+
case "ExpressionStatement":
|
|
62
|
+
if (node.expression.type === "CallExpression") {
|
|
63
|
+
const callee = node.expression.callee;
|
|
64
|
+
|
|
65
|
+
// Method calls that aren't simple setters
|
|
66
|
+
if (callee.type === "MemberExpression") {
|
|
67
|
+
const methodName = callee.property.name;
|
|
68
|
+
|
|
69
|
+
// Allow simple MobX observable setup
|
|
70
|
+
if (methodName === "makeObservable" || methodName === "makeAutoObservable") {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Flag complex method calls
|
|
75
|
+
if (!['push', 'set', 'add'].includes(methodName)) {
|
|
76
|
+
return { type: "method_call", description: "complex method calls" };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Direct function calls (not method calls)
|
|
81
|
+
if (callee.type === "Identifier" &&
|
|
82
|
+
!['require', 'parseInt', 'parseFloat', 'Boolean', 'Number', 'String'].includes(callee.name)) {
|
|
83
|
+
return { type: "function_call", description: "function calls" };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Complex assignments with computations
|
|
88
|
+
if (node.expression.type === "AssignmentExpression") {
|
|
89
|
+
const right = node.expression.right;
|
|
90
|
+
if (right.type === "BinaryExpression" ||
|
|
91
|
+
right.type === "ConditionalExpression" ||
|
|
92
|
+
(right.type === "CallExpression" &&
|
|
93
|
+
right.callee.type === "MemberExpression" &&
|
|
94
|
+
!['map', 'filter', 'toString', 'slice'].includes(right.callee.property.name))) {
|
|
95
|
+
return { type: "complex_assignment", description: "complex computations" };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
MethodDefinition(node) {
|
|
106
|
+
if (node.kind === "constructor" && node.value && node.value.body) {
|
|
107
|
+
const statements = node.value.body.body;
|
|
108
|
+
let complexLogicCount = 0;
|
|
109
|
+
|
|
110
|
+
for (const stmt of statements) {
|
|
111
|
+
// Skip simple assignments, declarations, and super calls
|
|
112
|
+
if (isSimpleAssignment(stmt) ||
|
|
113
|
+
isSimpleDeclaration(stmt) ||
|
|
114
|
+
isSuperCall(stmt)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check for complex logic
|
|
119
|
+
const complexity = isComplexLogic(stmt);
|
|
120
|
+
if (complexity) {
|
|
121
|
+
context.report({
|
|
122
|
+
node: stmt,
|
|
123
|
+
messageId: "constructorLogic",
|
|
124
|
+
data: {
|
|
125
|
+
description: complexity.description
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
complexLogicCount++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Also flag constructors with too many statements overall
|
|
133
|
+
if (statements.length > 10) {
|
|
134
|
+
context.report({
|
|
135
|
+
node: node,
|
|
136
|
+
messageId: "tooManyStatements",
|
|
137
|
+
data: {
|
|
138
|
+
count: statements.length
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
};
|
|
@@ -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;
|