@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,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule: S003 – Prevent unvalidated redirects
|
|
3
|
+
* Rule ID: custom/s003
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
meta: {
|
|
10
|
+
type: "problem",
|
|
11
|
+
docs: {
|
|
12
|
+
description: "Prevent unvalidated redirects to user-controlled URLs",
|
|
13
|
+
recommended: true,
|
|
14
|
+
},
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
unvalidatedRedirect:
|
|
18
|
+
"Redirect to user-controlled URL '{{target}}' without validation. Use allow list or warning page.",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
create(context) {
|
|
23
|
+
const userInputSources = [
|
|
24
|
+
"req.query",
|
|
25
|
+
"req.body",
|
|
26
|
+
"input",
|
|
27
|
+
"form",
|
|
28
|
+
"params",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function isUserControlled(nodeText) {
|
|
32
|
+
return userInputSources.some((source) => nodeText.includes(source));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function reportIfUserControlled(node, nodeText) {
|
|
36
|
+
if (isUserControlled(nodeText)) {
|
|
37
|
+
context.report({
|
|
38
|
+
node,
|
|
39
|
+
messageId: "unvalidatedRedirect",
|
|
40
|
+
data: { target: nodeText },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
CallExpression(node) {
|
|
47
|
+
const callee = node.callee;
|
|
48
|
+
|
|
49
|
+
// res.redirect(...)
|
|
50
|
+
if (
|
|
51
|
+
callee.type === "MemberExpression" &&
|
|
52
|
+
callee.property.name === "redirect" &&
|
|
53
|
+
node.arguments.length > 0
|
|
54
|
+
) {
|
|
55
|
+
const argText = context.getSourceCode().getText(node.arguments[0]);
|
|
56
|
+
reportIfUserControlled(node, argText);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
AssignmentExpression(node) {
|
|
61
|
+
const left = node.left;
|
|
62
|
+
const right = node.right;
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
left.type === "MemberExpression" &&
|
|
66
|
+
left.object.name === "window" &&
|
|
67
|
+
["location", "location.href"].includes(left.property.name || "") &&
|
|
68
|
+
right
|
|
69
|
+
) {
|
|
70
|
+
const text = context.getSourceCode().getText(right);
|
|
71
|
+
reportIfUserControlled(node, text);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
left.type === "MemberExpression" &&
|
|
76
|
+
left.object.name === "location" &&
|
|
77
|
+
["href", "replace"].includes(left.property.name || "") &&
|
|
78
|
+
right
|
|
79
|
+
) {
|
|
80
|
+
const text = context.getSourceCode().getText(right);
|
|
81
|
+
reportIfUserControlled(node, text);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule: S007 – Plaintext OTP Check
|
|
3
|
+
* Rule ID: custom/s007
|
|
4
|
+
* Description: Verify that OTPs are not stored or transmitted in plaintext form.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'problem',
|
|
12
|
+
docs: {
|
|
13
|
+
description: 'Verify that OTPs are not stored or transmitted in plaintext form.',
|
|
14
|
+
recommended: true,
|
|
15
|
+
url: 'https://owasp.org/www-community/vulnerabilities/Insecure_Storage',
|
|
16
|
+
},
|
|
17
|
+
messages: {
|
|
18
|
+
plaintextOtp:
|
|
19
|
+
'OTP should not be stored or transmitted in plaintext. Consider hashing or encrypting it.',
|
|
20
|
+
},
|
|
21
|
+
schema: [],
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
create(context) {
|
|
25
|
+
return {
|
|
26
|
+
Property(node) {
|
|
27
|
+
if (
|
|
28
|
+
node.key &&
|
|
29
|
+
node.key.type === 'Identifier' &&
|
|
30
|
+
/otp/i.test(node.key.name) &&
|
|
31
|
+
node.value &&
|
|
32
|
+
node.value.type === 'Identifier' &&
|
|
33
|
+
!/hash|encrypt|bcrypt|sha/i.test(node.value.name)
|
|
34
|
+
) {
|
|
35
|
+
context.report({
|
|
36
|
+
node,
|
|
37
|
+
messageId: 'plaintextOtp',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
CallExpression(node) {
|
|
43
|
+
const calleeName = getCalleeName(node.callee);
|
|
44
|
+
if (!calleeName) return;
|
|
45
|
+
|
|
46
|
+
if (/(insert|update|save|query|create)/i.test(calleeName)) {
|
|
47
|
+
for (const arg of node.arguments) {
|
|
48
|
+
const text = context.getSourceCode().getText(arg);
|
|
49
|
+
if (/otp/i.test(text) && !/hash|encrypt|bcrypt|sha/i.test(text)) {
|
|
50
|
+
context.report({
|
|
51
|
+
node: arg,
|
|
52
|
+
messageId: 'plaintextOtp',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ===== Helper function =====
|
|
63
|
+
|
|
64
|
+
function getCalleeName(callee) {
|
|
65
|
+
if (callee.type === 'Identifier') {
|
|
66
|
+
return callee.name;
|
|
67
|
+
} else if (
|
|
68
|
+
callee.type === 'MemberExpression' &&
|
|
69
|
+
callee.property.type === 'Identifier'
|
|
70
|
+
) {
|
|
71
|
+
return callee.property.name;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule: S013 – Enforce TLS Usage
|
|
3
|
+
* Rule ID: custom-tls/s013
|
|
4
|
+
*/
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
const HTTP_REGEX = /^http:\/\//i;
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
meta: {
|
|
11
|
+
type: "problem",
|
|
12
|
+
docs: {
|
|
13
|
+
description:
|
|
14
|
+
"Ensure all client connections use TLS (HTTPS) and prevent unencrypted (HTTP) connections.",
|
|
15
|
+
recommended: true,
|
|
16
|
+
},
|
|
17
|
+
messages: {
|
|
18
|
+
insecureUrl:
|
|
19
|
+
"Unencrypted connection detected (http://). Always use HTTPS (TLS).",
|
|
20
|
+
},
|
|
21
|
+
schema: [],
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
create(context) {
|
|
25
|
+
/** Báo lỗi nếu node là string chứa http:// */
|
|
26
|
+
function reportIfHttp(node, text) {
|
|
27
|
+
if (HTTP_REGEX.test(text)) {
|
|
28
|
+
context.report({ node, messageId: "insecureUrl" });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
// String literal
|
|
34
|
+
Literal(node) {
|
|
35
|
+
if (typeof node.value === "string") reportIfHttp(node, node.value);
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// Template “thuần” (không có ${...})
|
|
39
|
+
TemplateLiteral(node) {
|
|
40
|
+
if (node.expressions.length === 0) {
|
|
41
|
+
const raw = node.quasis.map(q => q.value.raw).join("");
|
|
42
|
+
reportIfHttp(node, raw);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* S047 – Verify system generated initial passwords or activation codes SHOULD be securely randomly generated
|
|
5
|
+
* OWASP ASVS 2.3.1
|
|
6
|
+
* Rule ID: custom/s047
|
|
7
|
+
* SHOULD be at least 6 characters long, and MAY contain letters and numbers, and expire after a short period of time. These initial secrets must not be permitted to become the long term password.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
docs: {
|
|
15
|
+
description:
|
|
16
|
+
"Initial passwords or activation codes must be securely generated, at least 6 characters, expire soon, and not reused long-term.",
|
|
17
|
+
recommended: false,
|
|
18
|
+
},
|
|
19
|
+
messages: {
|
|
20
|
+
weakInitialSecret:
|
|
21
|
+
"Initial secret (password or activation code) must be securely randomly generated, at least 6 characters, expire soon, and not reused long-term.",
|
|
22
|
+
},
|
|
23
|
+
schema: [],
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
create(context) {
|
|
27
|
+
const SECRET_NAME_REGEX = /(initial|activation|temp).*?(password|code|token|secret)/i;
|
|
28
|
+
|
|
29
|
+
function isPotentialSecretName(name) {
|
|
30
|
+
return SECRET_NAME_REGEX.test(name);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function checkInsecureGeneration(node) {
|
|
34
|
+
const callee = node.callee;
|
|
35
|
+
|
|
36
|
+
// Check for Math.random()
|
|
37
|
+
if (
|
|
38
|
+
callee.type === "MemberExpression" &&
|
|
39
|
+
callee.object.type === "Identifier" &&
|
|
40
|
+
callee.object.name === "Math" &&
|
|
41
|
+
callee.property.type === "Identifier" &&
|
|
42
|
+
callee.property.name === "random"
|
|
43
|
+
) {
|
|
44
|
+
context.report({ node, messageId: "weakInitialSecret" });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check for Date.now()
|
|
48
|
+
if (
|
|
49
|
+
callee.type === "MemberExpression" &&
|
|
50
|
+
callee.object.type === "Identifier" &&
|
|
51
|
+
callee.object.name === "Date" &&
|
|
52
|
+
callee.property.type === "Identifier" &&
|
|
53
|
+
callee.property.name === "now"
|
|
54
|
+
) {
|
|
55
|
+
context.report({ node, messageId: "weakInitialSecret" });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
VariableDeclarator(node) {
|
|
61
|
+
if (
|
|
62
|
+
node.id.type === "Identifier" &&
|
|
63
|
+
isPotentialSecretName(node.id.name) &&
|
|
64
|
+
node.init
|
|
65
|
+
) {
|
|
66
|
+
if (node.init.type === "Literal") {
|
|
67
|
+
const val = node.init.value;
|
|
68
|
+
if (typeof val === "string" && val.length < 6) {
|
|
69
|
+
context.report({ node, messageId: "weakInitialSecret" });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
node.init.type === "CallExpression" ||
|
|
75
|
+
node.init.type === "NewExpression"
|
|
76
|
+
) {
|
|
77
|
+
checkInsecureGeneration(node.init);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
AssignmentExpression(node) {
|
|
83
|
+
const left = node.left;
|
|
84
|
+
const right = node.right;
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
left.type === "MemberExpression" &&
|
|
88
|
+
left.property.type === "Identifier" &&
|
|
89
|
+
isPotentialSecretName(left.property.name)
|
|
90
|
+
) {
|
|
91
|
+
if (right.type === "Literal") {
|
|
92
|
+
const val = right.value;
|
|
93
|
+
if (typeof val === "string" && val.length < 6) {
|
|
94
|
+
context.report({ node, messageId: "weakInitialSecret" });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (
|
|
99
|
+
right.type === "CallExpression" ||
|
|
100
|
+
right.type === "NewExpression"
|
|
101
|
+
) {
|
|
102
|
+
checkInsecureGeneration(right);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* S055 – Verify that REST services explicitly check the incoming Content-Type to be the expected one, such as application/xml or application/json.
|
|
5
|
+
* OWASP ASVS 13.2.5
|
|
6
|
+
* Rule ID: custom/s055
|
|
7
|
+
* Verify that REST services explicitly check the incoming Content-Type to be the expected one, such as application/xml or application/json.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
meta: {
|
|
12
|
+
type: "problem",
|
|
13
|
+
docs: {
|
|
14
|
+
description:
|
|
15
|
+
"Ensure REST request handlers validate Content-Type when using req.body",
|
|
16
|
+
recommended: false,
|
|
17
|
+
},
|
|
18
|
+
messages: {
|
|
19
|
+
missingCheck:
|
|
20
|
+
"Handler uses req.body but does not validate Content-Type.",
|
|
21
|
+
},
|
|
22
|
+
schema: [],
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
create(context) {
|
|
26
|
+
function walkForReqBody(node, reqName = "req") {
|
|
27
|
+
const visited = new Set();
|
|
28
|
+
let found = false;
|
|
29
|
+
|
|
30
|
+
function walk(n) {
|
|
31
|
+
if (!n || typeof n !== "object" || visited.has(n) || found) return;
|
|
32
|
+
visited.add(n);
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
n.type === "MemberExpression" &&
|
|
36
|
+
n.object.type === "Identifier" &&
|
|
37
|
+
n.object.name === reqName &&
|
|
38
|
+
n.property.type === "Identifier" &&
|
|
39
|
+
n.property.name === "body"
|
|
40
|
+
) {
|
|
41
|
+
found = true;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const key in n) {
|
|
46
|
+
if (!Object.prototype.hasOwnProperty.call(n, key)) continue;
|
|
47
|
+
const child = n[key];
|
|
48
|
+
if (Array.isArray(child)) child.forEach(walk);
|
|
49
|
+
else if (typeof child === "object" && child !== null) walk(child);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
walk(node);
|
|
54
|
+
return found;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function walkForContentTypeCheck(node, reqName = "req") {
|
|
58
|
+
const visited = new Set();
|
|
59
|
+
|
|
60
|
+
function walk(n) {
|
|
61
|
+
if (!n || typeof n !== "object" || visited.has(n)) return false;
|
|
62
|
+
visited.add(n);
|
|
63
|
+
|
|
64
|
+
// req.is("application/json")
|
|
65
|
+
if (
|
|
66
|
+
n.type === "CallExpression" &&
|
|
67
|
+
n.callee.type === "MemberExpression" &&
|
|
68
|
+
n.callee.object.type === "Identifier" &&
|
|
69
|
+
n.callee.object.name === reqName &&
|
|
70
|
+
n.callee.property.name === "is" &&
|
|
71
|
+
n.arguments.length > 0 &&
|
|
72
|
+
n.arguments[0].type === "Literal" &&
|
|
73
|
+
typeof n.arguments[0].value === "string" &&
|
|
74
|
+
n.arguments[0].value.startsWith("application/")
|
|
75
|
+
) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// req.headers["content-type"]
|
|
80
|
+
if (
|
|
81
|
+
n.type === "MemberExpression" &&
|
|
82
|
+
n.object?.type === "MemberExpression" &&
|
|
83
|
+
n.object.object?.type === "Identifier" &&
|
|
84
|
+
n.object.object.name === reqName &&
|
|
85
|
+
n.object.property?.name === "headers" &&
|
|
86
|
+
(
|
|
87
|
+
(n.property.type === "Literal" && n.property.value === "content-type") ||
|
|
88
|
+
(n.property.type === "Identifier" && n.property.name === "content-type")
|
|
89
|
+
)
|
|
90
|
+
) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Look through children
|
|
95
|
+
for (const key in n) {
|
|
96
|
+
if (!Object.prototype.hasOwnProperty.call(n, key)) continue;
|
|
97
|
+
const child = n[key];
|
|
98
|
+
if (Array.isArray(child)) {
|
|
99
|
+
if (child.some(walk)) return true;
|
|
100
|
+
} else if (typeof child === "object" && child !== null) {
|
|
101
|
+
if (walk(child)) return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return walk(node);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
CallExpression(node) {
|
|
113
|
+
// Match app.post("/path", handler)
|
|
114
|
+
if (
|
|
115
|
+
node.callee.type === "MemberExpression" &&
|
|
116
|
+
["post", "put", "patch", "delete"].includes(
|
|
117
|
+
node.callee.property.name
|
|
118
|
+
)
|
|
119
|
+
) {
|
|
120
|
+
const handler = node.arguments.find(
|
|
121
|
+
(arg) =>
|
|
122
|
+
arg.type === "FunctionExpression" ||
|
|
123
|
+
arg.type === "ArrowFunctionExpression"
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (!handler || !handler.body) return;
|
|
127
|
+
|
|
128
|
+
const reqName = handler.params[0]?.name || "req";
|
|
129
|
+
|
|
130
|
+
const usesBody = walkForReqBody(handler.body, reqName);
|
|
131
|
+
const hasValidation = walkForContentTypeCheck(handler.body, reqName);
|
|
132
|
+
|
|
133
|
+
if (usesBody && !hasValidation) {
|
|
134
|
+
context.report({
|
|
135
|
+
node: handler,
|
|
136
|
+
messageId: "missingCheck",
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: T002 – Interface names should start with 'I'
|
|
3
|
+
* Rule ID: custom/t002
|
|
4
|
+
* Purpose: Enforce consistent interface naming convention by requiring the 'I' prefix
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
meta: {
|
|
11
|
+
type: "suggestion",
|
|
12
|
+
docs: {
|
|
13
|
+
description: "Interface names should start with 'I'",
|
|
14
|
+
recommended: false
|
|
15
|
+
},
|
|
16
|
+
fixable: "code",
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
interfacePrefix: "Interface name '{{name}}' should start with 'I'"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
create(context) {
|
|
24
|
+
return {
|
|
25
|
+
TSInterfaceDeclaration(node) {
|
|
26
|
+
const interfaceName = node.id.name;
|
|
27
|
+
if (!interfaceName.startsWith("I")) {
|
|
28
|
+
context.report({
|
|
29
|
+
node: node.id,
|
|
30
|
+
messageId: "interfacePrefix",
|
|
31
|
+
data: {
|
|
32
|
+
name: interfaceName,
|
|
33
|
+
},
|
|
34
|
+
fix(fixer) {
|
|
35
|
+
return fixer.replaceText(node.id, `I${interfaceName}`);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: T003 – Avoid using @ts-ignore without justification
|
|
3
|
+
* Rule ID: custom/t003
|
|
4
|
+
* Purpose: Require clear justification when using @ts-ignore comments
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
meta: {
|
|
11
|
+
type: "suggestion",
|
|
12
|
+
docs: {
|
|
13
|
+
description: "Avoid using @ts-ignore without a justification",
|
|
14
|
+
recommended: false
|
|
15
|
+
},
|
|
16
|
+
fixable: null,
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
tsIgnoreReason: "Please provide a reason for using @ts-ignore"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
create(context) {
|
|
24
|
+
return {
|
|
25
|
+
Program() {
|
|
26
|
+
const sourceCode = context.getSourceCode();
|
|
27
|
+
const comments = sourceCode.getAllComments();
|
|
28
|
+
|
|
29
|
+
comments.forEach((comment) => {
|
|
30
|
+
if (comment.type === "Line" && comment.value.includes("@ts-ignore")) {
|
|
31
|
+
// Check if there's a justification after @ts-ignore
|
|
32
|
+
const hasJustification = comment.value
|
|
33
|
+
.replace("@ts-ignore", "")
|
|
34
|
+
.trim()
|
|
35
|
+
.length > 0;
|
|
36
|
+
|
|
37
|
+
if (!hasJustification) {
|
|
38
|
+
context.report({
|
|
39
|
+
node: comment,
|
|
40
|
+
messageId: "tsIgnoreReason",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: T019 – Disallow empty type definitions
|
|
3
|
+
* Rule ID: custom/t019
|
|
4
|
+
* Purpose: Prevent empty type definitions and suggest more specific types
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
meta: {
|
|
11
|
+
type: "suggestion",
|
|
12
|
+
docs: {
|
|
13
|
+
description: "Disallow empty type definitions",
|
|
14
|
+
category: "TypeScript",
|
|
15
|
+
recommended: true,
|
|
16
|
+
},
|
|
17
|
+
fixable: "code",
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: {
|
|
20
|
+
emptyType: "Empty type definition is not allowed. Add at least one property or use a more specific type.",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
create(context) {
|
|
25
|
+
// Helper function to get a more specific type suggestion based on the name
|
|
26
|
+
function getSuggestedType(name) {
|
|
27
|
+
const lowerName = name.toLowerCase();
|
|
28
|
+
|
|
29
|
+
// Suggest more specific types based on common naming patterns
|
|
30
|
+
if (lowerName.includes("config") || lowerName.includes("options")) {
|
|
31
|
+
return "Record<string, unknown>";
|
|
32
|
+
}
|
|
33
|
+
if (lowerName.includes("props")) {
|
|
34
|
+
return "Record<string, React.ReactNode>";
|
|
35
|
+
}
|
|
36
|
+
if (lowerName.includes("state")) {
|
|
37
|
+
return "Record<string, unknown>";
|
|
38
|
+
}
|
|
39
|
+
if (lowerName.includes("params")) {
|
|
40
|
+
return "Record<string, string | number | boolean>";
|
|
41
|
+
}
|
|
42
|
+
if (lowerName.includes("result") || lowerName.includes("response")) {
|
|
43
|
+
return "Record<string, unknown>";
|
|
44
|
+
}
|
|
45
|
+
if (lowerName.includes("event") || lowerName.includes("handler")) {
|
|
46
|
+
return "(...args: unknown[]) => void";
|
|
47
|
+
}
|
|
48
|
+
if (lowerName.includes("callback") || lowerName.includes("fn")) {
|
|
49
|
+
return "(...args: unknown[]) => unknown";
|
|
50
|
+
}
|
|
51
|
+
if (lowerName.includes("data") || lowerName.includes("item")) {
|
|
52
|
+
return "Record<string, unknown>";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Default suggestion
|
|
56
|
+
return "Record<string, unknown>";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
TSInterfaceDeclaration(node) {
|
|
61
|
+
if (node.body.body.length === 0) {
|
|
62
|
+
const suggestedType = getSuggestedType(node.id.name);
|
|
63
|
+
context.report({
|
|
64
|
+
node: node.body,
|
|
65
|
+
messageId: "emptyType",
|
|
66
|
+
fix(fixer) {
|
|
67
|
+
return fixer.replaceText(
|
|
68
|
+
node,
|
|
69
|
+
`type ${node.id.name} = ${suggestedType};`
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
TSTypeAliasDeclaration(node) {
|
|
76
|
+
if (
|
|
77
|
+
node.typeAnnotation.type === "TSTypeLiteral" &&
|
|
78
|
+
node.typeAnnotation.members.length === 0
|
|
79
|
+
) {
|
|
80
|
+
const suggestedType = getSuggestedType(node.id.name);
|
|
81
|
+
context.report({
|
|
82
|
+
node: node.typeAnnotation,
|
|
83
|
+
messageId: "emptyType",
|
|
84
|
+
fix(fixer) {
|
|
85
|
+
return fixer.replaceText(
|
|
86
|
+
node.typeAnnotation,
|
|
87
|
+
suggestedType
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule for: T007 – Avoid declaring functions inside constructors or class bodies
|
|
3
|
+
* Rule ID: custom/t007
|
|
4
|
+
* Purpose: Discourage function declarations within constructors or class methods
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Avoid declaring functions inside constructors or class bodies",
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
schema: [],
|
|
15
|
+
messages: {
|
|
16
|
+
noFunctionInConstructor: "Avoid declaring functions inside class constructors.",
|
|
17
|
+
noFunctionInClassBody: "Avoid declaring nested functions inside class body."
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
return {
|
|
22
|
+
MethodDefinition(node) {
|
|
23
|
+
if (node.kind === "constructor" && node.value && node.value.body && node.value.body.body) {
|
|
24
|
+
const constructorBody = node.value.body.body;
|
|
25
|
+
constructorBody.forEach(element => {
|
|
26
|
+
if (element.type === "FunctionDeclaration" || element.type === "FunctionExpression") {
|
|
27
|
+
context.report({
|
|
28
|
+
node: element,
|
|
29
|
+
messageId: "noFunctionInConstructor"
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
ClassBody(node) {
|
|
36
|
+
node.body.forEach(element => {
|
|
37
|
+
if (element.type === "MethodDefinition" && element.value && element.value.body && element.value.body.body) {
|
|
38
|
+
const methodBody = element.value.body.body;
|
|
39
|
+
methodBody.forEach(subNode => {
|
|
40
|
+
if (subNode.type === "FunctionDeclaration") {
|
|
41
|
+
context.report({
|
|
42
|
+
node: subNode,
|
|
43
|
+
messageId: "noFunctionInClassBody"
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
};
|