@blumintinc/eslint-plugin-blumint 1.12.6 → 1.14.0
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/README.md +171 -99
- package/lib/index.js +300 -17
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
- package/lib/rules/consistent-callback-naming.js +1 -1
- package/lib/rules/dynamic-https-errors.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -11
- package/lib/rules/enforce-assert-safe-object-key.d.ts +8 -0
- package/lib/rules/{enforce-assertSafe-object-key.js → enforce-assert-safe-object-key.js} +52 -96
- package/lib/rules/enforce-assert-throws.d.ts +3 -1
- package/lib/rules/enforce-assert-throws.js +493 -51
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +3 -1
- package/lib/rules/enforce-boolean-naming-prefixes.js +820 -39
- package/lib/rules/enforce-callable-types.js +4 -4
- package/lib/rules/enforce-callback-memo.js +143 -18
- package/lib/rules/enforce-centralized-mock-firestore.js +5 -2
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +278 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
- package/lib/rules/enforce-dynamic-file-naming.js +53 -28
- package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-early-destructuring.d.ts +2 -0
- package/lib/rules/enforce-early-destructuring.js +980 -0
- package/lib/rules/enforce-empty-object-check.d.ts +11 -0
- package/lib/rules/enforce-empty-object-check.js +502 -0
- package/lib/rules/enforce-exported-function-types.js +4 -4
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +262 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +350 -3
- package/lib/rules/enforce-firestore-facade.js +349 -157
- package/lib/rules/enforce-firestore-rules-get-access.d.ts +3 -0
- package/lib/rules/enforce-firestore-rules-get-access.js +109 -0
- package/lib/rules/enforce-firestore-set-merge.js +3 -4
- package/lib/rules/enforce-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +92 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +147 -0
- package/lib/rules/enforce-microdiff.js +3 -2
- package/lib/rules/enforce-object-literal-as-const.js +4 -4
- package/lib/rules/enforce-positive-naming.js +9 -7
- package/lib/rules/enforce-props-argument-name.d.ts +2 -7
- package/lib/rules/enforce-props-argument-name.js +165 -151
- package/lib/rules/enforce-props-naming-consistency.d.ts +2 -0
- package/lib/rules/enforce-props-naming-consistency.js +145 -0
- package/lib/rules/enforce-querykey-ts.d.ts +9 -0
- package/lib/rules/enforce-querykey-ts.js +274 -0
- package/lib/rules/enforce-safe-stringify.js +1 -1
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-singular-type-names.js +10 -6
- package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
- package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
- package/lib/rules/enforce-storage-context.d.ts +9 -0
- package/lib/rules/enforce-storage-context.js +646 -0
- package/lib/rules/enforce-timestamp-now.js +23 -7
- package/lib/rules/enforce-transform-memoization.d.ts +4 -0
- package/lib/rules/enforce-transform-memoization.js +416 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +100 -0
- package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
- package/lib/rules/enforce-unique-cursor-headers.js +365 -0
- package/lib/rules/enforce-verb-noun-naming.js +90 -14
- package/lib/rules/ensure-pointer-events-none.js +28 -2
- package/lib/rules/export-if-in-doubt.js +27 -4
- package/lib/rules/extract-global-constants.js +92 -53
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +351 -83
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +277 -0
- package/lib/rules/flatten-push-calls.d.ts +2 -0
- package/lib/rules/flatten-push-calls.js +428 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- package/lib/rules/global-const-style.js +50 -19
- package/lib/rules/jsdoc-above-field.d.ts +11 -0
- package/lib/rules/jsdoc-above-field.js +208 -0
- package/lib/rules/key-only-outermost-element.js +21 -3
- package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
- package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
- package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
- package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
- package/lib/rules/memo-nested-react-components.d.ts +8 -0
- package/lib/rules/memo-nested-react-components.js +410 -0
- package/lib/rules/memoize-root-level-hocs.d.ts +7 -0
- package/lib/rules/memoize-root-level-hocs.js +242 -0
- package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
- package/lib/rules/no-always-true-false-conditions.js +68 -8
- package/lib/rules/no-array-length-in-deps.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +314 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- package/lib/rules/no-async-foreach.js +210 -12
- package/lib/rules/no-circular-references.js +18 -17
- package/lib/rules/no-class-instance-destructuring.js +55 -12
- package/lib/rules/no-complex-cloud-params.js +22 -5
- package/lib/rules/no-compositing-layer-props.js +6 -4
- package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
- package/lib/rules/no-console-error.d.ts +9 -0
- package/lib/rules/no-console-error.js +525 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
- package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
- package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
- package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
- package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +320 -68
- package/lib/rules/no-excessive-parent-chain.d.ts +5 -0
- package/lib/rules/no-excessive-parent-chain.js +273 -0
- package/lib/rules/no-explicit-return-type.d.ts +2 -1
- package/lib/rules/no-explicit-return-type.js +215 -26
- package/lib/rules/no-filter-without-return.js +5 -1
- package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
- package/lib/rules/no-firestore-jest-mock.js +126 -8
- package/lib/rules/no-firestore-object-arrays.js +398 -28
- package/lib/rules/no-handler-suffix.d.ts +12 -0
- package/lib/rules/no-handler-suffix.js +305 -0
- package/lib/rules/no-hungarian.js +14 -14
- package/lib/rules/no-inline-component-prop.d.ts +10 -0
- package/lib/rules/no-inline-component-prop.js +456 -0
- package/lib/rules/no-jsx-in-hooks.js +6 -1
- package/lib/rules/no-jsx-whitespace-literal.js +8 -2
- package/lib/rules/no-margin-properties.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +22 -9
- package/lib/rules/no-memoize-on-static.js +9 -1
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +331 -0
- package/lib/rules/no-misused-switch-case.js +22 -1
- package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
- package/lib/rules/no-mixed-firestore-transactions.js +296 -34
- package/lib/rules/no-mock-firebase-admin.js +5 -2
- package/lib/rules/no-object-values-on-strings.js +10 -2
- package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +272 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +206 -0
- package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
- package/lib/rules/no-redundant-annotation-assertion.js +402 -0
- package/lib/rules/no-redundant-param-types.js +16 -6
- package/lib/rules/no-redundant-this-params.d.ts +3 -0
- package/lib/rules/no-redundant-this-params.js +459 -0
- package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +328 -0
- package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
- package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
- package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +173 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +133 -0
- package/lib/rules/no-stale-state-across-await.d.ts +1 -0
- package/lib/rules/no-stale-state-across-await.js +185 -0
- package/lib/rules/no-static-constants-in-dynamic-files.d.ts +3 -0
- package/lib/rules/no-static-constants-in-dynamic-files.js +149 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.d.ts +1 -0
- package/lib/rules/no-try-catch-already-exists-in-transaction.js +342 -0
- package/lib/rules/no-type-assertion-returns.js +49 -49
- package/lib/rules/no-undefined-null-passthrough.js +217 -40
- package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
- package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
- package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
- package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
- package/lib/rules/no-unnecessary-destructuring.js +14 -4
- package/lib/rules/no-unnecessary-verb-suffix.js +2 -3
- package/lib/rules/no-unpinned-dependencies.js +36 -5
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +5 -2
- package/lib/rules/no-unused-props.js +451 -112
- package/lib/rules/no-unused-usestate.js +6 -2
- package/lib/rules/no-useless-fragment.js +28 -2
- package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
- package/lib/rules/no-useless-usememo-primitives.js +393 -0
- package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
- package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
- package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
- package/lib/rules/omit-index-html.d.ts +2 -1
- package/lib/rules/omit-index-html.js +62 -7
- package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +236 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +394 -0
- package/lib/rules/prefer-batch-operations.d.ts +1 -3
- package/lib/rules/prefer-batch-operations.js +228 -35
- package/lib/rules/prefer-block-comments-for-declarations.js +22 -7
- package/lib/rules/prefer-clone-deep.js +3 -3
- package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
- package/lib/rules/prefer-destructuring-no-class.js +244 -59
- package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
- package/lib/rules/prefer-docsetter-setall.js +243 -0
- package/lib/rules/prefer-document-flattening.d.ts +4 -0
- package/lib/rules/prefer-document-flattening.js +239 -0
- package/lib/rules/prefer-field-paths-in-transforms.d.ts +7 -0
- package/lib/rules/prefer-field-paths-in-transforms.js +251 -0
- package/lib/rules/prefer-fragment-component.js +4 -4
- package/lib/rules/prefer-fragment-shorthand.js +2 -1
- package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
- package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
- package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +305 -31
- package/lib/rules/prefer-memoized-props.d.ts +3 -0
- package/lib/rules/prefer-memoized-props.js +445 -0
- package/lib/rules/prefer-next-dynamic.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +357 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +435 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +7 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +188 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +277 -0
- package/lib/rules/prefer-settings-object.js +23 -15
- package/lib/rules/prefer-type-alias-over-typeof-constant.d.ts +3 -0
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +259 -0
- package/lib/rules/prefer-type-over-interface.js +7 -2
- package/lib/rules/prefer-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +169 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +431 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +25 -2
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
- package/lib/rules/prevent-children-clobber.d.ts +2 -0
- package/lib/rules/prevent-children-clobber.js +536 -0
- package/lib/rules/react-memoize-literals.d.ts +4 -0
- package/lib/rules/react-memoize-literals.js +495 -0
- package/lib/rules/react-usememo-should-be-component.d.ts +2 -1
- package/lib/rules/react-usememo-should-be-component.js +144 -78
- package/lib/rules/require-dynamic-firebase-imports.js +2 -1
- package/lib/rules/require-hooks-default-params.js +17 -16
- package/lib/rules/require-https-error-cause.d.ts +4 -0
- package/lib/rules/require-https-error-cause.js +136 -0
- package/lib/rules/require-https-error.js +43 -21
- package/lib/rules/require-image-optimized.js +2 -2
- package/lib/rules/require-memo.js +12 -2
- package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
- package/lib/rules/require-memoize-jsx-returners.js +485 -0
- package/lib/rules/require-usememo-object-literals.js +9 -4
- package/lib/rules/semantic-function-prefixes.js +38 -18
- package/lib/rules/sync-onwrite-name-func.js +5 -1
- package/lib/rules/test-file-location-enforcement.d.ts +1 -0
- package/lib/rules/test-file-location-enforcement.js +57 -0
- package/lib/rules/use-custom-link.js +6 -1
- package/lib/rules/use-custom-router.js +6 -1
- package/lib/rules/use-latest-callback.d.ts +3 -0
- package/lib/rules/use-latest-callback.js +271 -0
- package/lib/rules/vertically-group-related-functions.d.ts +16 -0
- package/lib/rules/vertically-group-related-functions.js +480 -0
- package/lib/utils/ASTHelpers.d.ts +13 -1
- package/lib/utils/ASTHelpers.js +34 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/getMethodName.d.ts +27 -0
- package/lib/utils/getMethodName.js +35 -0
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/lib/utils/tsTypeClassifier.d.ts +30 -0
- package/lib/utils/tsTypeClassifier.js +149 -0
- package/package.json +23 -14
- package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noHandlerSuffix = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const minimatch_1 = require("minimatch");
|
|
6
|
+
const createRule_1 = require("../utils/createRule");
|
|
7
|
+
const DEFAULT_OPTIONS = {
|
|
8
|
+
ignoreClassMethods: false,
|
|
9
|
+
ignoreInterfaceImplementations: false,
|
|
10
|
+
interfaceAllowlist: [],
|
|
11
|
+
allowNames: [],
|
|
12
|
+
allowPatterns: [],
|
|
13
|
+
allowFilePatterns: [],
|
|
14
|
+
};
|
|
15
|
+
function toEntityName(entity) {
|
|
16
|
+
if (entity.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
17
|
+
return entity.name;
|
|
18
|
+
}
|
|
19
|
+
if (entity.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
|
|
20
|
+
return `${toEntityName(entity.left)}.${entity.right.name}`;
|
|
21
|
+
}
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
function isUnsafeAllowPattern(pattern) {
|
|
25
|
+
// Flag nested quantifiers that commonly lead to catastrophic backtracking
|
|
26
|
+
const nestedQuantifierPattern = /\((?:[^()\\]|\\.)*[+*{][^)]*\)\s*[+*{]/;
|
|
27
|
+
return nestedQuantifierPattern.test(pattern);
|
|
28
|
+
}
|
|
29
|
+
function getStaticKeyName(key) {
|
|
30
|
+
if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
31
|
+
return key.name;
|
|
32
|
+
}
|
|
33
|
+
if (key.type === utils_1.AST_NODE_TYPES.Literal && typeof key.value === 'string') {
|
|
34
|
+
return key.value;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function getHandlerSuffix(name) {
|
|
39
|
+
const match = name.match(/^(.*?)(Handlers?)$/i);
|
|
40
|
+
if (!match)
|
|
41
|
+
return null;
|
|
42
|
+
const [, baseName, suffix] = match;
|
|
43
|
+
if (!suffix)
|
|
44
|
+
return null;
|
|
45
|
+
const normalizedSuffix = suffix.toLowerCase() === 'handlers' ? 'Handlers' : 'Handler';
|
|
46
|
+
return {
|
|
47
|
+
baseName,
|
|
48
|
+
suffix: normalizedSuffix,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function isInAllowedFile(filename, allowFilePatterns) {
|
|
52
|
+
return allowFilePatterns.some((pattern) => (0, minimatch_1.minimatch)(filename, pattern));
|
|
53
|
+
}
|
|
54
|
+
function toMemberExpressionName(expr) {
|
|
55
|
+
if (expr.computed || expr.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const objectName = expr.object.type === utils_1.AST_NODE_TYPES.Identifier
|
|
59
|
+
? expr.object.name
|
|
60
|
+
: expr.object.type === utils_1.AST_NODE_TYPES.MemberExpression
|
|
61
|
+
? toMemberExpressionName(expr.object)
|
|
62
|
+
: null;
|
|
63
|
+
return objectName ? `${objectName}.${expr.property.name}` : null;
|
|
64
|
+
}
|
|
65
|
+
function getImplementedInterfaces(classNode) {
|
|
66
|
+
return (classNode.implements?.flatMap((impl) => {
|
|
67
|
+
const expr = impl.expression;
|
|
68
|
+
const name = expr.type === utils_1.AST_NODE_TYPES.Identifier
|
|
69
|
+
? expr.name
|
|
70
|
+
: expr.type === utils_1.AST_NODE_TYPES.MemberExpression
|
|
71
|
+
? toMemberExpressionName(expr)
|
|
72
|
+
: null;
|
|
73
|
+
return name ? [name] : [];
|
|
74
|
+
}) ?? []);
|
|
75
|
+
}
|
|
76
|
+
function getClassFromMember(node) {
|
|
77
|
+
const classBody = node.parent;
|
|
78
|
+
if (classBody &&
|
|
79
|
+
classBody.type === utils_1.AST_NODE_TYPES.ClassBody &&
|
|
80
|
+
classBody.parent &&
|
|
81
|
+
(classBody.parent.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
|
|
82
|
+
classBody.parent.type === utils_1.AST_NODE_TYPES.ClassExpression)) {
|
|
83
|
+
return classBody.parent;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
function getTypeAnnotationName(annotation) {
|
|
88
|
+
if (!annotation)
|
|
89
|
+
return null;
|
|
90
|
+
if (annotation.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
91
|
+
const typeName = annotation.typeAnnotation.typeName;
|
|
92
|
+
if (typeName.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
93
|
+
typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
|
|
94
|
+
return toEntityName(typeName);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
exports.noHandlerSuffix = (0, createRule_1.createRule)({
|
|
100
|
+
name: 'no-handler-suffix',
|
|
101
|
+
meta: {
|
|
102
|
+
type: 'suggestion',
|
|
103
|
+
docs: {
|
|
104
|
+
description: 'Disallow the generic "handler" suffix in callback names so names explain the action they perform',
|
|
105
|
+
recommended: 'error',
|
|
106
|
+
},
|
|
107
|
+
schema: [
|
|
108
|
+
{
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
ignoreClassMethods: { type: 'boolean' },
|
|
112
|
+
ignoreInterfaceImplementations: { type: 'boolean' },
|
|
113
|
+
interfaceAllowlist: {
|
|
114
|
+
type: 'array',
|
|
115
|
+
items: { type: 'string' },
|
|
116
|
+
},
|
|
117
|
+
allowNames: { type: 'array', items: { type: 'string' } },
|
|
118
|
+
allowPatterns: { type: 'array', items: { type: 'string' } },
|
|
119
|
+
allowFilePatterns: { type: 'array', items: { type: 'string' } },
|
|
120
|
+
},
|
|
121
|
+
additionalProperties: false,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
messages: {
|
|
125
|
+
handlerSuffix: 'Function "{{name}}" ends with the generic {{suffix}} suffix. Handler names hide the outcome of the callback and make call sites indistinguishable. Rename it to describe the effect (e.g., "{{suggestedName}}" or another action phrase) so readers know what the function actually does.',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
defaultOptions: [DEFAULT_OPTIONS],
|
|
129
|
+
create(context, [options]) {
|
|
130
|
+
const filename = context.getFilename();
|
|
131
|
+
const resolvedOptions = { ...DEFAULT_OPTIONS, ...(options ?? {}) };
|
|
132
|
+
const allowNames = new Set(resolvedOptions.allowNames);
|
|
133
|
+
const interfaceAllowlist = new Set(resolvedOptions.interfaceAllowlist);
|
|
134
|
+
const invalidAllowPatterns = [];
|
|
135
|
+
const unsafeAllowPatterns = [];
|
|
136
|
+
const allowPatterns = (resolvedOptions.allowPatterns ?? []).flatMap((pattern) => {
|
|
137
|
+
try {
|
|
138
|
+
if (isUnsafeAllowPattern(pattern)) {
|
|
139
|
+
unsafeAllowPatterns.push(pattern);
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
return [new RegExp(pattern)];
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
const reason = error && typeof error === 'object' && 'message' in error
|
|
146
|
+
? ` (${String(error.message)})`
|
|
147
|
+
: '';
|
|
148
|
+
invalidAllowPatterns.push(`${pattern}${reason}`);
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
if (invalidAllowPatterns.length > 0 || unsafeAllowPatterns.length > 0) {
|
|
153
|
+
const errorParts = [];
|
|
154
|
+
if (invalidAllowPatterns.length > 0) {
|
|
155
|
+
errorParts.push(`invalid allowPatterns: ${invalidAllowPatterns.join(', ')}`);
|
|
156
|
+
}
|
|
157
|
+
if (unsafeAllowPatterns.length > 0) {
|
|
158
|
+
errorParts.push(`unsafe allowPatterns (avoid nested quantifiers that risk catastrophic backtracking): ${unsafeAllowPatterns.join(', ')}`);
|
|
159
|
+
}
|
|
160
|
+
throw new Error(`no-handler-suffix: ${errorParts.join('; ')}`);
|
|
161
|
+
}
|
|
162
|
+
if (isInAllowedFile(filename, resolvedOptions.allowFilePatterns ?? [])) {
|
|
163
|
+
return {};
|
|
164
|
+
}
|
|
165
|
+
function isAllowedName(name) {
|
|
166
|
+
if (allowNames.has(name))
|
|
167
|
+
return true;
|
|
168
|
+
return allowPatterns.some((regex) => regex.test(name));
|
|
169
|
+
}
|
|
170
|
+
function shouldIgnoreForInterfaces(node) {
|
|
171
|
+
const { ignoreInterfaceImplementations } = resolvedOptions;
|
|
172
|
+
if ((node.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
|
|
173
|
+
node.type === utils_1.AST_NODE_TYPES.PropertyDefinition) &&
|
|
174
|
+
!node.computed &&
|
|
175
|
+
(node.key.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
176
|
+
node.key.type === utils_1.AST_NODE_TYPES.PrivateIdentifier ||
|
|
177
|
+
(node.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
178
|
+
typeof node.key.value === 'string'))) {
|
|
179
|
+
const classNode = getClassFromMember(node);
|
|
180
|
+
if (!classNode)
|
|
181
|
+
return false;
|
|
182
|
+
const implemented = getImplementedInterfaces(classNode);
|
|
183
|
+
if (implemented.length === 0)
|
|
184
|
+
return false;
|
|
185
|
+
if (ignoreInterfaceImplementations) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
if (interfaceAllowlist.size > 0) {
|
|
189
|
+
return implemented.some((iface) => interfaceAllowlist.has(iface));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
193
|
+
node.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
194
|
+
const annotationName = getTypeAnnotationName(node.id.typeAnnotation);
|
|
195
|
+
if (annotationName &&
|
|
196
|
+
(interfaceAllowlist.has(annotationName) ||
|
|
197
|
+
resolvedOptions.ignoreInterfaceImplementations)) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
function getParentName(parent) {
|
|
204
|
+
if (!parent)
|
|
205
|
+
return null;
|
|
206
|
+
if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
207
|
+
parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
208
|
+
return parent.id.name;
|
|
209
|
+
}
|
|
210
|
+
if ((parent.type === utils_1.AST_NODE_TYPES.Property ||
|
|
211
|
+
parent.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
|
|
212
|
+
parent.type === utils_1.AST_NODE_TYPES.PropertyDefinition) &&
|
|
213
|
+
!parent.computed &&
|
|
214
|
+
parent.key.type !== utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
215
|
+
return getStaticKeyName(parent.key);
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
function reportIfHandlerName(name, identifierNode, owner) {
|
|
220
|
+
if (!name || !identifierNode)
|
|
221
|
+
return;
|
|
222
|
+
const suffixInfo = getHandlerSuffix(name);
|
|
223
|
+
if (!suffixInfo)
|
|
224
|
+
return;
|
|
225
|
+
if (isAllowedName(name))
|
|
226
|
+
return;
|
|
227
|
+
if ((owner.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
|
|
228
|
+
owner.type === utils_1.AST_NODE_TYPES.PropertyDefinition) &&
|
|
229
|
+
resolvedOptions.ignoreClassMethods) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (owner.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
|
|
233
|
+
owner.type === utils_1.AST_NODE_TYPES.PropertyDefinition ||
|
|
234
|
+
owner.type === utils_1.AST_NODE_TYPES.VariableDeclarator ||
|
|
235
|
+
owner.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
236
|
+
if (shouldIgnoreForInterfaces(owner)) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const suggestedName = suffixInfo.baseName || 'a verb phrase that states the outcome';
|
|
241
|
+
context.report({
|
|
242
|
+
node: identifierNode,
|
|
243
|
+
messageId: 'handlerSuffix',
|
|
244
|
+
data: {
|
|
245
|
+
name,
|
|
246
|
+
suffix: suffixInfo.suffix,
|
|
247
|
+
suggestedName,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
FunctionDeclaration(node) {
|
|
253
|
+
if (node.id?.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
254
|
+
reportIfHandlerName(node.id.name, node.id, node);
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
VariableDeclarator(node) {
|
|
258
|
+
if (node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
259
|
+
(node.init?.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
260
|
+
node.init?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
261
|
+
reportIfHandlerName(node.id.name, node.id, node);
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
MethodDefinition(node) {
|
|
265
|
+
if (!node.computed) {
|
|
266
|
+
const name = getStaticKeyName(node.key);
|
|
267
|
+
if (name) {
|
|
268
|
+
reportIfHandlerName(name, node.key, node);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
PropertyDefinition(node) {
|
|
273
|
+
if (!node.computed &&
|
|
274
|
+
(node.value?.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
275
|
+
node.value?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
276
|
+
const name = getStaticKeyName(node.key);
|
|
277
|
+
if (name) {
|
|
278
|
+
reportIfHandlerName(name, node.key, node);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
Property(node) {
|
|
283
|
+
if (!node.computed &&
|
|
284
|
+
(node.value.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
285
|
+
node.value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
286
|
+
const name = getStaticKeyName(node.key);
|
|
287
|
+
if (name) {
|
|
288
|
+
reportIfHandlerName(name, node.key, node);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
FunctionExpression(node) {
|
|
293
|
+
if (!node.id)
|
|
294
|
+
return;
|
|
295
|
+
// Avoid double-reporting when the parent already names the function
|
|
296
|
+
const parentName = getParentName(node.parent);
|
|
297
|
+
if (parentName && parentName === node.id.name) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
reportIfHandlerName(node.id.name, node.id, node);
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
//# sourceMappingURL=no-handler-suffix.js.map
|
|
@@ -46,11 +46,7 @@ const ALLOWED_SUFFIXES = [
|
|
|
46
46
|
'Displayed',
|
|
47
47
|
];
|
|
48
48
|
// Common compound nouns that should not be flagged as Hungarian notation
|
|
49
|
-
const ALLOWED_COMPOUND_NOUNS = [
|
|
50
|
-
'PhoneNumber',
|
|
51
|
-
'EmailAddress',
|
|
52
|
-
'PostalCode',
|
|
53
|
-
];
|
|
49
|
+
const ALLOWED_COMPOUND_NOUNS = ['PhoneNumber', 'EmailAddress', 'PostalCode'];
|
|
54
50
|
// Common built-in JavaScript prototype methods
|
|
55
51
|
const BUILT_IN_METHODS = new Set([
|
|
56
52
|
// String methods
|
|
@@ -205,7 +201,7 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
205
201
|
fixable: undefined,
|
|
206
202
|
schema: [],
|
|
207
203
|
messages: {
|
|
208
|
-
noHungarian: '
|
|
204
|
+
noHungarian: 'Identifier "{{name}}" encodes its type through a prefix or suffix (Hungarian notation). Type-coded names hide the domain concept and become misleading when the underlying type changes. Rename it to a domain-focused name without the type marker and rely on TypeScript for type information.',
|
|
209
205
|
},
|
|
210
206
|
},
|
|
211
207
|
defaultOptions: [],
|
|
@@ -225,11 +221,12 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
225
221
|
if (variableName.includes(compoundNoun)) {
|
|
226
222
|
// Check if it's a prefix like "strPhoneNumber" (which should be flagged)
|
|
227
223
|
const prefix = variableName.substring(0, variableName.indexOf(compoundNoun));
|
|
228
|
-
if (TYPE_MARKERS.some(marker => prefix.toLowerCase() === marker.toLowerCase())) {
|
|
224
|
+
if (TYPE_MARKERS.some((marker) => prefix.toLowerCase() === marker.toLowerCase())) {
|
|
229
225
|
// This is a type marker prefix + compound noun, so it should be flagged
|
|
230
226
|
return true;
|
|
231
227
|
}
|
|
232
|
-
// Otherwise
|
|
228
|
+
// Otherwise treat the compound noun as an allowed descriptive phrase
|
|
229
|
+
// (e.g., userPhoneNumber is acceptable even though it contains "Number")
|
|
233
230
|
return false;
|
|
234
231
|
}
|
|
235
232
|
}
|
|
@@ -281,7 +278,8 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
281
278
|
// Check if it's a suffix with proper boundary (e.g., userString, itemArray)
|
|
282
279
|
if (normalizedVarName.endsWith(normalizedMarker) &&
|
|
283
280
|
normalizedVarName.length > normalizedMarker.length &&
|
|
284
|
-
/[A-Z0-9]/.test(variableName[variableName.length - normalizedMarker.length - 1])
|
|
281
|
+
(/[A-Z0-9]/.test(variableName[variableName.length - normalizedMarker.length - 1]) ||
|
|
282
|
+
/[A-Z]/.test(variableName[variableName.length - normalizedMarker.length]))) {
|
|
285
283
|
return true;
|
|
286
284
|
}
|
|
287
285
|
// Check for word boundaries to avoid matching substrings
|
|
@@ -290,17 +288,19 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
290
288
|
if (markerIndex === -1) {
|
|
291
289
|
return false;
|
|
292
290
|
}
|
|
293
|
-
const
|
|
294
|
-
const
|
|
295
|
-
const suffix = variableName.at(markerIndex + normalizedMarker.length);
|
|
291
|
+
const preMarkerPrefix = markerIndex > 0 ? variableName[markerIndex - 1] : '';
|
|
292
|
+
const suffix = variableName[markerIndex + normalizedMarker.length] ?? '';
|
|
296
293
|
// Ensure we have proper word boundaries
|
|
297
294
|
// A word boundary is defined by:
|
|
298
295
|
// 1. Start of string OR underscore OR capital letter before the marker
|
|
299
296
|
// 2. End of string OR underscore OR capital letter after the marker
|
|
300
|
-
const hasStartBoundary = markerIndex === 0 ||
|
|
297
|
+
const hasStartBoundary = markerIndex === 0 ||
|
|
298
|
+
preMarkerPrefix === '_' ||
|
|
299
|
+
/[A-Z]/.test(preMarkerPrefix) ||
|
|
300
|
+
/[A-Z]/.test(variableName[markerIndex]);
|
|
301
301
|
const hasEndBoundary = markerIndex + normalizedMarker.length === normalizedVarName.length ||
|
|
302
302
|
suffix === '_' ||
|
|
303
|
-
/[A-Z]/.test(suffix
|
|
303
|
+
/[A-Z]/.test(suffix);
|
|
304
304
|
return hasStartBoundary && hasEndBoundary;
|
|
305
305
|
});
|
|
306
306
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
type Options = [
|
|
3
|
+
{
|
|
4
|
+
props?: string[];
|
|
5
|
+
allowRenderProps?: boolean;
|
|
6
|
+
allowModuleScopeFactories?: boolean;
|
|
7
|
+
}
|
|
8
|
+
];
|
|
9
|
+
export declare const noInlineComponentProp: TSESLint.RuleModule<"inlineComponentProp", Options, TSESLint.RuleListener>;
|
|
10
|
+
export {};
|