@sun-asterisk/sunlint 1.0.7 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.sunlint.json +35 -0
- package/CHANGELOG.md +30 -3
- package/CONTRIBUTING.md +235 -0
- package/PROJECT_STRUCTURE.md +60 -0
- package/README.md +146 -58
- package/cli.js +1 -0
- package/config/README.md +88 -0
- package/config/defaults/ai-rules-context.json +231 -0
- package/config/engines/engines.json +49 -0
- package/config/engines/eslint-rule-mapping.json +74 -0
- package/config/eslint-rule-mapping.json +126 -0
- package/config/integrations/eslint/base.config.js +125 -0
- package/config/integrations/eslint/simple.config.js +24 -0
- package/config/presets/strict.json +0 -1
- package/config/rule-analysis-strategies.js +74 -0
- package/config/{rules-registry.json → rules/rules-registry.json} +30 -7
- package/core/analysis-orchestrator.js +383 -591
- package/core/ast-modules/README.md +103 -0
- package/core/ast-modules/base-parser.js +90 -0
- package/core/ast-modules/index.js +97 -0
- package/core/ast-modules/package.json +37 -0
- package/core/ast-modules/parsers/eslint-js-parser.js +153 -0
- package/core/ast-modules/parsers/eslint-ts-parser.js +98 -0
- package/core/ast-modules/parsers/javascript-parser.js +187 -0
- package/core/ast-modules/parsers/typescript-parser.js +187 -0
- package/core/cli-action-handler.js +271 -255
- package/core/cli-program.js +18 -4
- package/core/config-manager.js +9 -3
- package/core/config-merger.js +40 -1
- package/core/config-validator.js +2 -2
- package/core/dependency-checker.js +125 -0
- package/core/enhanced-rules-registry.js +331 -0
- package/core/file-targeting-service.js +92 -23
- package/core/interfaces/analysis-engine.interface.js +100 -0
- package/core/multi-rule-runner.js +0 -221
- package/core/output-service.js +1 -1
- package/core/rule-mapping-service.js +1 -1
- package/core/rule-selection-service.js +10 -2
- package/core/smart-installer.js +164 -0
- package/docs/AI.md +163 -0
- package/docs/ARCHITECTURE.md +78 -0
- package/docs/CI-CD-GUIDE.md +315 -0
- package/docs/COMMAND-EXAMPLES.md +256 -0
- package/docs/CONFIGURATION.md +414 -0
- package/docs/DEBUG.md +86 -0
- package/docs/DEPENDENCIES.md +90 -0
- package/docs/DEPLOYMENT-STRATEGIES.md +270 -0
- package/docs/DISTRIBUTION.md +153 -0
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
- package/docs/ESLINT_INTEGRATION.md +238 -0
- package/docs/FOLDER_STRUCTURE.md +59 -0
- package/docs/FUTURE_PACKAGES.md +83 -0
- package/docs/HEURISTIC_VS_AI.md +113 -0
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +112 -0
- package/docs/PRODUCTION_SIZE_IMPACT.md +183 -0
- package/docs/README.md +32 -0
- package/docs/RELEASE_GUIDE.md +230 -0
- package/engines/eslint-engine.js +610 -0
- package/engines/heuristic-engine.js +864 -0
- package/engines/openai-engine.js +374 -0
- package/engines/tree-sitter-parser.js +0 -0
- package/engines/universal-ast-engine.js +0 -0
- package/integrations/eslint/README.md +99 -0
- package/integrations/eslint/configs/.eslintrc.js +98 -0
- package/integrations/eslint/configs/eslint.config.js +133 -0
- package/integrations/eslint/configs/eslint.config.simple.js +24 -0
- package/integrations/eslint/package.json +23 -0
- package/integrations/eslint/plugin/index.js +164 -0
- package/integrations/eslint/plugin/package.json +13 -0
- package/integrations/eslint/plugin/rules/common/c002-no-duplicate-code.js +204 -0
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +246 -0
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +216 -0
- package/integrations/eslint/plugin/rules/common/c010-limit-block-nesting.js +90 -0
- package/integrations/eslint/plugin/rules/common/c013-no-dead-code.js +78 -0
- package/integrations/eslint/plugin/rules/common/c014-abstract-dependency-preferred.js +38 -0
- package/integrations/eslint/plugin/rules/common/c017-limit-constructor-logic.js +146 -0
- package/integrations/eslint/plugin/rules/common/c018-no-generic-throw.js +335 -0
- package/integrations/eslint/plugin/rules/common/c023-no-duplicate-variable-name-in-scope.js +142 -0
- package/integrations/eslint/plugin/rules/common/c029-catch-block-logging.js +115 -0
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +294 -0
- package/integrations/eslint/plugin/rules/common/c035-no-empty-catch.js +162 -0
- package/integrations/eslint/plugin/rules/common/c041-no-config-inline.js +122 -0
- package/integrations/eslint/plugin/rules/common/c042-boolean-name-prefix.js +406 -0
- package/integrations/eslint/plugin/rules/common/c043-no-console-or-print.js +300 -0
- package/integrations/eslint/plugin/rules/common/c047-no-duplicate-retry-logic.js +239 -0
- package/integrations/eslint/plugin/rules/common/c072-one-assert-per-test.js +184 -0
- package/integrations/eslint/plugin/rules/common/c075-explicit-function-return-types.js +168 -0
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +254 -0
- package/integrations/eslint/plugin/rules/security/s001-fail-securely.js +381 -0
- package/integrations/eslint/plugin/rules/security/s002-idor-check.js +945 -0
- package/integrations/eslint/plugin/rules/security/s003-no-unvalidated-redirect.js +86 -0
- package/integrations/eslint/plugin/rules/security/s007-no-plaintext-otp.js +74 -0
- package/integrations/eslint/plugin/rules/security/s013-verify-tls-connection.js +47 -0
- package/integrations/eslint/plugin/rules/security/s047-secure-random-passwords.js +108 -0
- package/integrations/eslint/plugin/rules/security/s055-verification-rest-check-the-incoming-content-type.js +143 -0
- package/integrations/eslint/plugin/rules/typescript/t002-interface-prefix-i.js +42 -0
- package/integrations/eslint/plugin/rules/typescript/t003-ts-ignore-reason.js +48 -0
- package/integrations/eslint/plugin/rules/typescript/t004-no-empty-type.js +95 -0
- package/integrations/eslint/plugin/rules/typescript/t007-no-fn-in-constructor.js +52 -0
- package/integrations/eslint/plugin/rules/typescript/t010-no-nested-union-tuple.js +48 -0
- package/integrations/eslint/plugin/rules/typescript/t019-no-this-assign.js +81 -0
- package/integrations/eslint/plugin/rules/typescript/t020-no-default-multi-export.js +127 -0
- package/integrations/eslint/plugin/rules/typescript/t021-limit-nested-generics.js +150 -0
- package/integrations/eslint/tsconfig.json +27 -0
- package/package.json +61 -21
- package/rules/README.md +252 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
- package/rules/common/C002_no_duplicate_code/config.json +23 -0
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
- package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
- package/rules/{C006_function_naming → common/C006_function_naming}/analyzer.js +13 -2
- package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
- package/rules/common/C013_no_dead_code/analyzer.js +206 -0
- package/rules/common/C014_dependency_injection/analyzer.js +338 -0
- package/rules/common/C017_constructor_logic/analyzer.js +314 -0
- package/rules/{C019_log_level_usage → common/C019_log_level_usage}/analyzer.js +5 -2
- package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/analyzer.js +49 -15
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
- package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
- package/rules/common/C043_no_console_or_print/analyzer.js +304 -0
- package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +351 -0
- package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
- package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
- package/rules/docs/C002_no_duplicate_code.md +57 -0
- package/rules/index.js +149 -0
- package/rules/migration/converter.js +385 -0
- package/rules/migration/mapping.json +164 -0
- package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
- package/rules/security/S026_json_schema_validation/config.json +27 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +263 -0
- package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
- package/rules/security/S029_csrf_protection/analyzer.js +264 -0
- package/rules/tests/C002_no_duplicate_code.test.js +50 -0
- package/rules/universal/C010/generic.js +0 -0
- package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
- package/rules/utils/ast-utils.js +191 -0
- package/rules/utils/base-analyzer.js +98 -0
- package/rules/utils/pattern-matchers.js +239 -0
- package/rules/utils/rule-helpers.js +264 -0
- package/rules/utils/severity-constants.js +93 -0
- package/scripts/build-release.sh +117 -0
- package/scripts/ci-report.js +179 -0
- package/scripts/install.sh +196 -0
- package/scripts/manual-release.sh +338 -0
- package/scripts/merge-reports.js +424 -0
- package/scripts/pre-release-test.sh +175 -0
- package/scripts/prepare-release.sh +202 -0
- package/scripts/setup-github-registry.sh +42 -0
- package/scripts/test-scripts/README.md +22 -0
- package/scripts/test-scripts/test-c041-comparison.js +114 -0
- package/scripts/test-scripts/test-c041-eslint.js +67 -0
- package/scripts/test-scripts/test-eslint-rules.js +146 -0
- package/scripts/test-scripts/test-real-world.js +44 -0
- package/scripts/test-scripts/test-rules-on-real-projects.js +86 -0
- package/scripts/trigger-release.sh +285 -0
- package/scripts/validate-rule-structure.js +148 -0
- package/scripts/verify-install.sh +82 -0
- package/config/sunlint-schema.json +0 -159
- package/config/typescript/custom-rules.js +0 -9
- package/config/typescript/package-lock.json +0 -1585
- package/config/typescript/package.json +0 -13
- package/config/typescript/security-rules/index.js +0 -90
- package/config/typescript/tsconfig.json +0 -29
- package/core/ai-analyzer.js +0 -169
- package/core/eslint-engine-service.js +0 -312
- package/core/eslint-instance-manager.js +0 -104
- package/core/eslint-integration-service.js +0 -363
- package/core/sunlint-engine-service.js +0 -23
- package/core/typescript-analyzer.js +0 -262
- package/core/typescript-engine.js +0 -313
- /package/config/{default.json → defaults/default.json} +0 -0
- /package/config/{typescript/eslint.config.js → integrations/eslint/typescript.config.js} +0 -0
- /package/config/{typescript/custom-rules-new.js → schemas/sunlint-schema.json} +0 -0
- /package/config/{typescript → testing}/test-s005-working.ts +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s005-no-origin-auth.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s006-activation-recovery-secret-not-plaintext.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s008-crypto-agility.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s009-no-insecure-crypto.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s010-no-insecure-random-in-sensitive-context.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s011-no-insecure-uuid.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s012-hardcode-secret.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s014-insecure-tls-version.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s015-insecure-tls-certificate.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s016-sensitive-query-parameter.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s017-no-sql-injection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s018-positive-input-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s019-no-raw-user-input-in-email.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s020-no-eval-dynamic-execution.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s022-output-encoding.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s023-no-json-injection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s025-server-side-input-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s026-json-schema-validation.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s027-no-hardcoded-secrets.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s029-require-csrf-protection.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s030-no-directory-browsing.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s033-require-samesite-cookie.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s034-require-host-cookie-prefix.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s035-cookie-specific-path.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s036-no-unsafe-file-include.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s037-require-anti-cache-headers.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s038-no-version-disclosure.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s039-no-session-token-in-url.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s041-require-session-invalidate-on-logout.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s042-require-periodic-reauthentication.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s043-terminate-sessions-on-password-change.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s044-require-full-session-for-sensitive-operations.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s045-anti-automation-controls.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s046-secure-notification-on-auth-change.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s048-password-credential-recovery.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s050-session-token-weak-hash.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s052-secure-random-authentication-code.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s054-verification-default-account.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s057-utc-logging.js +0 -0
- /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s058-no-ssrf.js +0 -0
- /package/rules/{C006_function_naming → common/C006_function_naming}/config.json +0 -0
- /package/rules/{C019_log_level_usage → common/C019_log_level_usage}/config.json +0 -0
- /package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/config.json +0 -0
- /package/rules/{C031_validation_separation → common/C031_validation_separation}/analyzer.js +0 -0
- /package/rules/{C031_validation_separation/README.md → docs/C031_validation_separation.md} +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C029 – Every `catch` block must log the error cause
|
|
3
|
+
* Rule ID: custom/c029
|
|
4
|
+
* Goal: Catching errors without logging/rethrowing can hide bugs
|
|
5
|
+
*
|
|
6
|
+
* NOTE: This ESLint rule provides basic catch block validation.
|
|
7
|
+
* For enhanced analysis with context validation and multi-level severity,
|
|
8
|
+
* use SunLint C029 which offers superior detection capabilities.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
docs: {
|
|
15
|
+
description: "Every `catch` block must log the error cause (C029)",
|
|
16
|
+
recommended: true,
|
|
17
|
+
url: "https://coding-standards.sun.com/rules/c029"
|
|
18
|
+
},
|
|
19
|
+
schema: [],
|
|
20
|
+
messages: {
|
|
21
|
+
silentCatch: "Catch block must log error or rethrow - silent error handling hides bugs (C029)",
|
|
22
|
+
emptyCatch: "Empty catch block - error is silently ignored (C029)"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
return {
|
|
27
|
+
CatchClause(node) {
|
|
28
|
+
const body = node.body && node.body.body;
|
|
29
|
+
|
|
30
|
+
// Check for empty catch blocks
|
|
31
|
+
if (!Array.isArray(body) || body.length === 0) {
|
|
32
|
+
context.report({
|
|
33
|
+
node,
|
|
34
|
+
messageId: "emptyCatch"
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const hasLogOrThrow = body.some(stmt => {
|
|
40
|
+
// Check for throw statements
|
|
41
|
+
if (stmt.type === "ThrowStatement") {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check for test assertions (Jest patterns)
|
|
46
|
+
if (stmt.type === "ExpressionStatement" &&
|
|
47
|
+
stmt.expression.type === "CallExpression" &&
|
|
48
|
+
stmt.expression.callee &&
|
|
49
|
+
stmt.expression.callee.name === "expect") {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check for Redux thunk error handling patterns
|
|
54
|
+
if (stmt.type === "VariableDeclaration" &&
|
|
55
|
+
stmt.declarations.some(decl =>
|
|
56
|
+
decl.init &&
|
|
57
|
+
decl.init.type === "CallExpression" &&
|
|
58
|
+
decl.init.callee &&
|
|
59
|
+
decl.init.callee.name === "handleAxiosError")) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check for return rejectWithValue
|
|
64
|
+
if (stmt.type === "ReturnStatement" &&
|
|
65
|
+
stmt.argument &&
|
|
66
|
+
stmt.argument.type === "CallExpression" &&
|
|
67
|
+
stmt.argument.callee &&
|
|
68
|
+
stmt.argument.callee.name === "rejectWithValue") {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check for dispatch calls (Redux patterns)
|
|
73
|
+
if (stmt.type === "ExpressionStatement" &&
|
|
74
|
+
stmt.expression.type === "CallExpression" &&
|
|
75
|
+
stmt.expression.callee &&
|
|
76
|
+
stmt.expression.callee.name === "dispatch") {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check for console.log, console.error, console.warn
|
|
81
|
+
if (stmt.type === "ExpressionStatement" &&
|
|
82
|
+
stmt.expression.type === "CallExpression" &&
|
|
83
|
+
stmt.expression.callee &&
|
|
84
|
+
stmt.expression.callee.type === "MemberExpression" &&
|
|
85
|
+
stmt.expression.callee.object.name === "console" &&
|
|
86
|
+
(stmt.expression.callee.property.name === "log" ||
|
|
87
|
+
stmt.expression.callee.property.name === "error" ||
|
|
88
|
+
stmt.expression.callee.property.name === "warn")) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check for custom logger calls (logger.error, log.error, etc.)
|
|
93
|
+
if (stmt.type === "ExpressionStatement" &&
|
|
94
|
+
stmt.expression.type === "CallExpression" &&
|
|
95
|
+
stmt.expression.callee &&
|
|
96
|
+
stmt.expression.callee.type === "MemberExpression" &&
|
|
97
|
+
(stmt.expression.callee.property.name === "error" ||
|
|
98
|
+
stmt.expression.callee.property.name === "warn" ||
|
|
99
|
+
stmt.expression.callee.property.name === "log")) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return false;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!hasLogOrThrow) {
|
|
107
|
+
context.report({
|
|
108
|
+
node,
|
|
109
|
+
messageId: "silentCatch"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C030 – Dùng custom error class thay vì dùng lỗi hệ thống trực tiếp
|
|
3
|
+
* Rule ID: custom/c030
|
|
4
|
+
* Purpose: Enforce using custom error classes instead of generic Error class for better error handling and categorization
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const c030Rule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Use custom error classes instead of generic Error class",
|
|
12
|
+
recommended: true
|
|
13
|
+
},
|
|
14
|
+
schema: [
|
|
15
|
+
{
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
allowGenericInTests: {
|
|
19
|
+
type: "boolean",
|
|
20
|
+
description: "Whether to allow generic Error in test files (default: true)"
|
|
21
|
+
},
|
|
22
|
+
allowedBuiltinErrors: {
|
|
23
|
+
type: "array",
|
|
24
|
+
items: { type: "string" },
|
|
25
|
+
description: "Built-in error types that are allowed (e.g., TypeError, RangeError)"
|
|
26
|
+
},
|
|
27
|
+
customErrorClasses: {
|
|
28
|
+
type: "array",
|
|
29
|
+
items: { type: "string" },
|
|
30
|
+
description: "Custom error class names that are recommended"
|
|
31
|
+
},
|
|
32
|
+
allowRethrow: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Whether to allow rethrowing caught errors (default: true)"
|
|
35
|
+
},
|
|
36
|
+
strictMode: {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description: "Enable strict mode - only custom errors allowed (default: false)"
|
|
39
|
+
},
|
|
40
|
+
requireErrorCode: {
|
|
41
|
+
type: "boolean",
|
|
42
|
+
description: "Require custom errors to have error codes (default: true)"
|
|
43
|
+
},
|
|
44
|
+
requireStatusCode: {
|
|
45
|
+
type: "boolean",
|
|
46
|
+
description: "Require custom errors for HTTP to have status codes (default: false)"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
additionalProperties: false
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
messages: {
|
|
53
|
+
useCustomError: "Use custom error class instead of generic 'Error'. Consider using specific error types like ValidationError, NotFoundError, BusinessRuleError, etc. Vietnamese: 'Dùng custom error class thay vì Error generic'",
|
|
54
|
+
useSpecificBuiltin: "Consider using a more specific built-in error type like TypeError, RangeError, or a custom error class. Vietnamese: 'Cân nhắc dùng built-in error cụ thể hơn hoặc custom error class'",
|
|
55
|
+
missingErrorCode: "Custom error class should include an error code property. Vietnamese: 'Custom error class nên có thuộc tính error code'",
|
|
56
|
+
missingStatusCode: "HTTP-related error class should include a status code property. Vietnamese: 'Error class liên quan HTTP nên có thuộc tính status code'",
|
|
57
|
+
preferCustomError: "Prefer custom error classes for better error categorization and handling. Vietnamese: 'Ưu tiên custom error classes để phân loại và xử lý lỗi tốt hơn'"
|
|
58
|
+
},
|
|
59
|
+
fixable: null
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
create(context) {
|
|
63
|
+
const options = context.options[0] || {};
|
|
64
|
+
|
|
65
|
+
// Default configuration
|
|
66
|
+
const allowGenericInTests = options.allowGenericInTests !== false;
|
|
67
|
+
const allowedBuiltinErrors = new Set(options.allowedBuiltinErrors || [
|
|
68
|
+
'TypeError', 'RangeError', 'SyntaxError', 'ReferenceError', 'URIError', 'EvalError'
|
|
69
|
+
]);
|
|
70
|
+
const customErrorClasses = new Set(options.customErrorClasses || [
|
|
71
|
+
'ValidationError', 'NotFoundError', 'BusinessRuleError', 'BusinessError',
|
|
72
|
+
'ExternalServiceError', 'AuthenticationError', 'AuthorizationError',
|
|
73
|
+
'NetworkError', 'DatabaseError', 'ConfigurationError', 'TimeoutError'
|
|
74
|
+
]);
|
|
75
|
+
const allowRethrow = options.allowRethrow !== false;
|
|
76
|
+
const strictMode = options.strictMode || false;
|
|
77
|
+
const requireErrorCode = options.requireErrorCode !== false;
|
|
78
|
+
const requireStatusCode = options.requireStatusCode || false;
|
|
79
|
+
|
|
80
|
+
const sourceCode = context.getSourceCode();
|
|
81
|
+
const filename = context.getFilename();
|
|
82
|
+
|
|
83
|
+
function isTestFile() {
|
|
84
|
+
if (!allowGenericInTests) return false;
|
|
85
|
+
|
|
86
|
+
const testPatterns = ['.test.', '.spec.', '__tests__', '/test/', '/tests/', '.e2e.', '.stories.'];
|
|
87
|
+
return testPatterns.some(pattern => filename.includes(pattern));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isRethrowStatement(node) {
|
|
91
|
+
if (!allowRethrow) return false;
|
|
92
|
+
|
|
93
|
+
// Check if throwing a caught error parameter
|
|
94
|
+
if (node.argument && node.argument.type === 'Identifier') {
|
|
95
|
+
// Look for catch clauses in parent scopes
|
|
96
|
+
let parent = node.parent;
|
|
97
|
+
while (parent) {
|
|
98
|
+
if (parent.type === 'CatchClause' &&
|
|
99
|
+
parent.param &&
|
|
100
|
+
parent.param.name === node.argument.name) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
parent = parent.parent;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getErrorClassName(node) {
|
|
111
|
+
if (!node.argument) return null;
|
|
112
|
+
|
|
113
|
+
if (node.argument.type === 'NewExpression' && node.argument.callee) {
|
|
114
|
+
if (node.argument.callee.type === 'Identifier') {
|
|
115
|
+
return node.argument.callee.name;
|
|
116
|
+
}
|
|
117
|
+
if (node.argument.callee.type === 'MemberExpression' &&
|
|
118
|
+
node.argument.callee.property &&
|
|
119
|
+
node.argument.callee.property.type === 'Identifier') {
|
|
120
|
+
return node.argument.callee.property.name;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isCustomErrorClass(className) {
|
|
128
|
+
if (!className) return false;
|
|
129
|
+
|
|
130
|
+
// Check if it's a known custom error class
|
|
131
|
+
if (customErrorClasses.has(className)) return true;
|
|
132
|
+
|
|
133
|
+
// Check if it follows custom error naming patterns
|
|
134
|
+
const customErrorPatterns = [
|
|
135
|
+
/Error$/, // Ends with 'Error'
|
|
136
|
+
/Exception$/, // Ends with 'Exception'
|
|
137
|
+
/Failure$/, // Ends with 'Failure'
|
|
138
|
+
/Fault$/ // Ends with 'Fault'
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
return customErrorPatterns.some(pattern =>
|
|
142
|
+
pattern.test(className) &&
|
|
143
|
+
!allowedBuiltinErrors.has(className) &&
|
|
144
|
+
className !== 'Error'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function checkErrorClassDefinition(node) {
|
|
149
|
+
// Check if custom error class has required properties
|
|
150
|
+
if (node.type === 'ClassDeclaration' &&
|
|
151
|
+
node.id &&
|
|
152
|
+
isCustomErrorClass(node.id.name)) {
|
|
153
|
+
|
|
154
|
+
const className = node.id.name;
|
|
155
|
+
const classBody = node.body.body;
|
|
156
|
+
|
|
157
|
+
if (requireErrorCode) {
|
|
158
|
+
const hasErrorCode = classBody.some(member => {
|
|
159
|
+
if (member.type === 'PropertyDefinition' || member.type === 'ClassProperty') {
|
|
160
|
+
return member.key && member.key.name === 'code';
|
|
161
|
+
}
|
|
162
|
+
if (member.type === 'MethodDefinition' && member.kind === 'constructor') {
|
|
163
|
+
// Check if constructor sets error code
|
|
164
|
+
const constructorBody = member.value.body.body;
|
|
165
|
+
return constructorBody.some(stmt => {
|
|
166
|
+
if (stmt.type === 'ExpressionStatement' &&
|
|
167
|
+
stmt.expression.type === 'AssignmentExpression' &&
|
|
168
|
+
stmt.expression.left.type === 'MemberExpression' &&
|
|
169
|
+
stmt.expression.left.property.name === 'code') {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (!hasErrorCode) {
|
|
179
|
+
context.report({
|
|
180
|
+
node: node.id,
|
|
181
|
+
messageId: "missingErrorCode"
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (requireStatusCode && /http|api|web|service/i.test(className.toLowerCase())) {
|
|
187
|
+
const hasStatusCode = classBody.some(member => {
|
|
188
|
+
if (member.type === 'PropertyDefinition' || member.type === 'ClassProperty') {
|
|
189
|
+
return member.key && (member.key.name === 'statusCode' || member.key.name === 'status');
|
|
190
|
+
}
|
|
191
|
+
if (member.type === 'MethodDefinition' && member.kind === 'constructor') {
|
|
192
|
+
// Check if constructor sets status code
|
|
193
|
+
const constructorBody = member.value.body.body;
|
|
194
|
+
return constructorBody.some(stmt => {
|
|
195
|
+
if (stmt.type === 'ExpressionStatement' &&
|
|
196
|
+
stmt.expression.type === 'AssignmentExpression' &&
|
|
197
|
+
stmt.expression.left.type === 'MemberExpression' &&
|
|
198
|
+
(stmt.expression.left.property.name === 'statusCode' ||
|
|
199
|
+
stmt.expression.left.property.name === 'status')) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (!hasStatusCode) {
|
|
209
|
+
context.report({
|
|
210
|
+
node: node.id,
|
|
211
|
+
messageId: "missingStatusCode"
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
ThrowStatement(node) {
|
|
220
|
+
// Skip test files if allowed
|
|
221
|
+
if (isTestFile()) return;
|
|
222
|
+
|
|
223
|
+
// Skip rethrow statements if allowed
|
|
224
|
+
if (isRethrowStatement(node)) return;
|
|
225
|
+
|
|
226
|
+
const errorClassName = getErrorClassName(node);
|
|
227
|
+
|
|
228
|
+
// Check for generic Error usage
|
|
229
|
+
if (errorClassName === 'Error') {
|
|
230
|
+
context.report({
|
|
231
|
+
node: node.argument,
|
|
232
|
+
messageId: "useCustomError"
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// In strict mode, only custom errors are allowed
|
|
238
|
+
if (strictMode && errorClassName) {
|
|
239
|
+
if (!isCustomErrorClass(errorClassName) && !allowedBuiltinErrors.has(errorClassName)) {
|
|
240
|
+
context.report({
|
|
241
|
+
node: node.argument,
|
|
242
|
+
messageId: "preferCustomError"
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Check for other built-in errors that could be more specific
|
|
248
|
+
if (allowedBuiltinErrors.has(errorClassName) && !strictMode) {
|
|
249
|
+
// Only suggest if it's a generic built-in error
|
|
250
|
+
if (['TypeError', 'RangeError'].includes(errorClassName)) {
|
|
251
|
+
context.report({
|
|
252
|
+
node: node.argument,
|
|
253
|
+
messageId: "useSpecificBuiltin"
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
ClassDeclaration(node) {
|
|
260
|
+
checkErrorClassDefinition(node);
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
// Check for Promise.reject with generic Error
|
|
264
|
+
'CallExpression[callee.type="MemberExpression"][callee.object.name="Promise"][callee.property.name="reject"]'(node) {
|
|
265
|
+
if (isTestFile()) return;
|
|
266
|
+
|
|
267
|
+
const arg = node.arguments[0];
|
|
268
|
+
if (arg && arg.type === 'NewExpression' &&
|
|
269
|
+
arg.callee && arg.callee.name === 'Error') {
|
|
270
|
+
context.report({
|
|
271
|
+
node: arg,
|
|
272
|
+
messageId: "useCustomError"
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
// Check for async function error throwing
|
|
278
|
+
'AwaitExpression > CallExpression[callee.type="MemberExpression"][callee.property.name="reject"]'(node) {
|
|
279
|
+
if (isTestFile()) return;
|
|
280
|
+
|
|
281
|
+
const arg = node.arguments[0];
|
|
282
|
+
if (arg && arg.type === 'NewExpression' &&
|
|
283
|
+
arg.callee && arg.callee.name === 'Error') {
|
|
284
|
+
context.report({
|
|
285
|
+
node: arg,
|
|
286
|
+
messageId: "useCustomError"
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
module.exports = c030Rule;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C035 – No empty catch blocks (without error handling or logging)
|
|
3
|
+
* Rule ID: custom/c035
|
|
4
|
+
* Purpose: Prevent silently swallowing errors in catch blocks without logging or handling them
|
|
5
|
+
* Note: Primarily intended for backend code. Frontend may handle errors through UI.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
meta: {
|
|
10
|
+
type: "problem",
|
|
11
|
+
docs: {
|
|
12
|
+
description: "No empty catch blocks (without error handling or logging)",
|
|
13
|
+
recommended: true
|
|
14
|
+
},
|
|
15
|
+
schema: [
|
|
16
|
+
{
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
allowFrontend: {
|
|
20
|
+
type: "boolean",
|
|
21
|
+
default: true
|
|
22
|
+
},
|
|
23
|
+
allowIgnoredParams: {
|
|
24
|
+
type: "boolean",
|
|
25
|
+
default: true
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
additionalProperties: false
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
messages: {
|
|
32
|
+
emptyCatch: "Empty catch blocks are not allowed. Errors should be logged or handled explicitly.",
|
|
33
|
+
emptyCatchBackend: "Backend code must log errors in catch blocks. Consider using console.error(), logger, or proper error handling."
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
create(context) {
|
|
37
|
+
const options = context.options[0] || {};
|
|
38
|
+
const allowFrontend = options.allowFrontend !== false;
|
|
39
|
+
const allowIgnoredParams = options.allowIgnoredParams !== false;
|
|
40
|
+
|
|
41
|
+
function isBackendFile(filename) {
|
|
42
|
+
// Heuristics to determine if it's a backend file
|
|
43
|
+
const backendPatterns = [
|
|
44
|
+
/server/i, /backend/i, /api/i, /service/i,
|
|
45
|
+
/controller/i, /model/i, /dao/i, /repository/i,
|
|
46
|
+
/middleware/i, /route/i, /endpoint/i
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
return backendPatterns.some(pattern => pattern.test(filename));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isFrontendFile(filename) {
|
|
53
|
+
// Heuristics to determine if it's a frontend file
|
|
54
|
+
const frontendPatterns = [
|
|
55
|
+
/component/i, /page/i, /screen/i, /view/i,
|
|
56
|
+
/ui/i, /frontend/i, /client/i, /app/i,
|
|
57
|
+
/hook/i, /context/i, /store/i, /reducer/i
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
return frontendPatterns.some(pattern => pattern.test(filename));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isIntentionallyIgnored(param) {
|
|
64
|
+
if (!param || !param.name) return false;
|
|
65
|
+
|
|
66
|
+
const ignoredPatterns = [
|
|
67
|
+
'ignored', '_', '__', 'unused', 'ignore',
|
|
68
|
+
'_error', '_err', '_e', 'ignored_error'
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const paramName = param.name.toLowerCase();
|
|
72
|
+
return ignoredPatterns.some(pattern =>
|
|
73
|
+
paramName === pattern || paramName.includes('ignored')
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function hasFrontendErrorHandling(body) {
|
|
78
|
+
// Check for common frontend error handling patterns
|
|
79
|
+
const bodyText = context.getSourceCode().getText(body);
|
|
80
|
+
const frontendPatterns = [
|
|
81
|
+
/show.*error/i, /display.*error/i, /toast/i, /alert/i,
|
|
82
|
+
/notification/i, /modal/i, /snackbar/i, /message/i,
|
|
83
|
+
/set.*error/i, /error.*state/i, /ui.*error/i
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
return frontendPatterns.some(pattern => pattern.test(bodyText));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
CatchClause(node) {
|
|
91
|
+
const filename = context.getFilename();
|
|
92
|
+
const body = node.body && node.body.body;
|
|
93
|
+
const param = node.param;
|
|
94
|
+
|
|
95
|
+
// Allow intentionally ignored parameters
|
|
96
|
+
if (allowIgnoredParams && isIntentionallyIgnored(param)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check if catch block is empty
|
|
101
|
+
if (!Array.isArray(body) || body.length === 0) {
|
|
102
|
+
const isBackend = isBackendFile(filename);
|
|
103
|
+
const isFrontend = isFrontendFile(filename);
|
|
104
|
+
|
|
105
|
+
// For backend files, always report
|
|
106
|
+
if (isBackend) {
|
|
107
|
+
context.report({
|
|
108
|
+
node,
|
|
109
|
+
messageId: "emptyCatchBackend"
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// For frontend files, be more lenient if option is set
|
|
115
|
+
if (isFrontend && allowFrontend) {
|
|
116
|
+
return; // Allow empty catch in frontend
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Default behavior for unclassified files
|
|
120
|
+
context.report({
|
|
121
|
+
node,
|
|
122
|
+
messageId: "emptyCatch"
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check for proper error handling in non-empty blocks
|
|
128
|
+
const hasLogging = body.some(statement => {
|
|
129
|
+
const text = context.getSourceCode().getText(statement);
|
|
130
|
+
return /console\.(error|warn|log)|logger\.|log\(|throw\s/i.test(text);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const isBackend = isBackendFile(filename);
|
|
134
|
+
const isFrontend = isFrontendFile(filename);
|
|
135
|
+
|
|
136
|
+
// Backend should have logging or re-throwing
|
|
137
|
+
if (isBackend && !hasLogging) {
|
|
138
|
+
// Check if there's at least some error handling
|
|
139
|
+
const hasAnyHandling = body.some(statement => {
|
|
140
|
+
const text = context.getSourceCode().getText(statement);
|
|
141
|
+
return /error|err|exception|fail/i.test(text);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!hasAnyHandling) {
|
|
145
|
+
context.report({
|
|
146
|
+
node,
|
|
147
|
+
messageId: "emptyCatchBackend"
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Frontend can handle through UI
|
|
153
|
+
if (isFrontend && allowFrontend) {
|
|
154
|
+
const hasFrontendHandling = hasFrontendErrorHandling(node.body);
|
|
155
|
+
if (hasLogging || hasFrontendHandling) {
|
|
156
|
+
return; // OK for frontend
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: C041 – No Hardcoded Sensitive Information
|
|
3
|
+
* Rule ID: custom/c041
|
|
4
|
+
* Purpose: Detect hardcoded sensitive information while avoiding false positives in UI/component contexts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "No hardcoded sensitive information",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
inlineConfig: "Potential hardcoded sensitive information detected. Move sensitive values to environment variables or secure config files."
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
function isConfigOrUIContext(line) {
|
|
21
|
+
const lowerLine = line.toLowerCase();
|
|
22
|
+
|
|
23
|
+
// UI/Component contexts - likely false positives
|
|
24
|
+
const uiContexts = [
|
|
25
|
+
'inputtype', 'type:', 'type =', 'inputtype=',
|
|
26
|
+
'routes =', 'route:', 'path:', 'routes:',
|
|
27
|
+
'import {', 'export {', 'from ', 'import ',
|
|
28
|
+
'interface', 'type ', 'enum ',
|
|
29
|
+
'props:', 'defaultprops',
|
|
30
|
+
'schema', 'validator',
|
|
31
|
+
'hook', 'use', 'const use', 'import.*use',
|
|
32
|
+
// React/UI specific
|
|
33
|
+
'textinput', 'input ', 'field ', 'form',
|
|
34
|
+
'component', 'page', 'screen', 'modal',
|
|
35
|
+
// Route/navigation specific
|
|
36
|
+
'navigation', 'route', 'path', 'url:', 'route:',
|
|
37
|
+
'setuppassword', 'resetpassword', 'forgotpassword',
|
|
38
|
+
'changepassword', 'confirmpassword'
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
return uiContexts.some(context => lowerLine.includes(context));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isFalsePositive(value, sourceCode) {
|
|
45
|
+
const lowerValue = value.toLowerCase();
|
|
46
|
+
|
|
47
|
+
// Global false positive indicators
|
|
48
|
+
const globalFalsePositives = [
|
|
49
|
+
'test', 'mock', 'example', 'demo', 'sample', 'placeholder', 'dummy', 'fake',
|
|
50
|
+
'xmlns', 'namespace', 'schema', 'w3.org', 'google.com', 'googleapis.com',
|
|
51
|
+
'error', 'message', 'missing', 'invalid', 'failed', 'localhost', '127.0.0.1'
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// Check global false positives
|
|
55
|
+
if (globalFalsePositives.some(pattern => lowerValue.includes(pattern))) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if line context suggests UI/component usage
|
|
60
|
+
if (isConfigOrUIContext(sourceCode)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const sensitivePatterns = [
|
|
68
|
+
{ pattern: /password/i, minLength: 4 },
|
|
69
|
+
{ pattern: /secret/i, minLength: 6 },
|
|
70
|
+
{ pattern: /api[_-]?key/i, minLength: 10 },
|
|
71
|
+
{ pattern: /auth[_-]?token/i, minLength: 16 },
|
|
72
|
+
{ pattern: /access[_-]?token/i, minLength: 16 },
|
|
73
|
+
{ pattern: /(mongodb|mysql|postgres|redis):\/\//i, minLength: 10 }
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
function reportIfSensitive(node) {
|
|
77
|
+
const sourceCode = context.getSourceCode();
|
|
78
|
+
const lineText = sourceCode.lines[node.loc.start.line - 1];
|
|
79
|
+
|
|
80
|
+
if (typeof node.value !== "string" || node.value.length < 4) return;
|
|
81
|
+
|
|
82
|
+
// Skip if it's in a UI/component context
|
|
83
|
+
if (isFalsePositive(node.value, lineText)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check against sensitive patterns - both variable name and value
|
|
88
|
+
const lowerLine = lineText.toLowerCase();
|
|
89
|
+
const lowerValue = node.value.toLowerCase();
|
|
90
|
+
|
|
91
|
+
for (const { pattern, minLength } of sensitivePatterns) {
|
|
92
|
+
// Check if pattern matches variable name OR value
|
|
93
|
+
const matchesValue = pattern.test(node.value) && node.value.length >= minLength;
|
|
94
|
+
const matchesLine = pattern.test(lineText) && node.value.length >= minLength;
|
|
95
|
+
|
|
96
|
+
if (matchesValue || matchesLine) {
|
|
97
|
+
context.report({
|
|
98
|
+
node,
|
|
99
|
+
messageId: "inlineConfig"
|
|
100
|
+
});
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
Literal(node) {
|
|
108
|
+
reportIfSensitive(node);
|
|
109
|
+
},
|
|
110
|
+
TemplateLiteral(node) {
|
|
111
|
+
if (node.quasis.length === 1) {
|
|
112
|
+
// Create a mock node for template literal value
|
|
113
|
+
const mockNode = {
|
|
114
|
+
...node,
|
|
115
|
+
value: node.quasis[0].value.raw
|
|
116
|
+
};
|
|
117
|
+
reportIfSensitive(mockNode);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
};
|