@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
|
@@ -17,7 +17,7 @@ exports.noUnusedUseState = createRule({
|
|
|
17
17
|
},
|
|
18
18
|
fixable: 'code',
|
|
19
19
|
messages: {
|
|
20
|
-
unusedUseState: '
|
|
20
|
+
unusedUseState: 'State value "{{stateName}}" from useState is discarded. React still allocates state and re-renders for a value you never read, which misleads readers into thinking the component depends on that state. Remove the useState pair or switch to a ref/derived value when you only need the setter-style side effect.',
|
|
21
21
|
},
|
|
22
22
|
schema: [],
|
|
23
23
|
},
|
|
@@ -41,12 +41,16 @@ exports.noUnusedUseState = createRule({
|
|
|
41
41
|
utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
|
|
42
42
|
(arrayPattern.elements[0].name === '_' ||
|
|
43
43
|
arrayPattern.elements[0].name.startsWith('_'))) {
|
|
44
|
+
const stateIdentifier = arrayPattern.elements[0];
|
|
44
45
|
context.report({
|
|
45
46
|
node,
|
|
46
47
|
messageId: 'unusedUseState',
|
|
48
|
+
data: {
|
|
49
|
+
stateName: stateIdentifier.name,
|
|
50
|
+
},
|
|
47
51
|
fix: (fixer) => {
|
|
48
52
|
// Remove the entire useState declaration
|
|
49
|
-
const sourceCode = context.
|
|
53
|
+
const sourceCode = context.sourceCode;
|
|
50
54
|
const parentStatement = node.parent;
|
|
51
55
|
if (parentStatement &&
|
|
52
56
|
parentStatement.type ===
|
|
@@ -1,16 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.noUselessFragment = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes JSX child node types into short descriptors used inside lint messages.
|
|
6
|
+
* Keeps message phrasing consistent regardless of the specific child node shape.
|
|
7
|
+
* @param child - The JSX child node to describe.
|
|
8
|
+
* @returns Human-readable descriptor for the child type used in lint messages.
|
|
9
|
+
*/
|
|
10
|
+
const describeChild = (child) => {
|
|
11
|
+
switch (child.type) {
|
|
12
|
+
case 'JSXElement':
|
|
13
|
+
return 'JSX element';
|
|
14
|
+
case 'JSXFragment':
|
|
15
|
+
return 'fragment';
|
|
16
|
+
case 'JSXExpressionContainer':
|
|
17
|
+
return 'expression result';
|
|
18
|
+
case 'JSXText':
|
|
19
|
+
return 'text node';
|
|
20
|
+
case 'JSXSpreadChild':
|
|
21
|
+
return 'spread child';
|
|
22
|
+
default:
|
|
23
|
+
return 'child node';
|
|
24
|
+
}
|
|
25
|
+
};
|
|
4
26
|
exports.noUselessFragment = {
|
|
5
27
|
create(context) {
|
|
6
28
|
return {
|
|
7
29
|
JSXFragment(node) {
|
|
8
30
|
if (node.children.length === 1) {
|
|
31
|
+
const [child] = node.children;
|
|
9
32
|
context.report({
|
|
10
33
|
node,
|
|
11
34
|
messageId: 'noUselessFragment',
|
|
35
|
+
data: {
|
|
36
|
+
childKind: describeChild(child),
|
|
37
|
+
},
|
|
12
38
|
fix(fixer) {
|
|
13
|
-
const sourceCode = context.
|
|
39
|
+
const sourceCode = context.sourceCode;
|
|
14
40
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
15
41
|
const openingFragment = sourceCode.getFirstToken(node);
|
|
16
42
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
@@ -38,7 +64,7 @@ exports.noUselessFragment = {
|
|
|
38
64
|
recommended: 'error',
|
|
39
65
|
},
|
|
40
66
|
messages: {
|
|
41
|
-
noUselessFragment: 'React fragment
|
|
67
|
+
noUselessFragment: 'React fragment wraps a single {{childKind}} and does not provide grouping. Fragments exist to wrap multiple siblings; leaving this fragment adds extra syntax and a React tree node without changing the rendered output. Remove the fragment and return the child directly.',
|
|
42
68
|
},
|
|
43
69
|
schema: [],
|
|
44
70
|
fixable: 'code',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type Options = [
|
|
2
|
+
{
|
|
3
|
+
ignoreCallExpressions?: boolean;
|
|
4
|
+
ignoreSymbol?: boolean;
|
|
5
|
+
tsOnly?: boolean;
|
|
6
|
+
}
|
|
7
|
+
];
|
|
8
|
+
export declare const noUselessUsememoPrimitives: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"uselessUseMemoPrimitive", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
9
|
+
export default noUselessUsememoPrimitives;
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noUselessUsememoPrimitives = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const tsTypeClassifier_1 = require("../utils/tsTypeClassifier");
|
|
7
|
+
const DEFAULT_OPTIONS = {
|
|
8
|
+
ignoreCallExpressions: true,
|
|
9
|
+
ignoreSymbol: true,
|
|
10
|
+
tsOnly: false,
|
|
11
|
+
};
|
|
12
|
+
const NON_DETERMINISTIC_MEMBERS = new Set([
|
|
13
|
+
'Date.now',
|
|
14
|
+
'Math.random',
|
|
15
|
+
'performance.now',
|
|
16
|
+
'crypto.randomUUID',
|
|
17
|
+
'crypto.getRandomValues',
|
|
18
|
+
]);
|
|
19
|
+
const NON_DETERMINISTIC_CONSTRUCTORS = new Set(['Date']);
|
|
20
|
+
const COMPARISON_OPERATORS = new Set([
|
|
21
|
+
'==',
|
|
22
|
+
'===',
|
|
23
|
+
'!=',
|
|
24
|
+
'!==',
|
|
25
|
+
'<',
|
|
26
|
+
'<=',
|
|
27
|
+
'>',
|
|
28
|
+
'>=',
|
|
29
|
+
'instanceof',
|
|
30
|
+
'in',
|
|
31
|
+
]);
|
|
32
|
+
function isStringLikeWithoutTypes(expr) {
|
|
33
|
+
switch (expr.type) {
|
|
34
|
+
case utils_1.AST_NODE_TYPES.Literal:
|
|
35
|
+
return typeof expr.value === 'string';
|
|
36
|
+
case utils_1.AST_NODE_TYPES.TemplateLiteral:
|
|
37
|
+
return true;
|
|
38
|
+
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
39
|
+
return expr.operator === 'typeof';
|
|
40
|
+
case utils_1.AST_NODE_TYPES.BinaryExpression:
|
|
41
|
+
return (expr.operator === '+' &&
|
|
42
|
+
(isStringLikeWithoutTypes(expr.left) ||
|
|
43
|
+
isStringLikeWithoutTypes(expr.right)));
|
|
44
|
+
default:
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function isUseMemoCallee(callee) {
|
|
49
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
50
|
+
return callee.name === 'useMemo';
|
|
51
|
+
}
|
|
52
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
53
|
+
!callee.computed &&
|
|
54
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
55
|
+
return callee.property.name === 'useMemo';
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
function getReturnedExpression(callback) {
|
|
60
|
+
if (callback.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
61
|
+
return callback.body;
|
|
62
|
+
}
|
|
63
|
+
if (callback.body.body.length !== 1) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const soleStatement = callback.body.body[0];
|
|
67
|
+
if (soleStatement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
68
|
+
soleStatement.argument &&
|
|
69
|
+
soleStatement.argument.type !== utils_1.AST_NODE_TYPES.SequenceExpression) {
|
|
70
|
+
return soleStatement.argument;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
function walkExpression(expr, predicate, maxDepth = 100) {
|
|
75
|
+
const stack = [
|
|
76
|
+
{ node: expr, depth: 0 },
|
|
77
|
+
];
|
|
78
|
+
while (stack.length > 0) {
|
|
79
|
+
const { node: current, depth } = stack.pop();
|
|
80
|
+
if (depth > maxDepth)
|
|
81
|
+
continue;
|
|
82
|
+
if (predicate(current)) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
for (const key of Object.keys(current)) {
|
|
87
|
+
if (key === 'parent')
|
|
88
|
+
continue;
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
+
const value = current[key];
|
|
91
|
+
if (!value)
|
|
92
|
+
continue;
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
for (const child of value) {
|
|
95
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
96
|
+
stack.push({ node: child, depth: depth + 1 });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (typeof value === 'object' && 'type' in value) {
|
|
101
|
+
stack.push({ node: value, depth: depth + 1 });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
function containsCallExpression(expr) {
|
|
108
|
+
return walkExpression(expr, (node) => [
|
|
109
|
+
utils_1.AST_NODE_TYPES.CallExpression,
|
|
110
|
+
utils_1.AST_NODE_TYPES.NewExpression,
|
|
111
|
+
utils_1.AST_NODE_TYPES.TaggedTemplateExpression,
|
|
112
|
+
].includes(node.type));
|
|
113
|
+
}
|
|
114
|
+
function isNonDeterministicCall(node) {
|
|
115
|
+
const callee = node.callee;
|
|
116
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression && !callee.computed) {
|
|
117
|
+
const object = callee.object;
|
|
118
|
+
const property = callee.property;
|
|
119
|
+
if (object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
120
|
+
property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
121
|
+
const key = `${object.name}.${property.name}`;
|
|
122
|
+
if (NON_DETERMINISTIC_MEMBERS.has(key)) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier && callee.name === 'Date') {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
function isNonDeterministicInvocation(expr) {
|
|
133
|
+
return walkExpression(expr, (node) => {
|
|
134
|
+
if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
135
|
+
return isNonDeterministicCall(node);
|
|
136
|
+
}
|
|
137
|
+
if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
138
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
139
|
+
NON_DETERMINISTIC_CONSTRUCTORS.has(node.callee.name)) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function hasUnsafeSideEffects(expr) {
|
|
147
|
+
return walkExpression(expr, (node) => [
|
|
148
|
+
utils_1.AST_NODE_TYPES.AssignmentExpression,
|
|
149
|
+
utils_1.AST_NODE_TYPES.AwaitExpression,
|
|
150
|
+
utils_1.AST_NODE_TYPES.UpdateExpression,
|
|
151
|
+
utils_1.AST_NODE_TYPES.YieldExpression,
|
|
152
|
+
utils_1.AST_NODE_TYPES.SequenceExpression,
|
|
153
|
+
].includes(node.type) ||
|
|
154
|
+
(node.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
|
|
155
|
+
node.operator === 'delete'));
|
|
156
|
+
}
|
|
157
|
+
function describePrimitiveExpression(expr) {
|
|
158
|
+
switch (expr.type) {
|
|
159
|
+
case utils_1.AST_NODE_TYPES.Literal: {
|
|
160
|
+
if ('regex' in expr && expr.regex) {
|
|
161
|
+
return 'RegExp object';
|
|
162
|
+
}
|
|
163
|
+
if ('bigint' in expr && expr.bigint !== undefined) {
|
|
164
|
+
return 'bigint value';
|
|
165
|
+
}
|
|
166
|
+
if (expr.value === null)
|
|
167
|
+
return 'null value';
|
|
168
|
+
if (typeof expr.value === 'boolean')
|
|
169
|
+
return 'boolean value';
|
|
170
|
+
if (typeof expr.value === 'number')
|
|
171
|
+
return 'number value';
|
|
172
|
+
if (typeof expr.value === 'string')
|
|
173
|
+
return 'string value';
|
|
174
|
+
/* istanbul ignore next -- defensive fallback for uncommon literals */
|
|
175
|
+
return 'primitive value';
|
|
176
|
+
}
|
|
177
|
+
case utils_1.AST_NODE_TYPES.TemplateLiteral:
|
|
178
|
+
return 'string value';
|
|
179
|
+
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
180
|
+
if (expr.operator === '!')
|
|
181
|
+
return 'boolean condition';
|
|
182
|
+
if (expr.operator === 'void')
|
|
183
|
+
return 'undefined value';
|
|
184
|
+
if (expr.operator === 'typeof')
|
|
185
|
+
return 'string value';
|
|
186
|
+
/* istanbul ignore next -- other unary operators are treated as primitives */
|
|
187
|
+
return 'primitive value';
|
|
188
|
+
case utils_1.AST_NODE_TYPES.BinaryExpression:
|
|
189
|
+
if (COMPARISON_OPERATORS.has(expr.operator)) {
|
|
190
|
+
return 'boolean condition';
|
|
191
|
+
}
|
|
192
|
+
if (expr.operator === '+' && isStringLikeWithoutTypes(expr)) {
|
|
193
|
+
return 'string value';
|
|
194
|
+
}
|
|
195
|
+
return 'number value';
|
|
196
|
+
case utils_1.AST_NODE_TYPES.LogicalExpression:
|
|
197
|
+
return 'primitive value';
|
|
198
|
+
case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
|
199
|
+
return 'primitive value';
|
|
200
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
201
|
+
if (expr.name === 'undefined')
|
|
202
|
+
return 'undefined value';
|
|
203
|
+
if (expr.name === 'Infinity' || expr.name === 'NaN')
|
|
204
|
+
return 'number value';
|
|
205
|
+
return 'primitive value';
|
|
206
|
+
default:
|
|
207
|
+
/* istanbul ignore next -- unreachable with current node set */
|
|
208
|
+
return 'primitive value';
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function isPrimitiveExpressionWithoutTypes(expr) {
|
|
212
|
+
switch (expr.type) {
|
|
213
|
+
case utils_1.AST_NODE_TYPES.Literal: {
|
|
214
|
+
if ('regex' in expr && expr.regex) {
|
|
215
|
+
return { primitive: false, kind: describePrimitiveExpression(expr) };
|
|
216
|
+
}
|
|
217
|
+
return { primitive: true, kind: describePrimitiveExpression(expr) };
|
|
218
|
+
}
|
|
219
|
+
case utils_1.AST_NODE_TYPES.Identifier: {
|
|
220
|
+
const identifier = expr;
|
|
221
|
+
if (identifier.name === 'undefined' ||
|
|
222
|
+
identifier.name === 'Infinity' ||
|
|
223
|
+
identifier.name === 'NaN') {
|
|
224
|
+
return { primitive: true, kind: describePrimitiveExpression(expr) };
|
|
225
|
+
}
|
|
226
|
+
return { primitive: false, kind: 'primitive value' };
|
|
227
|
+
}
|
|
228
|
+
case utils_1.AST_NODE_TYPES.TemplateLiteral:
|
|
229
|
+
return { primitive: true, kind: describePrimitiveExpression(expr) };
|
|
230
|
+
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
231
|
+
return { primitive: true, kind: describePrimitiveExpression(expr) };
|
|
232
|
+
case utils_1.AST_NODE_TYPES.BinaryExpression: {
|
|
233
|
+
const primitive = COMPARISON_OPERATORS.has(expr.operator) ||
|
|
234
|
+
(isPrimitiveExpressionWithoutTypes(expr.left)
|
|
235
|
+
.primitive &&
|
|
236
|
+
isPrimitiveExpressionWithoutTypes(expr.right)
|
|
237
|
+
.primitive);
|
|
238
|
+
return {
|
|
239
|
+
primitive,
|
|
240
|
+
kind: describePrimitiveExpression(expr),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
case utils_1.AST_NODE_TYPES.LogicalExpression:
|
|
244
|
+
return {
|
|
245
|
+
primitive: isPrimitiveExpressionWithoutTypes(expr.left)
|
|
246
|
+
.primitive &&
|
|
247
|
+
isPrimitiveExpressionWithoutTypes(expr.right)
|
|
248
|
+
.primitive,
|
|
249
|
+
kind: describePrimitiveExpression(expr),
|
|
250
|
+
};
|
|
251
|
+
case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
|
252
|
+
return {
|
|
253
|
+
primitive: isPrimitiveExpressionWithoutTypes(expr.consequent).primitive &&
|
|
254
|
+
isPrimitiveExpressionWithoutTypes(expr.alternate).primitive,
|
|
255
|
+
kind: describePrimitiveExpression(expr),
|
|
256
|
+
};
|
|
257
|
+
case utils_1.AST_NODE_TYPES.ChainExpression:
|
|
258
|
+
return isPrimitiveExpressionWithoutTypes(expr.expression);
|
|
259
|
+
case utils_1.AST_NODE_TYPES.TSAsExpression:
|
|
260
|
+
case utils_1.AST_NODE_TYPES.TSTypeAssertion:
|
|
261
|
+
case utils_1.AST_NODE_TYPES.TSNonNullExpression:
|
|
262
|
+
return isPrimitiveExpressionWithoutTypes(expr.expression);
|
|
263
|
+
default:
|
|
264
|
+
return { primitive: false, kind: 'primitive value' };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
exports.noUselessUsememoPrimitives = (0, createRule_1.createRule)({
|
|
268
|
+
name: 'no-useless-usememo-primitives',
|
|
269
|
+
meta: {
|
|
270
|
+
type: 'suggestion',
|
|
271
|
+
docs: {
|
|
272
|
+
description: 'Disallow useless useMemo with primitive values.',
|
|
273
|
+
recommended: 'error',
|
|
274
|
+
},
|
|
275
|
+
fixable: 'code',
|
|
276
|
+
schema: [
|
|
277
|
+
{
|
|
278
|
+
type: 'object',
|
|
279
|
+
properties: {
|
|
280
|
+
ignoreCallExpressions: { type: 'boolean', default: true },
|
|
281
|
+
ignoreSymbol: { type: 'boolean', default: true },
|
|
282
|
+
tsOnly: { type: 'boolean', default: false },
|
|
283
|
+
},
|
|
284
|
+
additionalProperties: false,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
messages: {
|
|
288
|
+
uselessUseMemoPrimitive: 'useMemo wraps a primitive {{valueKind}}. → Primitives are pass-by-value and have no identity to preserve, so memoization provides zero referential-stability benefit and only adds unnecessary hook overhead. → Remove useMemo and inline the expression directly.',
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
defaultOptions: [DEFAULT_OPTIONS],
|
|
292
|
+
create(context) {
|
|
293
|
+
const options = { ...DEFAULT_OPTIONS, ...context.options[0] };
|
|
294
|
+
const sourceCode = context.sourceCode;
|
|
295
|
+
const services = sourceCode.parserServices;
|
|
296
|
+
const parserServices = services &&
|
|
297
|
+
'hasFullTypeInformation' in services &&
|
|
298
|
+
services.hasFullTypeInformation
|
|
299
|
+
? services
|
|
300
|
+
: null;
|
|
301
|
+
if (options.tsOnly && !parserServices) {
|
|
302
|
+
return {};
|
|
303
|
+
}
|
|
304
|
+
let tsModule = null;
|
|
305
|
+
let checker = null;
|
|
306
|
+
if (parserServices) {
|
|
307
|
+
try {
|
|
308
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
309
|
+
const ts = require('typescript');
|
|
310
|
+
tsModule = ts;
|
|
311
|
+
checker = parserServices.program.getTypeChecker();
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
/* istanbul ignore next -- TypeScript not available, falls back to heuristic path */
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function classifyExpressionTypeInternal(expr) {
|
|
318
|
+
if (!checker || !tsModule || !parserServices) {
|
|
319
|
+
return { status: 'unknown', kind: 'unknown value' };
|
|
320
|
+
}
|
|
321
|
+
return (0, tsTypeClassifier_1.classifyExpressionType)(expr, {
|
|
322
|
+
checker,
|
|
323
|
+
tsModule,
|
|
324
|
+
parserServices,
|
|
325
|
+
options,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
CallExpression(node) {
|
|
330
|
+
if (!isUseMemoCallee(node.callee)) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (node.arguments.length === 0) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const callback = node.arguments[0];
|
|
337
|
+
if (callback.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
338
|
+
callback.type !== utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (callback.async) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (callback.generator) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const returnedExpression = getReturnedExpression(callback);
|
|
348
|
+
if (!returnedExpression) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (hasUnsafeSideEffects(returnedExpression)) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (isNonDeterministicInvocation(returnedExpression)) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (options.ignoreCallExpressions &&
|
|
358
|
+
containsCallExpression(returnedExpression)) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const typeEvaluation = classifyExpressionTypeInternal(returnedExpression);
|
|
362
|
+
let isPrimitive = false;
|
|
363
|
+
let valueKind = typeEvaluation.kind;
|
|
364
|
+
if (typeEvaluation.status === 'primitive') {
|
|
365
|
+
isPrimitive = true;
|
|
366
|
+
}
|
|
367
|
+
else if (typeEvaluation.status !== 'non-primitive') {
|
|
368
|
+
const heuristic = isPrimitiveExpressionWithoutTypes(returnedExpression);
|
|
369
|
+
if (heuristic.primitive) {
|
|
370
|
+
isPrimitive = true;
|
|
371
|
+
valueKind = heuristic.kind;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (!isPrimitive) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
context.report({
|
|
378
|
+
node,
|
|
379
|
+
messageId: 'uselessUseMemoPrimitive',
|
|
380
|
+
data: {
|
|
381
|
+
valueKind,
|
|
382
|
+
},
|
|
383
|
+
fix(fixer) {
|
|
384
|
+
const replacement = `(${sourceCode.getText(returnedExpression)})`;
|
|
385
|
+
return fixer.replaceText(node, replacement);
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
exports.default = exports.noUselessUsememoPrimitives;
|
|
393
|
+
//# sourceMappingURL=no-useless-usememo-primitives.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
type Options = [
|
|
3
|
+
{
|
|
4
|
+
/**
|
|
5
|
+
* Patterns (regex strings) that describe call expressions considered "expensive"
|
|
6
|
+
* and therefore allowed even when they return pass-by-value types.
|
|
7
|
+
*/
|
|
8
|
+
allowExpensiveCalleePatterns?: string[];
|
|
9
|
+
}
|
|
10
|
+
];
|
|
11
|
+
type MessageIds = 'primitiveMemo' | 'invalidRegex';
|
|
12
|
+
export declare const noUsememoForPassByValue: TSESLint.RuleModule<MessageIds, Options, TSESLint.RuleListener>;
|
|
13
|
+
export default noUsememoForPassByValue;
|