@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,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C003 – Clear variable names, avoid arbitrary abbreviations
|
|
3
|
+
* Rule ID: custom/c003
|
|
4
|
+
* Purpose: Ensure clear, understandable variable names without arbitrary abbreviations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const c003Rule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Clear variable names, avoid arbitrary abbreviations",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [
|
|
15
|
+
{
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
allowedSingleChar: {
|
|
19
|
+
type: "array",
|
|
20
|
+
items: { type: "string" },
|
|
21
|
+
description: "Single character variables that are allowed (default: i, j, k)"
|
|
22
|
+
},
|
|
23
|
+
allowedAbbreviations: {
|
|
24
|
+
type: "array",
|
|
25
|
+
items: { type: "string" },
|
|
26
|
+
description: "Common abbreviations that are allowed"
|
|
27
|
+
},
|
|
28
|
+
minLength: {
|
|
29
|
+
type: "integer",
|
|
30
|
+
minimum: 1,
|
|
31
|
+
description: "Minimum variable name length (default: 2)"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
additionalProperties: false
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
messages: {
|
|
38
|
+
singleChar: "Variable '{{name}}' is only 1 character long. Use descriptive names (except for counters like i, j, k).",
|
|
39
|
+
tooShort: "Variable '{{name}}' is too short ({{length}} characters). Use descriptive names with at least {{minLength}} characters.",
|
|
40
|
+
abbreviation: "Variable '{{name}}' appears to be an unclear abbreviation. Use full descriptive names.",
|
|
41
|
+
unclear: "Variable '{{name}}' is unclear or ambiguous. Use more specific descriptive names."
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
create(context) {
|
|
46
|
+
const options = context.options[0] || {};
|
|
47
|
+
const allowedSingleChar = new Set(options.allowedSingleChar || ['i', 'j', 'k', 'x', 'y', 'z']);
|
|
48
|
+
const allowedAbbreviations = new Set(options.allowedAbbreviations || [
|
|
49
|
+
'id', 'url', 'api', 'ui', 'db', 'config', 'env', 'app',
|
|
50
|
+
'btn', 'img', 'src', 'dest', 'req', 'res', 'ctx',
|
|
51
|
+
'min', 'max', 'len', 'num', 'str', 'json'
|
|
52
|
+
]);
|
|
53
|
+
const minLength = options.minLength || 2;
|
|
54
|
+
|
|
55
|
+
// Common abbreviation patterns that should be avoided
|
|
56
|
+
const suspiciousAbbreviations = [
|
|
57
|
+
/^[a-z]{1,2}[0-9]*$/, // e.g., 'u', 'usr', 'n1', 'v2'
|
|
58
|
+
/^[a-z]*[aeiou]*[bcdfghjklmnpqrstvwxyz]{3,}$/, // too many consonants
|
|
59
|
+
/^[bcdfghjklmnpqrstvwxyz]{3,}[aeiou]*$/, // consonants at start
|
|
60
|
+
/^(tmp|temp|val|var|data|info|item|elem|el|obj|arr)([A-Z0-9].*)?$/, // generic names
|
|
61
|
+
/^[a-z]+(Mgr|Ctrl|Svc|Repo|Util|Hlpr|Mngr)$/, // manager/helper patterns
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
// Generic/unclear variable names that should be avoided
|
|
65
|
+
const unclearNames = new Set([
|
|
66
|
+
'data', 'info', 'item', 'element', 'object', 'value', 'result',
|
|
67
|
+
'response', 'request', 'temp', 'tmp', 'var', 'variable',
|
|
68
|
+
'stuff', 'thing', 'something', 'anything', 'everything',
|
|
69
|
+
'flag', 'check', 'test', 'validate', 'process', 'handle',
|
|
70
|
+
'obj', 'arg', 'val', 'fn'
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
function isCounterContext(node) {
|
|
74
|
+
// Check if variable is used as a loop counter
|
|
75
|
+
if (!node || !node.parent) return false;
|
|
76
|
+
|
|
77
|
+
let parent = node.parent;
|
|
78
|
+
|
|
79
|
+
// Go up the tree to find ForStatement/ForInStatement/ForOfStatement
|
|
80
|
+
while (parent) {
|
|
81
|
+
if (parent.type === 'ForStatement') {
|
|
82
|
+
return parent.init && parent.init.declarations &&
|
|
83
|
+
parent.init.declarations.some(decl => decl && decl.id === node);
|
|
84
|
+
}
|
|
85
|
+
if (parent.type === 'ForInStatement' || parent.type === 'ForOfStatement') {
|
|
86
|
+
return parent.left && (parent.left === node ||
|
|
87
|
+
(parent.left.type === 'VariableDeclaration' &&
|
|
88
|
+
parent.left.declarations.some(decl => decl && decl.id === node)));
|
|
89
|
+
}
|
|
90
|
+
parent = parent.parent;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function checkVariableName(node, name) {
|
|
96
|
+
// Safety checks
|
|
97
|
+
if (!node || !name || typeof name !== 'string') {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Skip if it's a destructuring pattern with specific exceptions
|
|
102
|
+
if (node.parent && node.parent.type === 'Property' && node.parent.shorthand) {
|
|
103
|
+
return; // Allow destructuring shorthand
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Skip TypeScript type annotations and interface properties
|
|
107
|
+
if (node.parent && (node.parent.type === 'TSTypeAnnotation' ||
|
|
108
|
+
node.parent.type === 'TSPropertySignature' ||
|
|
109
|
+
node.parent.type === 'TSMethodSignature')) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Single character check
|
|
114
|
+
if (name.length === 1) {
|
|
115
|
+
if (!allowedSingleChar.has(name.toLowerCase()) && !isCounterContext(node)) {
|
|
116
|
+
context.report({
|
|
117
|
+
node,
|
|
118
|
+
messageId: "singleChar",
|
|
119
|
+
data: { name }
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Minimum length check
|
|
126
|
+
if (name.length < minLength) {
|
|
127
|
+
context.report({
|
|
128
|
+
node,
|
|
129
|
+
messageId: "tooShort",
|
|
130
|
+
data: { name, length: name.length, minLength }
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Skip allowed abbreviations
|
|
136
|
+
if (allowedAbbreviations.has(name.toLowerCase())) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check for unclear/generic names
|
|
141
|
+
if (unclearNames.has(name.toLowerCase())) {
|
|
142
|
+
context.report({
|
|
143
|
+
node,
|
|
144
|
+
messageId: "unclear",
|
|
145
|
+
data: { name }
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Check for suspicious abbreviation patterns
|
|
151
|
+
for (const pattern of suspiciousAbbreviations) {
|
|
152
|
+
if (pattern.test(name)) {
|
|
153
|
+
context.report({
|
|
154
|
+
node,
|
|
155
|
+
messageId: "abbreviation",
|
|
156
|
+
data: { name }
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function checkParameter(param) {
|
|
164
|
+
if (!param) return;
|
|
165
|
+
|
|
166
|
+
if (param.type === 'Identifier') {
|
|
167
|
+
checkVariableName(param, param.name);
|
|
168
|
+
} else if (param.type === 'AssignmentPattern' && param.left && param.left.type === 'Identifier') {
|
|
169
|
+
// Handle default parameters
|
|
170
|
+
checkVariableName(param.left, param.left.name);
|
|
171
|
+
} else if (param.type === 'ObjectPattern' && param.properties) {
|
|
172
|
+
// Handle object destructuring in parameters
|
|
173
|
+
param.properties.forEach(prop => {
|
|
174
|
+
if (prop && prop.type === 'Property' && prop.value && prop.value.type === 'Identifier') {
|
|
175
|
+
checkVariableName(prop.value, prop.value.name);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
} else if (param.type === 'ArrayPattern' && param.elements) {
|
|
179
|
+
// Handle array destructuring in parameters
|
|
180
|
+
param.elements.forEach(element => {
|
|
181
|
+
if (element && element.type === 'Identifier') {
|
|
182
|
+
checkVariableName(element, element.name);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
VariableDeclarator(node) {
|
|
190
|
+
if (!node || !node.id) return;
|
|
191
|
+
|
|
192
|
+
if (node.id.type === 'Identifier') {
|
|
193
|
+
checkVariableName(node.id, node.id.name);
|
|
194
|
+
} else if (node.id.type === 'ObjectPattern') {
|
|
195
|
+
// Handle destructuring
|
|
196
|
+
if (node.id.properties) {
|
|
197
|
+
node.id.properties.forEach(prop => {
|
|
198
|
+
if (prop && prop.type === 'Property' && prop.value && prop.value.type === 'Identifier') {
|
|
199
|
+
checkVariableName(prop.value, prop.value.name);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
} else if (node.id.type === 'ArrayPattern') {
|
|
204
|
+
// Handle array destructuring
|
|
205
|
+
if (node.id.elements) {
|
|
206
|
+
node.id.elements.forEach(element => {
|
|
207
|
+
if (element && element.type === 'Identifier') {
|
|
208
|
+
checkVariableName(element, element.name);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
FunctionDeclaration(node) {
|
|
216
|
+
// Check function parameters
|
|
217
|
+
if (node && node.params) {
|
|
218
|
+
node.params.forEach(param => checkParameter(param));
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
ArrowFunctionExpression(node) {
|
|
223
|
+
// Check arrow function parameters
|
|
224
|
+
if (node && node.params) {
|
|
225
|
+
node.params.forEach(param => checkParameter(param));
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
FunctionExpression(node) {
|
|
230
|
+
// Check function expression parameters
|
|
231
|
+
if (node && node.params) {
|
|
232
|
+
node.params.forEach(param => checkParameter(param));
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
CatchClause(node) {
|
|
237
|
+
// Check catch clause parameters
|
|
238
|
+
if (node && node.param && node.param.type === 'Identifier') {
|
|
239
|
+
checkVariableName(node.param, node.param.name);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
module.exports = c003Rule;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C006 – Function names must be verbs or verb-noun phrases
|
|
3
|
+
* Rule ID: custom/c006
|
|
4
|
+
* Purpose: Enforce function naming convention using verbs or verb-noun phrases to clearly indicate actions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const c006Rule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Function names must be verbs or verb-noun phrases",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [
|
|
15
|
+
{
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
allowedVerbs: {
|
|
19
|
+
type: "array",
|
|
20
|
+
items: { type: "string" },
|
|
21
|
+
description: "Additional verbs to allow (beyond common ones)"
|
|
22
|
+
},
|
|
23
|
+
allowedPrefixes: {
|
|
24
|
+
type: "array",
|
|
25
|
+
items: { type: "string" },
|
|
26
|
+
description: "Allowed verb prefixes (default: get, set, is, has, can, should, etc.)"
|
|
27
|
+
},
|
|
28
|
+
allowConstructors: {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
description: "Allow constructor functions (PascalCase) (default: true)"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
additionalProperties: false
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
messages: {
|
|
37
|
+
notVerbNoun: "Function '{{name}}' should be a verb or verb-noun phrase (e.g., 'getData', 'calculateTotal', 'validateInput').",
|
|
38
|
+
useVerbForm: "Function '{{name}}' should start with a verb. Consider: '{{suggestions}}'.",
|
|
39
|
+
avoidNounOnly: "Function '{{name}}' appears to be a noun only. Use verb form like 'get{{name}}', 'create{{name}}', or 'process{{name}}'."
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
create(context) {
|
|
44
|
+
const options = context.options[0] || {};
|
|
45
|
+
|
|
46
|
+
// Common verb prefixes that indicate action
|
|
47
|
+
const commonVerbPrefixes = new Set([
|
|
48
|
+
'get', 'set', 'fetch', 'load', 'save', 'store', 'update', 'delete', 'remove',
|
|
49
|
+
'create', 'make', 'build', 'generate', 'produce', 'construct',
|
|
50
|
+
'add', 'insert', 'append', 'push', 'pop', 'shift', 'unshift',
|
|
51
|
+
'find', 'search', 'filter', 'sort', 'map', 'reduce', 'transform',
|
|
52
|
+
'validate', 'verify', 'check', 'test', 'confirm', 'ensure',
|
|
53
|
+
'calculate', 'compute', 'process', 'parse', 'format', 'convert',
|
|
54
|
+
'send', 'receive', 'transmit', 'broadcast', 'emit', 'dispatch',
|
|
55
|
+
'open', 'close', 'start', 'stop', 'begin', 'end', 'finish',
|
|
56
|
+
'show', 'hide', 'display', 'render', 'draw', 'paint',
|
|
57
|
+
'connect', 'disconnect', 'link', 'unlink', 'attach', 'detach',
|
|
58
|
+
'enable', 'disable', 'activate', 'deactivate', 'toggle',
|
|
59
|
+
'is', 'has', 'can', 'should', 'will', 'must', 'may',
|
|
60
|
+
'handle', 'manage', 'control', 'execute', 'run', 'invoke',
|
|
61
|
+
'reset', 'clear', 'clean', 'refresh', 'reload', 'restore'
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
const allowedPrefixes = new Set([
|
|
65
|
+
...commonVerbPrefixes,
|
|
66
|
+
...(options.allowedPrefixes || [])
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
const allowedVerbs = new Set([
|
|
70
|
+
...commonVerbPrefixes,
|
|
71
|
+
...(options.allowedVerbs || [])
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
const allowConstructors = options.allowConstructors !== false;
|
|
75
|
+
|
|
76
|
+
// Helper function to check if a name is PascalCase (likely a constructor)
|
|
77
|
+
function isPascalCase(name) {
|
|
78
|
+
return /^[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Helper function to check if a name is camelCase starting with a verb
|
|
82
|
+
function isVerbNounPattern(name) {
|
|
83
|
+
if (!name || name.length === 0) return false;
|
|
84
|
+
|
|
85
|
+
// Check if it starts with a known verb prefix
|
|
86
|
+
const lowerName = name.toLowerCase();
|
|
87
|
+
for (const verb of allowedPrefixes) {
|
|
88
|
+
if (lowerName.startsWith(verb.toLowerCase())) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Generate suggestions for a noun-based function name
|
|
97
|
+
function generateSuggestions(name) {
|
|
98
|
+
const suggestions = [
|
|
99
|
+
`get${name.charAt(0).toUpperCase() + name.slice(1)}`,
|
|
100
|
+
`create${name.charAt(0).toUpperCase() + name.slice(1)}`,
|
|
101
|
+
`process${name.charAt(0).toUpperCase() + name.slice(1)}`
|
|
102
|
+
];
|
|
103
|
+
return suggestions.join(', ');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check if the name appears to be just a noun
|
|
107
|
+
function isLikelyNounOnly(name) {
|
|
108
|
+
const nounPatterns = [
|
|
109
|
+
/^(user|data|info|item|list|array|object|config|settings|options)$/i,
|
|
110
|
+
/^(file|document|record|entry|element|component|widget)$/i,
|
|
111
|
+
/^(message|notification|alert|error|warning|success)$/i,
|
|
112
|
+
/^(report|summary|total|count|number|value|result)$/i
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
return nounPatterns.some(pattern => pattern.test(name));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function checkFunctionName(node, name) {
|
|
119
|
+
// Safety checks
|
|
120
|
+
if (!node || !name || typeof name !== 'string') {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Allow constructor functions (PascalCase)
|
|
125
|
+
if (allowConstructors && isPascalCase(name)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Skip very short names (likely okay: a, b, fn, etc.)
|
|
130
|
+
if (name.length <= 2) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check if it follows verb-noun pattern
|
|
135
|
+
if (isVerbNounPattern(name)) {
|
|
136
|
+
return; // Good! Follows the pattern
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check if it's likely a noun-only name
|
|
140
|
+
if (isLikelyNounOnly(name)) {
|
|
141
|
+
context.report({
|
|
142
|
+
node,
|
|
143
|
+
messageId: "avoidNounOnly",
|
|
144
|
+
data: {
|
|
145
|
+
name,
|
|
146
|
+
suggestions: generateSuggestions(name)
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// General violation - doesn't start with verb
|
|
153
|
+
context.report({
|
|
154
|
+
node,
|
|
155
|
+
messageId: "notVerbNoun",
|
|
156
|
+
data: { name }
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
FunctionDeclaration(node) {
|
|
162
|
+
if (node.id && node.id.name) {
|
|
163
|
+
checkFunctionName(node.id, node.id.name);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
FunctionExpression(node) {
|
|
168
|
+
// Check named function expressions
|
|
169
|
+
if (node.id && node.id.name) {
|
|
170
|
+
checkFunctionName(node.id, node.id.name);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
ArrowFunctionExpression(node) {
|
|
175
|
+
// For arrow functions assigned to variables
|
|
176
|
+
if (node.parent && node.parent.type === 'VariableDeclarator' && node.parent.id) {
|
|
177
|
+
checkFunctionName(node.parent.id, node.parent.id.name);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
MethodDefinition(node) {
|
|
182
|
+
// Check class methods
|
|
183
|
+
if (node.key && node.key.name && node.kind === 'method') {
|
|
184
|
+
// Skip constructor methods
|
|
185
|
+
if (node.key.name !== 'constructor') {
|
|
186
|
+
checkFunctionName(node.key, node.key.name);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
Property(node) {
|
|
192
|
+
// Check object method properties
|
|
193
|
+
if (node.method && node.key && node.key.name) {
|
|
194
|
+
checkFunctionName(node.key, node.key.name);
|
|
195
|
+
}
|
|
196
|
+
// Check function values assigned to object properties
|
|
197
|
+
if (!node.method && node.value &&
|
|
198
|
+
(node.value.type === 'FunctionExpression' || node.value.type === 'ArrowFunctionExpression') &&
|
|
199
|
+
node.key && node.key.name) {
|
|
200
|
+
checkFunctionName(node.key, node.key.name);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
module.exports = c006Rule;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C010 – Không nên có hơn 3 cấp lồng nhau (nested block)
|
|
3
|
+
* Rule ID: custom/c010
|
|
4
|
+
* Goal: Limit nested blocks (if/for/while/switch) to maximum 3 levels to improve readability and maintainability
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Không nên có hơn 3 cấp lồng nhau (nested block)",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [
|
|
15
|
+
{
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
maxDepth: {
|
|
19
|
+
type: "integer",
|
|
20
|
+
minimum: 1
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
additionalProperties: false
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
messages: {
|
|
27
|
+
tooDeep: "Block nesting is too deep (level {{depth}}). Maximum allowed is {{maxDepth}} levels."
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
create(context) {
|
|
31
|
+
const options = context.options[0] || {};
|
|
32
|
+
const maxDepth = options.maxDepth || 3;
|
|
33
|
+
let blockStack = [];
|
|
34
|
+
|
|
35
|
+
function enterBlock(node) {
|
|
36
|
+
blockStack.push(node);
|
|
37
|
+
const depth = blockStack.length;
|
|
38
|
+
|
|
39
|
+
if (depth > maxDepth) {
|
|
40
|
+
context.report({
|
|
41
|
+
node,
|
|
42
|
+
messageId: "tooDeep",
|
|
43
|
+
data: {
|
|
44
|
+
depth,
|
|
45
|
+
maxDepth
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function exitBlock() {
|
|
52
|
+
blockStack.pop();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
// Handle if statements
|
|
57
|
+
IfStatement: enterBlock,
|
|
58
|
+
'IfStatement:exit': exitBlock,
|
|
59
|
+
|
|
60
|
+
// Handle for loops
|
|
61
|
+
ForStatement: enterBlock,
|
|
62
|
+
'ForStatement:exit': exitBlock,
|
|
63
|
+
|
|
64
|
+
ForInStatement: enterBlock,
|
|
65
|
+
'ForInStatement:exit': exitBlock,
|
|
66
|
+
|
|
67
|
+
ForOfStatement: enterBlock,
|
|
68
|
+
'ForOfStatement:exit': exitBlock,
|
|
69
|
+
|
|
70
|
+
// Handle while loops
|
|
71
|
+
WhileStatement: enterBlock,
|
|
72
|
+
'WhileStatement:exit': exitBlock,
|
|
73
|
+
|
|
74
|
+
DoWhileStatement: enterBlock,
|
|
75
|
+
'DoWhileStatement:exit': exitBlock,
|
|
76
|
+
|
|
77
|
+
// Handle switch statements
|
|
78
|
+
SwitchStatement: enterBlock,
|
|
79
|
+
'SwitchStatement:exit': exitBlock,
|
|
80
|
+
|
|
81
|
+
// Handle try-catch blocks
|
|
82
|
+
TryStatement: enterBlock,
|
|
83
|
+
'TryStatement:exit': exitBlock,
|
|
84
|
+
|
|
85
|
+
// Handle with statements (though rarely used)
|
|
86
|
+
WithStatement: enterBlock,
|
|
87
|
+
'WithStatement:exit': exitBlock
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C013 – Do not leave dead code commented out
|
|
3
|
+
* Rule ID: custom/c013
|
|
4
|
+
* Purpose: Prevent commented-out code from being left in the codebase to maintain cleanliness
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Do not leave dead code commented out",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
deadCode: "Do not leave dead code commented out. Remove it or use version control to track changes."
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
return {
|
|
21
|
+
BlockStatement(node) {
|
|
22
|
+
let unreachable = false;
|
|
23
|
+
for (const stmt of node.body) {
|
|
24
|
+
if (unreachable) {
|
|
25
|
+
context.report({
|
|
26
|
+
node: stmt,
|
|
27
|
+
messageId: "deadCode"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
stmt.type === "ReturnStatement" ||
|
|
33
|
+
stmt.type === "ThrowStatement" ||
|
|
34
|
+
stmt.type === "ContinueStatement" ||
|
|
35
|
+
stmt.type === "BreakStatement"
|
|
36
|
+
) {
|
|
37
|
+
unreachable = true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C014 – Use Dependency Injection instead of direct instantiation
|
|
3
|
+
* Rule ID: custom/c014
|
|
4
|
+
* Purpose: Enforce dependency injection pattern by preventing direct class instantiation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Use Dependency Injection instead of direct instantiation",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
directInstantiation: "Avoid direct class instantiation. Use dependency injection instead."
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
return {
|
|
21
|
+
NewExpression(node) {
|
|
22
|
+
if (
|
|
23
|
+
node.callee &&
|
|
24
|
+
node.callee.type === "Identifier" &&
|
|
25
|
+
/^[A-Z]/.test(node.callee.name) // Class name starts with uppercase
|
|
26
|
+
) {
|
|
27
|
+
context.report({
|
|
28
|
+
node,
|
|
29
|
+
messageId: "directInstantiation",
|
|
30
|
+
data: {
|
|
31
|
+
name: node.callee.name
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
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 should only initialize properties, avoid complex logic"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
return {
|
|
21
|
+
MethodDefinition(node) {
|
|
22
|
+
if (
|
|
23
|
+
node.kind === "constructor" &&
|
|
24
|
+
node.value &&
|
|
25
|
+
node.value.body &&
|
|
26
|
+
node.value.body.body.length > 5 // threshold for logic lines in constructor
|
|
27
|
+
) {
|
|
28
|
+
context.report({
|
|
29
|
+
node,
|
|
30
|
+
messageId: "constructorLogic",
|
|
31
|
+
data: {
|
|
32
|
+
count: node.value.body.body.length
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|