@sun-asterisk/sunlint 1.0.7 → 1.1.3
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 +73 -52
- 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} +22 -0
- 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 +147 -0
- package/core/ast-modules/parsers/eslint-ts-parser.js +106 -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/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/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/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/HEURISTIC_VS_AI.md +113 -0
- package/docs/README.md +32 -0
- package/docs/RELEASE_GUIDE.md +230 -0
- package/engines/eslint-engine.js +601 -0
- package/engines/heuristic-engine.js +860 -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/test-c041-rule.js +87 -0
- package/integrations/eslint/tsconfig.json +27 -0
- package/package.json +29 -16
- 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,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: T025 – Avoid deeply nested union or tuple types
|
|
3
|
+
* Rule ID: custom/t025
|
|
4
|
+
* Purpose: Prevent complex nested union or tuple types to maintain type readability and simplicity
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Avoid deeply nested union or tuple types",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
nestedUnion: "Avoid nested union types.",
|
|
17
|
+
nestedTuple: "Avoid nested tuple types."
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
function checkNestedTypes(node) {
|
|
22
|
+
if (node.type === 'TSUnionType') {
|
|
23
|
+
const nested = node.types.some(t => t.type === 'TSUnionType' || t.type === 'TSTupleType');
|
|
24
|
+
if (nested) {
|
|
25
|
+
context.report({
|
|
26
|
+
node,
|
|
27
|
+
messageId: "nestedUnion"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (node.type === 'TSTupleType') {
|
|
33
|
+
const nested = node.elementTypes.some(t => t.type === 'TSTupleType' || t.type === 'TSUnionType');
|
|
34
|
+
if (nested) {
|
|
35
|
+
context.report({
|
|
36
|
+
node,
|
|
37
|
+
messageId: "nestedTuple"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
TSUnionType: checkNestedTypes,
|
|
45
|
+
TSTupleType: checkNestedTypes
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Rule T019: Do not assign to this arbitrarily
|
|
3
|
+
* @description Maintain proper context and avoid this manipulation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'problem',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Do not assign to this arbitrarily',
|
|
11
|
+
category: 'Best Practices',
|
|
12
|
+
recommended: true
|
|
13
|
+
},
|
|
14
|
+
fixable: null,
|
|
15
|
+
schema: []
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
// Detect direct assignment to 'this'
|
|
21
|
+
AssignmentExpression(node) {
|
|
22
|
+
if (node.left.type === 'ThisExpression') {
|
|
23
|
+
context.report({
|
|
24
|
+
node,
|
|
25
|
+
message: 'T019: Do not assign to "this" directly. Use proper binding, arrow functions, or explicit parameter passing.'
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// Detect patterns like "const that = this" or "var self = this"
|
|
31
|
+
VariableDeclarator(node) {
|
|
32
|
+
if (node.init && node.init.type === 'ThisExpression') {
|
|
33
|
+
// Common patterns: that, self, _this, me
|
|
34
|
+
const variableName = node.id.name;
|
|
35
|
+
const suspiciousNames = ['that', 'self', '_this', 'me', '_self'];
|
|
36
|
+
|
|
37
|
+
if (suspiciousNames.includes(variableName.toLowerCase())) {
|
|
38
|
+
context.report({
|
|
39
|
+
node,
|
|
40
|
+
message: `T019: Avoid storing "this" in variable "${variableName}". Use arrow functions or proper binding instead.`
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Detect method calls that bind this arbitrarily
|
|
47
|
+
CallExpression(node) {
|
|
48
|
+
// Detect .bind(this) patterns that might be problematic
|
|
49
|
+
if (node.callee.type === 'MemberExpression' &&
|
|
50
|
+
node.callee.property.name === 'bind' &&
|
|
51
|
+
node.arguments.length > 0) {
|
|
52
|
+
|
|
53
|
+
const bindTarget = node.arguments[0];
|
|
54
|
+
if (bindTarget.type === 'ThisExpression') {
|
|
55
|
+
// Allow legitimate .bind(this) in constructors and specific patterns
|
|
56
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
57
|
+
let currentScope = sourceCode.getScope ? sourceCode.getScope(node) : null;
|
|
58
|
+
|
|
59
|
+
if (!currentScope && context.getScope) {
|
|
60
|
+
currentScope = context.getScope();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const parent = currentScope ? currentScope.block : null;
|
|
64
|
+
const isInConstructor = parent &&
|
|
65
|
+
parent.type === 'FunctionExpression' &&
|
|
66
|
+
parent.parent &&
|
|
67
|
+
parent.parent.key &&
|
|
68
|
+
parent.parent.key.name === 'constructor';
|
|
69
|
+
|
|
70
|
+
if (!isInConstructor) {
|
|
71
|
+
context.report({
|
|
72
|
+
node,
|
|
73
|
+
message: 'T019: Consider using arrow functions instead of .bind(this) for cleaner context handling.'
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Rule T020: Avoid export default for multi-responsibility modules
|
|
3
|
+
* @description Improve tree-shaking and module clarity
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'suggestion',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Avoid export default for multi-responsibility modules',
|
|
11
|
+
category: 'Best Practices',
|
|
12
|
+
recommended: true
|
|
13
|
+
},
|
|
14
|
+
fixable: null,
|
|
15
|
+
schema: []
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
create(context) {
|
|
19
|
+
let hasDefaultExport = false;
|
|
20
|
+
let namedExports = [];
|
|
21
|
+
let exportedFunctions = [];
|
|
22
|
+
let exportedClasses = [];
|
|
23
|
+
let exportedConstants = [];
|
|
24
|
+
|
|
25
|
+
function analyzeExport(node, exportType) {
|
|
26
|
+
if (exportType === 'default') {
|
|
27
|
+
hasDefaultExport = true;
|
|
28
|
+
} else {
|
|
29
|
+
namedExports.push(node);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
// Track export default statements
|
|
35
|
+
ExportDefaultDeclaration(node) {
|
|
36
|
+
hasDefaultExport = true;
|
|
37
|
+
|
|
38
|
+
// Analyze what's being exported as default
|
|
39
|
+
if (node.declaration) {
|
|
40
|
+
if (node.declaration.type === 'FunctionDeclaration') {
|
|
41
|
+
exportedFunctions.push(node.declaration);
|
|
42
|
+
} else if (node.declaration.type === 'ClassDeclaration') {
|
|
43
|
+
exportedClasses.push(node.declaration);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Track named exports
|
|
49
|
+
ExportNamedDeclaration(node) {
|
|
50
|
+
namedExports.push(node);
|
|
51
|
+
|
|
52
|
+
if (node.declaration) {
|
|
53
|
+
if (node.declaration.type === 'FunctionDeclaration') {
|
|
54
|
+
exportedFunctions.push(node.declaration);
|
|
55
|
+
} else if (node.declaration.type === 'ClassDeclaration') {
|
|
56
|
+
exportedClasses.push(node.declaration);
|
|
57
|
+
} else if (node.declaration.type === 'VariableDeclaration') {
|
|
58
|
+
exportedConstants.push(node.declaration);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (node.specifiers) {
|
|
63
|
+
node.specifiers.forEach(spec => {
|
|
64
|
+
if (spec.type === 'ExportSpecifier') {
|
|
65
|
+
namedExports.push(spec);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// Check at the end of the program
|
|
72
|
+
'Program:exit'() {
|
|
73
|
+
if (!hasDefaultExport) {
|
|
74
|
+
return; // No default export, rule doesn't apply
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Count total exports
|
|
78
|
+
const totalNamedExports = namedExports.length;
|
|
79
|
+
const totalFunctions = exportedFunctions.length;
|
|
80
|
+
const totalClasses = exportedClasses.length;
|
|
81
|
+
const totalConstants = exportedConstants.length;
|
|
82
|
+
|
|
83
|
+
// Multi-responsibility indicators:
|
|
84
|
+
// 1. Multiple named exports alongside default export
|
|
85
|
+
// 2. Multiple functions being exported
|
|
86
|
+
// 3. Multiple classes being exported
|
|
87
|
+
// 4. Mix of functions, classes, and constants
|
|
88
|
+
|
|
89
|
+
const isMultiResponsibility =
|
|
90
|
+
totalNamedExports > 0 || // Has both default and named exports
|
|
91
|
+
totalFunctions > 1 || // Multiple functions
|
|
92
|
+
totalClasses > 1 || // Multiple classes
|
|
93
|
+
(totalFunctions > 0 && totalClasses > 0) || // Mix of functions and classes
|
|
94
|
+
(totalConstants > 2); // Too many constants
|
|
95
|
+
|
|
96
|
+
if (isMultiResponsibility) {
|
|
97
|
+
// Find the default export node to report
|
|
98
|
+
const sourceCode = context.getSourceCode();
|
|
99
|
+
const defaultExportNode = sourceCode.ast.body.find(
|
|
100
|
+
node => node.type === 'ExportDefaultDeclaration'
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (defaultExportNode) {
|
|
104
|
+
let message = 'T020: Avoid export default for multi-responsibility modules. ';
|
|
105
|
+
|
|
106
|
+
if (totalNamedExports > 0) {
|
|
107
|
+
message += `Found ${totalNamedExports} named exports alongside default export. `;
|
|
108
|
+
}
|
|
109
|
+
if (totalFunctions > 1) {
|
|
110
|
+
message += `Found ${totalFunctions} exported functions. `;
|
|
111
|
+
}
|
|
112
|
+
if (totalClasses > 1) {
|
|
113
|
+
message += `Found ${totalClasses} exported classes. `;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
message += 'Consider using only named exports for better tree-shaking and clarity.';
|
|
117
|
+
|
|
118
|
+
context.report({
|
|
119
|
+
node: defaultExportNode,
|
|
120
|
+
message
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Rule T021: Limit deeply nested generics
|
|
3
|
+
* @description Improve code readability and TypeScript performance
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'suggestion',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Limit deeply nested generics',
|
|
11
|
+
category: 'Best Practices',
|
|
12
|
+
recommended: true
|
|
13
|
+
},
|
|
14
|
+
fixable: null,
|
|
15
|
+
schema: [
|
|
16
|
+
{
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
maxDepth: {
|
|
20
|
+
type: 'integer',
|
|
21
|
+
minimum: 1,
|
|
22
|
+
default: 3
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
additionalProperties: false
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
create(context) {
|
|
31
|
+
const options = context.options[0] || {};
|
|
32
|
+
const maxDepth = options.maxDepth || 3;
|
|
33
|
+
|
|
34
|
+
function getGenericDepth(typeNode) {
|
|
35
|
+
if (!typeNode) return 0;
|
|
36
|
+
|
|
37
|
+
switch (typeNode.type) {
|
|
38
|
+
case 'TSTypeReference':
|
|
39
|
+
// Generic types like Array<T>, Promise<U>, etc.
|
|
40
|
+
// ESLint uses 'typeArguments' instead of 'typeParameters'
|
|
41
|
+
if (typeNode.typeArguments && typeNode.typeArguments.params) {
|
|
42
|
+
let maxChildDepth = 0;
|
|
43
|
+
for (const param of typeNode.typeArguments.params) {
|
|
44
|
+
const childDepth = getGenericDepth(param);
|
|
45
|
+
maxChildDepth = Math.max(maxChildDepth, childDepth);
|
|
46
|
+
}
|
|
47
|
+
return 1 + maxChildDepth;
|
|
48
|
+
}
|
|
49
|
+
return 0;
|
|
50
|
+
|
|
51
|
+
case 'TSArrayType':
|
|
52
|
+
// Array types like T[]
|
|
53
|
+
return 1 + getGenericDepth(typeNode.elementType);
|
|
54
|
+
|
|
55
|
+
case 'TSUnionType':
|
|
56
|
+
case 'TSIntersectionType':
|
|
57
|
+
// Union types like A | B or intersection types A & B
|
|
58
|
+
let maxDepth = 0;
|
|
59
|
+
for (const typeItem of typeNode.types) {
|
|
60
|
+
maxDepth = Math.max(maxDepth, getGenericDepth(typeItem));
|
|
61
|
+
}
|
|
62
|
+
return maxDepth;
|
|
63
|
+
|
|
64
|
+
case 'TSTupleType':
|
|
65
|
+
// Tuple types like [A, B, C]
|
|
66
|
+
let maxTupleDepth = 0;
|
|
67
|
+
for (const elementType of typeNode.elementTypes) {
|
|
68
|
+
maxTupleDepth = Math.max(maxTupleDepth, getGenericDepth(elementType));
|
|
69
|
+
}
|
|
70
|
+
return 1 + maxTupleDepth;
|
|
71
|
+
|
|
72
|
+
case 'TSMappedType':
|
|
73
|
+
// Mapped types like { [K in keyof T]: U }
|
|
74
|
+
return 1 + getGenericDepth(typeNode.typeAnnotation);
|
|
75
|
+
|
|
76
|
+
case 'TSConditionalType':
|
|
77
|
+
// Conditional types like T extends U ? A : B
|
|
78
|
+
return 1 + Math.max(
|
|
79
|
+
getGenericDepth(typeNode.trueType),
|
|
80
|
+
getGenericDepth(typeNode.falseType)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
default:
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function checkTypeDepth(node, typeName) {
|
|
89
|
+
const depth = getGenericDepth(node);
|
|
90
|
+
if (depth > maxDepth) {
|
|
91
|
+
context.report({
|
|
92
|
+
node,
|
|
93
|
+
message: `T021: Generic nesting depth of ${depth} exceeds maximum of ${maxDepth}. Consider breaking down complex types into intermediate type aliases for better readability and TypeScript compiler performance.`
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
// Check type aliases - this is the main entry point
|
|
100
|
+
TSTypeAliasDeclaration(node) {
|
|
101
|
+
checkTypeDepth(node.typeAnnotation, node.id.name);
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// Check interface properties
|
|
105
|
+
TSPropertySignature(node) {
|
|
106
|
+
if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) {
|
|
107
|
+
checkTypeDepth(node.typeAnnotation.typeAnnotation, 'property');
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
// Check function parameters
|
|
112
|
+
TSParameterProperty(node) {
|
|
113
|
+
if (node.parameter && node.parameter.typeAnnotation) {
|
|
114
|
+
checkTypeDepth(node.parameter.typeAnnotation.typeAnnotation, 'parameter');
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// Check function return types
|
|
119
|
+
TSFunctionType(node) {
|
|
120
|
+
if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) {
|
|
121
|
+
checkTypeDepth(node.typeAnnotation.typeAnnotation, 'return type');
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Check variable declarations
|
|
126
|
+
VariableDeclarator(node) {
|
|
127
|
+
if (node.id && node.id.typeAnnotation && node.id.typeAnnotation.typeAnnotation) {
|
|
128
|
+
checkTypeDepth(node.id.typeAnnotation.typeAnnotation, 'variable');
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// Check generic constraints
|
|
133
|
+
TSTypeParameter(node) {
|
|
134
|
+
if (node.constraint) {
|
|
135
|
+
checkTypeDepth(node.constraint, 'generic constraint');
|
|
136
|
+
}
|
|
137
|
+
if (node.default) {
|
|
138
|
+
checkTypeDepth(node.default, 'generic default');
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
// Check method signatures
|
|
143
|
+
TSMethodSignature(node) {
|
|
144
|
+
if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) {
|
|
145
|
+
checkTypeDepth(node.typeAnnotation.typeAnnotation, 'method return type');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const rule = require('./plugin/rules/common/c041-no-config-inline.js');
|
|
4
|
+
|
|
5
|
+
// Simple test cases
|
|
6
|
+
const testCases = [
|
|
7
|
+
// Should NOT flag (false positives to avoid)
|
|
8
|
+
{ code: 'const inputType = "password";', shouldError: false, desc: 'InputType definition' },
|
|
9
|
+
{ code: 'const fieldType = "password";', shouldError: false, desc: 'Field type' },
|
|
10
|
+
{ code: 'const usePassword = "password_validation";', shouldError: false, desc: 'Hook usage' },
|
|
11
|
+
{ code: 'const testPassword = "test123";', shouldError: false, desc: 'Test data' },
|
|
12
|
+
{ code: 'const mockSecret = "mock_secret_key";', shouldError: false, desc: 'Mock data' },
|
|
13
|
+
{ code: 'const route = "/reset_password";', shouldError: false, desc: 'Route path' },
|
|
14
|
+
|
|
15
|
+
// SHOULD flag (real sensitive data)
|
|
16
|
+
{ code: 'const dbPassword = "MySecretP@ssw0rd123";', shouldError: true, desc: 'Real hardcoded password' },
|
|
17
|
+
{ code: 'const apiKey = "sk-1234567890abcdef1234567890abcdef";', shouldError: true, desc: 'Real API key' },
|
|
18
|
+
{ code: 'const authToken = "bearer_token_12345678901234567";', shouldError: true, desc: 'Real auth token' }
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
console.log('🧪 Testing ESLint C041 rule manually...\n');
|
|
22
|
+
|
|
23
|
+
let passed = 0;
|
|
24
|
+
let failed = 0;
|
|
25
|
+
|
|
26
|
+
// Mock context for testing
|
|
27
|
+
function createMockContext(code) {
|
|
28
|
+
return {
|
|
29
|
+
getSourceCode: () => ({
|
|
30
|
+
lines: code.split('\n')
|
|
31
|
+
}),
|
|
32
|
+
report: ({ node, messageId }) => {
|
|
33
|
+
return { reported: true, messageId };
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
testCases.forEach((testCase, index) => {
|
|
39
|
+
try {
|
|
40
|
+
let reported = false;
|
|
41
|
+
|
|
42
|
+
// Parse the code to extract literal nodes (simplified)
|
|
43
|
+
const literalMatch = testCase.code.match(/"([^"]+)"/);
|
|
44
|
+
if (literalMatch) {
|
|
45
|
+
const mockNode = {
|
|
46
|
+
value: literalMatch[1],
|
|
47
|
+
loc: { start: { line: 1 } }
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const mockContext = {
|
|
51
|
+
getSourceCode: () => ({
|
|
52
|
+
lines: [testCase.code]
|
|
53
|
+
}),
|
|
54
|
+
report: ({ node, messageId }) => {
|
|
55
|
+
reported = true;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const ruleImplementation = rule.create(mockContext);
|
|
60
|
+
ruleImplementation.Literal(mockNode);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const testPassed = reported === testCase.shouldError;
|
|
64
|
+
|
|
65
|
+
if (testPassed) {
|
|
66
|
+
console.log(`✅ Test ${index + 1}: ${testCase.desc}`);
|
|
67
|
+
passed++;
|
|
68
|
+
} else {
|
|
69
|
+
console.log(`❌ Test ${index + 1}: ${testCase.desc}`);
|
|
70
|
+
console.log(` Expected: ${testCase.shouldError ? 'Should flag' : 'Should NOT flag'}`);
|
|
71
|
+
console.log(` Actual: ${reported ? 'Flagged' : 'Not flagged'}`);
|
|
72
|
+
failed++;
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.log(`💥 Test ${index + 1}: ${testCase.desc} - Error: ${error.message}`);
|
|
76
|
+
failed++;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
console.log(`\n📊 Test Results: ${passed} passed, ${failed} failed`);
|
|
81
|
+
console.log(`Success rate: ${Math.round((passed / (passed + failed)) * 100)}%`);
|
|
82
|
+
|
|
83
|
+
if (failed === 0) {
|
|
84
|
+
console.log('\n🎉 All ESLint rule tests passed!');
|
|
85
|
+
} else {
|
|
86
|
+
console.log('\n⚠️ Some tests failed. Review the rule logic.');
|
|
87
|
+
}
|
|
@@ -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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sunlint",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "☀️ Sun Lint - Universal Coding Standards | Multi-rule Quality & Security Analysis with ESLint Integration",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,18 +16,21 @@
|
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"test": "
|
|
20
|
-
"test:
|
|
21
|
-
"test:
|
|
22
|
-
"test:
|
|
23
|
-
"test:
|
|
24
|
-
"test:
|
|
25
|
-
"test:
|
|
26
|
-
"test:
|
|
27
|
-
"test:
|
|
28
|
-
"test:
|
|
19
|
+
"test": "node examples/run-tests.js",
|
|
20
|
+
"test:examples": "node examples/integration-tests/examples-integration-test.js",
|
|
21
|
+
"test:architecture": "node examples/integration-tests/core-architecture-test.js",
|
|
22
|
+
"test:integration": "echo 'Temporarily disabled - config extension issue'",
|
|
23
|
+
"test:realworld": "node examples/integration-tests/realworld-integration-test.js",
|
|
24
|
+
"test:cli": "node examples/integration-tests/direct-cli-test.js",
|
|
25
|
+
"test:c019": "node cli.js --rule=C019 --input=examples/test-fixtures --format=eslint",
|
|
26
|
+
"test:c006": "node cli.js --rule=C006 --input=examples/test-fixtures --format=eslint",
|
|
27
|
+
"test:c029": "node cli.js --rule=C029 --input=examples/test-fixtures --format=eslint",
|
|
28
|
+
"test:multi": "node cli.js --rules=C019,C006,C029 --input=examples/test-fixtures --format=eslint",
|
|
29
|
+
"test:all": "node cli.js --all --input=examples/test-fixtures --format=eslint",
|
|
30
|
+
"test:quality": "node cli.js --category=quality --input=examples/test-fixtures --format=eslint",
|
|
31
|
+
"test:security": "node cli.js --category=security --input=examples/test-fixtures --format=eslint",
|
|
29
32
|
"demo": "./demo.sh",
|
|
30
|
-
"demo:single": "node cli.js --rule=C019 --input=test
|
|
33
|
+
"demo:single": "node cli.js --rule=C019 --input=examples/test-fixtures/typescript --format=eslint",
|
|
31
34
|
"demo:multi": "node cli.js --rules=C019,C006 --input=test/fixtures --format=summary",
|
|
32
35
|
"demo:quality": "node cli.js --quality --input=test/fixtures --format=summary",
|
|
33
36
|
"demo:security": "node cli.js --security --input=test/fixtures --format=summary",
|
|
@@ -68,19 +71,26 @@
|
|
|
68
71
|
"core/",
|
|
69
72
|
"rules/",
|
|
70
73
|
"config/",
|
|
74
|
+
"engines/",
|
|
75
|
+
"integrations/",
|
|
76
|
+
"scripts/",
|
|
77
|
+
"docs/",
|
|
78
|
+
".sunlint.json",
|
|
71
79
|
"README.md",
|
|
72
80
|
"LICENSE",
|
|
73
|
-
"CHANGELOG.md"
|
|
81
|
+
"CHANGELOG.md",
|
|
82
|
+
"PROJECT_STRUCTURE.md",
|
|
83
|
+
"CONTRIBUTING.md"
|
|
74
84
|
],
|
|
75
85
|
"engines": {
|
|
76
86
|
"node": ">=18.18.0"
|
|
77
87
|
},
|
|
78
88
|
"dependencies": {
|
|
79
|
-
"@
|
|
80
|
-
"@typescript-eslint/parser": "^8.
|
|
89
|
+
"@babel/parser": "^7.23.0",
|
|
90
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
81
91
|
"chalk": "^4.1.2",
|
|
82
92
|
"commander": "^12.1.0",
|
|
83
|
-
"
|
|
93
|
+
"espree": "^9.6.0",
|
|
84
94
|
"glob": "^11.0.0",
|
|
85
95
|
"minimatch": "^10.0.3",
|
|
86
96
|
"node-fetch": "^3.3.2",
|
|
@@ -89,6 +99,9 @@
|
|
|
89
99
|
},
|
|
90
100
|
"devDependencies": {
|
|
91
101
|
"@types/node": "^22.10.1",
|
|
102
|
+
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
103
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
104
|
+
"eslint": "^9.31.0",
|
|
92
105
|
"jest": "^29.7.0"
|
|
93
106
|
},
|
|
94
107
|
"bugs": {
|