@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,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: T026 – Limit deeply nested generics
|
|
3
|
+
* Rule ID: custom/t026
|
|
4
|
+
* Purpose: Prevent deeply nested generic type parameters to maintain type readability and reduce cognitive load
|
|
5
|
+
*
|
|
6
|
+
* Following Vietnamese Coding Standards:
|
|
7
|
+
* - Rule C005: Each function should do one thing (this rule focuses solely on generic nesting)
|
|
8
|
+
* - Rule C015: Use domain language in naming (clear error messages in both English and Vietnamese)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
meta: {
|
|
15
|
+
type: "suggestion",
|
|
16
|
+
docs: {
|
|
17
|
+
description: "Limit deeply nested generics to maintain type readability",
|
|
18
|
+
category: "TypeScript",
|
|
19
|
+
recommended: true,
|
|
20
|
+
},
|
|
21
|
+
fixable: null,
|
|
22
|
+
schema: [
|
|
23
|
+
{
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
maxDepth: {
|
|
27
|
+
type: "integer",
|
|
28
|
+
minimum: 1,
|
|
29
|
+
description: "Maximum allowed depth of nested generics (default: 3)"
|
|
30
|
+
},
|
|
31
|
+
allowUtilityTypes: {
|
|
32
|
+
type: "boolean",
|
|
33
|
+
description: "Whether to allow deeper nesting in utility types like Partial, Pick, etc. (default: true)"
|
|
34
|
+
},
|
|
35
|
+
exemptedTypeNames: {
|
|
36
|
+
type: "array",
|
|
37
|
+
items: { type: "string" },
|
|
38
|
+
description: "List of type names that are exempt from the depth check"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
additionalProperties: false
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
messages: {
|
|
45
|
+
tooDeepNesting: "Generic type nesting is too deep ({{depth}} levels). Maximum allowed is {{maxDepth}} levels. Consider breaking down complex types into simpler ones.",
|
|
46
|
+
tooDeepNestingVietnamese: "Kiểu generic lồng quá sâu ({{depth}} cấp). Tối đa cho phép là {{maxDepth}} cấp. Hãy chia nhỏ kiểu phức tạp thành các kiểu đơn giản hơn."
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
create(context) {
|
|
51
|
+
const options = context.options[0] || {};
|
|
52
|
+
const maxDepth = options.maxDepth || 3;
|
|
53
|
+
const allowUtilityTypes = options.allowUtilityTypes !== false;
|
|
54
|
+
const exemptedTypeNames = new Set(options.exemptedTypeNames || []);
|
|
55
|
+
|
|
56
|
+
// Common TypeScript utility types that might need deeper nesting
|
|
57
|
+
const utilityTypes = new Set([
|
|
58
|
+
'Partial', 'Required', 'Readonly', 'Pick', 'Omit', 'Exclude', 'Extract',
|
|
59
|
+
'NonNullable', 'Parameters', 'ConstructorParameters', 'ReturnType',
|
|
60
|
+
'InstanceType', 'ThisParameterType', 'OmitThisParameter', 'ThisType',
|
|
61
|
+
'Uppercase', 'Lowercase', 'Capitalize', 'Uncapitalize'
|
|
62
|
+
// Note: Removed Promise, Array, Record, Map, Set to properly catch deep nesting
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Calculate the depth of nested generics in a type node
|
|
67
|
+
* @param {Object} node - The TypeScript AST node
|
|
68
|
+
* @param {number} currentDepth - Current depth level
|
|
69
|
+
* @returns {number} The maximum depth found
|
|
70
|
+
*/
|
|
71
|
+
function calculateGenericDepth(node, currentDepth = 0) {
|
|
72
|
+
if (!node) return currentDepth;
|
|
73
|
+
|
|
74
|
+
let maxDepth = currentDepth;
|
|
75
|
+
|
|
76
|
+
switch (node.type) {
|
|
77
|
+
case 'TSTypeReference':
|
|
78
|
+
// Check typeArguments (not typeParameters) for type references
|
|
79
|
+
if (node.typeArguments && node.typeArguments.params) {
|
|
80
|
+
const newDepth = currentDepth + 1;
|
|
81
|
+
for (const param of node.typeArguments.params) {
|
|
82
|
+
const paramDepth = calculateGenericDepth(param, newDepth);
|
|
83
|
+
maxDepth = Math.max(maxDepth, paramDepth);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case 'TSUnionType':
|
|
89
|
+
case 'TSIntersectionType':
|
|
90
|
+
if (node.types) {
|
|
91
|
+
for (const type of node.types) {
|
|
92
|
+
const typeDepth = calculateGenericDepth(type, currentDepth);
|
|
93
|
+
maxDepth = Math.max(maxDepth, typeDepth);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
|
|
98
|
+
case 'TSTupleType':
|
|
99
|
+
if (node.elementTypes) {
|
|
100
|
+
for (const element of node.elementTypes) {
|
|
101
|
+
const elementDepth = calculateGenericDepth(element, currentDepth);
|
|
102
|
+
maxDepth = Math.max(maxDepth, elementDepth);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
case 'TSArrayType':
|
|
108
|
+
if (node.elementType) {
|
|
109
|
+
maxDepth = Math.max(maxDepth, calculateGenericDepth(node.elementType, currentDepth));
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
case 'TSMappedType':
|
|
114
|
+
if (node.typeParameter) {
|
|
115
|
+
maxDepth = Math.max(maxDepth, calculateGenericDepth(node.typeParameter, currentDepth));
|
|
116
|
+
}
|
|
117
|
+
if (node.typeAnnotation) {
|
|
118
|
+
maxDepth = Math.max(maxDepth, calculateGenericDepth(node.typeAnnotation, currentDepth));
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
case 'TSConditionalType':
|
|
123
|
+
if (node.checkType) {
|
|
124
|
+
maxDepth = Math.max(maxDepth, calculateGenericDepth(node.checkType, currentDepth));
|
|
125
|
+
}
|
|
126
|
+
if (node.extendsType) {
|
|
127
|
+
maxDepth = Math.max(maxDepth, calculateGenericDepth(node.extendsType, currentDepth));
|
|
128
|
+
}
|
|
129
|
+
if (node.trueType) {
|
|
130
|
+
maxDepth = Math.max(maxDepth, calculateGenericDepth(node.trueType, currentDepth));
|
|
131
|
+
}
|
|
132
|
+
if (node.falseType) {
|
|
133
|
+
maxDepth = Math.max(maxDepth, calculateGenericDepth(node.falseType, currentDepth));
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
case 'TSIndexedAccessType':
|
|
138
|
+
if (node.objectType) {
|
|
139
|
+
maxDepth = Math.max(maxDepth, calculateGenericDepth(node.objectType, currentDepth));
|
|
140
|
+
}
|
|
141
|
+
if (node.indexType) {
|
|
142
|
+
maxDepth = Math.max(maxDepth, calculateGenericDepth(node.indexType, currentDepth));
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
case 'TSTypeQuery':
|
|
147
|
+
if (node.exprName) {
|
|
148
|
+
maxDepth = Math.max(maxDepth, calculateGenericDepth(node.exprName, currentDepth));
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
|
|
152
|
+
case 'TSFunctionType':
|
|
153
|
+
case 'TSConstructorType':
|
|
154
|
+
if (node.typeParameters && node.typeParameters.params) {
|
|
155
|
+
for (const param of node.typeParameters.params) {
|
|
156
|
+
const paramDepth = calculateGenericDepth(param, currentDepth + 1);
|
|
157
|
+
maxDepth = Math.max(maxDepth, paramDepth);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (node.parameters) {
|
|
161
|
+
for (const param of node.parameters) {
|
|
162
|
+
if (param.typeAnnotation) {
|
|
163
|
+
const paramDepth = calculateGenericDepth(param.typeAnnotation.typeAnnotation, currentDepth);
|
|
164
|
+
maxDepth = Math.max(maxDepth, paramDepth);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (node.typeAnnotation) {
|
|
169
|
+
maxDepth = Math.max(maxDepth, calculateGenericDepth(node.typeAnnotation.typeAnnotation, currentDepth));
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return maxDepth;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Check if a type reference is a utility type that should be exempted
|
|
179
|
+
* @param {Object} node - The TypeScript type reference node
|
|
180
|
+
* @returns {boolean} Whether this is an exempted utility type
|
|
181
|
+
*/
|
|
182
|
+
function isExemptedType(node) {
|
|
183
|
+
if (node.type !== 'TSTypeReference' || !node.typeName) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let typeName = '';
|
|
188
|
+
if (node.typeName.type === 'Identifier') {
|
|
189
|
+
typeName = node.typeName.name;
|
|
190
|
+
} else if (node.typeName.type === 'TSQualifiedName') {
|
|
191
|
+
// Handle qualified names like React.Component
|
|
192
|
+
typeName = getQualifiedTypeName(node.typeName);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return exemptedTypeNames.has(typeName) ||
|
|
196
|
+
(allowUtilityTypes && utilityTypes.has(typeName));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get the full qualified type name from a TSQualifiedName node
|
|
201
|
+
* @param {Object} node - The TSQualifiedName node
|
|
202
|
+
* @returns {string} The full qualified name
|
|
203
|
+
*/
|
|
204
|
+
function getQualifiedTypeName(node) {
|
|
205
|
+
if (node.type === 'Identifier') {
|
|
206
|
+
return node.name;
|
|
207
|
+
}
|
|
208
|
+
if (node.type === 'TSQualifiedName') {
|
|
209
|
+
return `${getQualifiedTypeName(node.left)}.${node.right.name}`;
|
|
210
|
+
}
|
|
211
|
+
return '';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Check a type node for excessive generic nesting
|
|
216
|
+
* @param {Object} node - The TypeScript AST node to check
|
|
217
|
+
*/
|
|
218
|
+
function checkTypeNode(node) {
|
|
219
|
+
if (!node) return;
|
|
220
|
+
|
|
221
|
+
// Skip utility types if allowed
|
|
222
|
+
if (isExemptedType(node)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const depth = calculateGenericDepth(node);
|
|
227
|
+
|
|
228
|
+
if (depth > maxDepth) {
|
|
229
|
+
context.report({
|
|
230
|
+
node,
|
|
231
|
+
messageId: "tooDeepNesting",
|
|
232
|
+
data: {
|
|
233
|
+
depth,
|
|
234
|
+
maxDepth
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
// Check type aliases
|
|
242
|
+
TSTypeAliasDeclaration(node) {
|
|
243
|
+
if (node.typeAnnotation) {
|
|
244
|
+
checkTypeNode(node.typeAnnotation);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
// Check interface declarations
|
|
249
|
+
TSInterfaceDeclaration(node) {
|
|
250
|
+
if (node.extends) {
|
|
251
|
+
for (const extend of node.extends) {
|
|
252
|
+
checkTypeNode(extend);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (node.body && node.body.body) {
|
|
257
|
+
for (const member of node.body.body) {
|
|
258
|
+
if (member.typeAnnotation && member.typeAnnotation.typeAnnotation) {
|
|
259
|
+
checkTypeNode(member.typeAnnotation.typeAnnotation);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
// Check function declarations
|
|
266
|
+
FunctionDeclaration(node) {
|
|
267
|
+
if (node.typeParameters && node.typeParameters.params) {
|
|
268
|
+
for (const param of node.typeParameters.params) {
|
|
269
|
+
if (param.constraint) {
|
|
270
|
+
checkTypeNode(param.constraint);
|
|
271
|
+
}
|
|
272
|
+
if (param.default) {
|
|
273
|
+
checkTypeNode(param.default);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// Check function expressions and arrow functions
|
|
280
|
+
FunctionExpression(node) {
|
|
281
|
+
if (node.typeParameters && node.typeParameters.params) {
|
|
282
|
+
for (const param of node.typeParameters.params) {
|
|
283
|
+
if (param.constraint) {
|
|
284
|
+
checkTypeNode(param.constraint);
|
|
285
|
+
}
|
|
286
|
+
if (param.default) {
|
|
287
|
+
checkTypeNode(param.default);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
ArrowFunctionExpression(node) {
|
|
294
|
+
if (node.typeParameters && node.typeParameters.params) {
|
|
295
|
+
for (const param of node.typeParameters.params) {
|
|
296
|
+
if (param.constraint) {
|
|
297
|
+
checkTypeNode(param.constraint);
|
|
298
|
+
}
|
|
299
|
+
if (param.default) {
|
|
300
|
+
checkTypeNode(param.default);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
// Check method definitions
|
|
307
|
+
MethodDefinition(node) {
|
|
308
|
+
if (node.value && node.value.typeParameters && node.value.typeParameters.params) {
|
|
309
|
+
for (const param of node.value.typeParameters.params) {
|
|
310
|
+
if (param.constraint) {
|
|
311
|
+
checkTypeNode(param.constraint);
|
|
312
|
+
}
|
|
313
|
+
if (param.default) {
|
|
314
|
+
checkTypeNode(param.default);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
// Check variable declarations with type annotations
|
|
321
|
+
VariableDeclarator(node) {
|
|
322
|
+
if (node.id && node.id.typeAnnotation && node.id.typeAnnotation.typeAnnotation) {
|
|
323
|
+
checkTypeNode(node.id.typeAnnotation.typeAnnotation);
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
// Check property signatures
|
|
328
|
+
TSPropertySignature(node) {
|
|
329
|
+
if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) {
|
|
330
|
+
checkTypeNode(node.typeAnnotation.typeAnnotation);
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
// Check method signatures
|
|
335
|
+
TSMethodSignature(node) {
|
|
336
|
+
if (node.typeParameters && node.typeParameters.params) {
|
|
337
|
+
for (const param of node.typeParameters.params) {
|
|
338
|
+
if (param.constraint) {
|
|
339
|
+
checkTypeNode(param.constraint);
|
|
340
|
+
}
|
|
341
|
+
if (param.default) {
|
|
342
|
+
checkTypeNode(param.default);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// Check call signatures
|
|
349
|
+
TSCallSignatureDeclaration(node) {
|
|
350
|
+
if (node.typeParameters && node.typeParameters.params) {
|
|
351
|
+
for (const param of node.typeParameters.params) {
|
|
352
|
+
if (param.constraint) {
|
|
353
|
+
checkTypeNode(param.constraint);
|
|
354
|
+
}
|
|
355
|
+
if (param.default) {
|
|
356
|
+
checkTypeNode(param.default);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
// Check construct signatures
|
|
363
|
+
TSConstructSignatureDeclaration(node) {
|
|
364
|
+
if (node.typeParameters && node.typeParameters.params) {
|
|
365
|
+
for (const param of node.typeParameters.params) {
|
|
366
|
+
if (param.constraint) {
|
|
367
|
+
checkTypeNode(param.constraint);
|
|
368
|
+
}
|
|
369
|
+
if (param.default) {
|
|
370
|
+
checkTypeNode(param.default);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
},
|
|
377
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// ESLint Flat Configuration for SunLint (ESLint v9+)
|
|
2
|
+
// Following Rule C005: Single responsibility - ESLint configuration
|
|
3
|
+
const typescriptParser = require('@typescript-eslint/parser');
|
|
4
|
+
const typescriptPlugin = require('@typescript-eslint/eslint-plugin');
|
|
5
|
+
const customPlugin = require('./eslint-plugin-custom');
|
|
6
|
+
|
|
7
|
+
module.exports = [
|
|
8
|
+
{
|
|
9
|
+
files: ['**/*.{js,ts,tsx,jsx}'],
|
|
10
|
+
languageOptions: {
|
|
11
|
+
parser: typescriptParser,
|
|
12
|
+
parserOptions: {
|
|
13
|
+
ecmaVersion: 2022,
|
|
14
|
+
sourceType: 'module',
|
|
15
|
+
project: './tsconfig.json'
|
|
16
|
+
},
|
|
17
|
+
globals: {
|
|
18
|
+
console: 'readonly',
|
|
19
|
+
process: 'readonly',
|
|
20
|
+
eval: 'readonly',
|
|
21
|
+
Buffer: 'readonly',
|
|
22
|
+
__dirname: 'readonly',
|
|
23
|
+
__filename: 'readonly',
|
|
24
|
+
exports: 'writable',
|
|
25
|
+
module: 'writable',
|
|
26
|
+
require: 'readonly',
|
|
27
|
+
global: 'readonly'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
plugins: {
|
|
31
|
+
'custom': customPlugin,
|
|
32
|
+
'@typescript-eslint': typescriptPlugin
|
|
33
|
+
},
|
|
34
|
+
rules: {
|
|
35
|
+
// Rule C019: No console.error for non-critical errors
|
|
36
|
+
'no-console': ['warn', { allow: ['warn', 'log'] }],
|
|
37
|
+
|
|
38
|
+
// Code quality rules
|
|
39
|
+
'no-unused-vars': 'warn',
|
|
40
|
+
'prefer-const': 'warn',
|
|
41
|
+
'no-var': 'error',
|
|
42
|
+
'no-undef': 'warn',
|
|
43
|
+
|
|
44
|
+
// Rule C005: Single responsibility principle
|
|
45
|
+
'max-lines-per-function': ['warn', { max: 50 }],
|
|
46
|
+
'complexity': ['warn', { max: 10 }],
|
|
47
|
+
|
|
48
|
+
// Rule C014: Avoid direct new instantiation patterns
|
|
49
|
+
'no-new': 'warn',
|
|
50
|
+
|
|
51
|
+
// Security and best practices
|
|
52
|
+
'no-eval': 'error',
|
|
53
|
+
'no-implied-eval': 'error',
|
|
54
|
+
'no-new-func': 'error',
|
|
55
|
+
'eqeqeq': 'error',
|
|
56
|
+
'curly': 'error',
|
|
57
|
+
|
|
58
|
+
// Quality rules (dynamic loading based on category)
|
|
59
|
+
'custom/c002': 'off',
|
|
60
|
+
'custom/c003': 'off',
|
|
61
|
+
'custom/c006': 'off',
|
|
62
|
+
'custom/c010': 'off',
|
|
63
|
+
'custom/c013': 'off',
|
|
64
|
+
'custom/c014': 'off',
|
|
65
|
+
'custom/c017': 'off',
|
|
66
|
+
'custom/c018': 'off',
|
|
67
|
+
'custom/c023': 'off',
|
|
68
|
+
'custom/c027': 'off',
|
|
69
|
+
'custom/c029': 'off',
|
|
70
|
+
'custom/c030': 'off',
|
|
71
|
+
'custom/c034': 'off',
|
|
72
|
+
'custom/c035': 'off',
|
|
73
|
+
'custom/c041': 'off',
|
|
74
|
+
'custom/c042': 'off',
|
|
75
|
+
'custom/c043': 'off',
|
|
76
|
+
'custom/c047': 'off',
|
|
77
|
+
'custom/c048': 'off',
|
|
78
|
+
|
|
79
|
+
// Security rules (dynamic loading based on category)
|
|
80
|
+
'custom/typescript_s003': 'off',
|
|
81
|
+
'custom/typescript_s005': 'off',
|
|
82
|
+
'custom/typescript_s006': 'off',
|
|
83
|
+
'custom/typescript_s008': 'off',
|
|
84
|
+
'custom/typescript_s009': 'off',
|
|
85
|
+
'custom/typescript_s010': 'off',
|
|
86
|
+
'custom/typescript_s011': 'off',
|
|
87
|
+
'custom/typescript_s012': 'off',
|
|
88
|
+
'custom/typescript_s014': 'off',
|
|
89
|
+
'custom/typescript_s015': 'off',
|
|
90
|
+
'custom/typescript_s016': 'off',
|
|
91
|
+
'custom/typescript_s017': 'off',
|
|
92
|
+
'custom/typescript_s018': 'off',
|
|
93
|
+
'custom/typescript_s019': 'off',
|
|
94
|
+
'custom/typescript_s020': 'off',
|
|
95
|
+
'custom/typescript_s022': 'off',
|
|
96
|
+
'custom/typescript_s023': 'off',
|
|
97
|
+
'custom/typescript_s025': 'off',
|
|
98
|
+
'custom/typescript_s026': 'off',
|
|
99
|
+
'custom/typescript_s027': 'off',
|
|
100
|
+
'custom/typescript_s029': 'off',
|
|
101
|
+
'custom/typescript_s030': 'off',
|
|
102
|
+
'custom/typescript_s033': 'off',
|
|
103
|
+
'custom/typescript_s034': 'off',
|
|
104
|
+
'custom/typescript_s035': 'off',
|
|
105
|
+
'custom/typescript_s036': 'off',
|
|
106
|
+
'custom/typescript_s037': 'off',
|
|
107
|
+
'custom/typescript_s038': 'off',
|
|
108
|
+
'custom/typescript_s039': 'off',
|
|
109
|
+
'custom/typescript_s041': 'off',
|
|
110
|
+
'custom/typescript_s042': 'off',
|
|
111
|
+
'custom/typescript_s043': 'off',
|
|
112
|
+
'custom/typescript_s044': 'off',
|
|
113
|
+
'custom/typescript_s045': 'off',
|
|
114
|
+
'custom/typescript_s046': 'off',
|
|
115
|
+
'custom/typescript_s047': 'off',
|
|
116
|
+
'custom/typescript_s048': 'off',
|
|
117
|
+
'custom/typescript_s050': 'off',
|
|
118
|
+
'custom/typescript_s052': 'off',
|
|
119
|
+
'custom/typescript_s054': 'off',
|
|
120
|
+
'custom/typescript_s055': 'off',
|
|
121
|
+
'custom/typescript_s057': 'off',
|
|
122
|
+
'custom/typescript_s058': 'off'
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Simple ESLint Flat Configuration for Testing
|
|
2
|
+
module.exports = [
|
|
3
|
+
{
|
|
4
|
+
files: ['**/*.{js,mjs,cjs}'],
|
|
5
|
+
languageOptions: {
|
|
6
|
+
ecmaVersion: 2022,
|
|
7
|
+
sourceType: 'module',
|
|
8
|
+
globals: {
|
|
9
|
+
console: 'readonly',
|
|
10
|
+
process: 'readonly',
|
|
11
|
+
eval: 'readonly'
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
rules: {
|
|
15
|
+
'no-console': ['warn', { allow: ['warn', 'log'] }],
|
|
16
|
+
'no-unused-vars': 'warn',
|
|
17
|
+
'prefer-const': 'warn',
|
|
18
|
+
'no-var': 'error',
|
|
19
|
+
'no-undef': 'warn',
|
|
20
|
+
'no-eval': 'error',
|
|
21
|
+
'eqeqeq': 'error'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
];
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "typescript",
|
|
3
|
+
"private": true,
|
|
4
|
+
"devDependencies": {
|
|
5
|
+
"@types/bun": "latest",
|
|
6
|
+
"@typescript-eslint/eslint-plugin": "^8.34.1",
|
|
7
|
+
"@typescript-eslint/parser": "^8.34.1",
|
|
8
|
+
"@typescript-eslint/utils": "^8.34.1",
|
|
9
|
+
"eslint": "^9.29.0",
|
|
10
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
11
|
+
"@eslint/js": "^9.28.0",
|
|
12
|
+
"eslint-plugin-sonarjs": "^3.0.2"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"typescript": "^5"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"lint": "eslint .",
|
|
19
|
+
"lint:ts": "eslint src/ --ext .ts,.tsx"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Test file to verify ESLint integration
|
|
2
|
+
class UserManager {
|
|
3
|
+
private users: any[] = [];
|
|
4
|
+
|
|
5
|
+
constructor() {
|
|
6
|
+
// Rule C032: API call in constructor (should be flagged)
|
|
7
|
+
this.fetchUsers();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Rule C006: Not verb-noun naming (should be flagged)
|
|
11
|
+
data() {
|
|
12
|
+
return this.users;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Rule C019: Using console.error for non-critical (should be flagged)
|
|
16
|
+
processUser(user: any) {
|
|
17
|
+
if (!user.name) {
|
|
18
|
+
console.error('User name is missing');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this.users.push(user);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Rule C014: Direct new instead of DI (should be flagged)
|
|
25
|
+
createValidator() {
|
|
26
|
+
return new UserValidator();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private fetchUsers() {
|
|
30
|
+
// Mock API call
|
|
31
|
+
return fetch('/api/users');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class UserValidator {
|
|
36
|
+
validate(user: any) {
|
|
37
|
+
return user.name && user.email;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Rule C005: Function doing multiple things (should be flagged)
|
|
42
|
+
function processAndValidateUser(user: any) {
|
|
43
|
+
// Validation logic
|
|
44
|
+
if (!user.name) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Processing logic
|
|
49
|
+
user.processedAt = new Date();
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { UserManager, UserValidator, processAndValidateUser };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Test S003 rule patterns
|
|
2
|
+
window.location.href = req.query.redirectUrl; // Should match AssignmentExpression
|
|
3
|
+
location.href = req.body.targetUrl; // Should match AssignmentExpression
|
|
4
|
+
location.replace = req.params.url; // Should match AssignmentExpression
|
|
5
|
+
res.redirect(req.query.returnUrl); // Should match CallExpression
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Enable latest features
|
|
4
|
+
"lib": ["ESNext", "DOM"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
|
|
22
|
+
// Some stricter flags (disabled by default)
|
|
23
|
+
"noUnusedLocals": false,
|
|
24
|
+
"noUnusedParameters": false,
|
|
25
|
+
"noPropertyAccessFromIndexSignature": false
|
|
26
|
+
}
|
|
27
|
+
}
|