@blumintinc/eslint-plugin-blumint 1.13.0 → 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 +170 -139
- package/lib/index.js +190 -1
- package/lib/rules/array-methods-this-context.js +1 -1
- 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.js +3 -4
- package/lib/rules/enforce-assert-safe-object-key.js +11 -11
- package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
- package/lib/rules/enforce-callback-memo.js +1 -1
- package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
- package/lib/rules/enforce-console-error.js +35 -7
- 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-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 +1 -1
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
- package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
- package/lib/rules/enforce-firestore-facade.js +4 -2
- package/lib/rules/enforce-firestore-set-merge.js +3 -4
- package/lib/rules/enforce-global-constants.js +1 -1
- package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
- package/lib/rules/enforce-memoize-async.js +2 -6
- package/lib/rules/enforce-memoize-getters.js +39 -15
- package/lib/rules/enforce-microdiff.js +1 -1
- package/lib/rules/enforce-object-literal-as-const.js +4 -4
- package/lib/rules/enforce-positive-naming.js +4 -0
- package/lib/rules/enforce-props-argument-name.js +2 -2
- 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 +1 -1
- 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.js +7 -3
- 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 +5 -4
- 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 +40 -13
- package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
- package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
- package/lib/rules/flatten-push-calls.d.ts +2 -0
- package/lib/rules/flatten-push-calls.js +428 -0
- 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/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.js +9 -3
- 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 +7 -3
- package/lib/rules/no-compositing-layer-props.js +2 -2
- 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.js +4 -6
- package/lib/rules/no-excessive-parent-chain.js +1 -1
- 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 +67 -12
- 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 +1 -1
- 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.js +6 -6
- package/lib/rules/no-memoize-on-static.js +9 -1
- package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
- 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.js +29 -17
- package/lib/rules/no-passthrough-getters.js +38 -2
- 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.js +16 -3
- 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.js +11 -10
- package/lib/rules/no-separate-loading-state.js +7 -16
- package/lib/rules/no-stale-state-across-await.js +1 -1
- package/lib/rules/no-type-assertion-returns.js +43 -49
- package/lib/rules/no-undefined-null-passthrough.js +61 -31
- 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 -1
- package/lib/rules/no-unpinned-dependencies.js +36 -5
- package/lib/rules/no-unused-props.d.ts +2 -2
- package/lib/rules/no-unused-props.js +295 -91
- 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.js +6 -4
- package/lib/rules/parallelize-async-operations.js +24 -5
- package/lib/rules/prefer-batch-operations.d.ts +1 -3
- package/lib/rules/prefer-batch-operations.js +32 -5
- package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
- 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-field-paths-in-transforms.js +4 -3
- package/lib/rules/prefer-fragment-component.js +1 -1
- 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.js +88 -13
- 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.js +60 -69
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
- package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
- package/lib/rules/prefer-params-over-parent-id.js +220 -171
- package/lib/rules/prefer-settings-object.js +2 -2
- package/lib/rules/prefer-type-over-interface.js +7 -2
- package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
- 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.js +2 -2
- 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 +1 -1
- package/lib/rules/require-memo.js +1 -1
- 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 +2 -3
- package/lib/rules/test-file-location-enforcement.js +1 -1
- package/lib/rules/use-latest-callback.js +6 -5
- 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 +24 -0
- package/lib/utils/getMethodName.d.ts +27 -0
- package/lib/utils/getMethodName.js +35 -0
- package/lib/utils/tsTypeClassifier.d.ts +30 -0
- package/lib/utils/tsTypeClassifier.js +149 -0
- package/package.json +3 -1
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.noInlineComponentProp = void 0;
|
|
27
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
28
|
+
const ts = __importStar(require("typescript"));
|
|
29
|
+
const createRule_1 = require("../utils/createRule");
|
|
30
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
31
|
+
const DEFAULT_PROP_PATTERNS = ['CatalogWrapper', '*Wrapper', '*Component'];
|
|
32
|
+
const DEFAULT_OPTIONS = {
|
|
33
|
+
props: DEFAULT_PROP_PATTERNS,
|
|
34
|
+
allowRenderProps: true,
|
|
35
|
+
allowModuleScopeFactories: true,
|
|
36
|
+
};
|
|
37
|
+
const INLINE_COMPONENT_NAME = 'inline component';
|
|
38
|
+
function isPascalCase(name) {
|
|
39
|
+
return /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
40
|
+
}
|
|
41
|
+
function matchesPattern(name, pattern) {
|
|
42
|
+
if (pattern === '*')
|
|
43
|
+
return true;
|
|
44
|
+
if (pattern.includes('*')) {
|
|
45
|
+
const wildcardCount = (pattern.match(/\*/g) || []).length;
|
|
46
|
+
if (wildcardCount > 2) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
const escaped = pattern
|
|
50
|
+
.replace(/[-/\\^$+?.()|[\]{}]/g, '\\$&')
|
|
51
|
+
.replace(/\*/g, '.*');
|
|
52
|
+
return new RegExp(`^${escaped}$`).test(name);
|
|
53
|
+
}
|
|
54
|
+
return name === pattern;
|
|
55
|
+
}
|
|
56
|
+
function isRenderPropName(name) {
|
|
57
|
+
if (name === 'children' || name === 'child' || name === 'render') {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (name.startsWith('render')) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return (name === 'rowRenderer' ||
|
|
64
|
+
name === 'cellRenderer' ||
|
|
65
|
+
name === 'itemRenderer' ||
|
|
66
|
+
name === 'renderItem' ||
|
|
67
|
+
name === 'renderRow');
|
|
68
|
+
}
|
|
69
|
+
function unwrapExpression(expr) {
|
|
70
|
+
let current = expr;
|
|
71
|
+
while (current) {
|
|
72
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
73
|
+
current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression) {
|
|
74
|
+
current = current.expression;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (current.type === utils_1.AST_NODE_TYPES.ChainExpression) {
|
|
78
|
+
current = current.expression;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
return current ?? null;
|
|
84
|
+
}
|
|
85
|
+
function isReactCreateElementCall(expr) {
|
|
86
|
+
if (expr.type !== utils_1.AST_NODE_TYPES.CallExpression)
|
|
87
|
+
return false;
|
|
88
|
+
const callee = expr.callee;
|
|
89
|
+
if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
90
|
+
callee.name === 'createElement') {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
94
|
+
!callee.computed &&
|
|
95
|
+
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
96
|
+
callee.property.name === 'createElement') {
|
|
97
|
+
if (callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
98
|
+
callee.object.name === 'React') {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
function returnsCreateElement(node) {
|
|
105
|
+
if (node.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
106
|
+
isReactCreateElementCall(node)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (node.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
110
|
+
for (const statement of node.body) {
|
|
111
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
112
|
+
statement.argument &&
|
|
113
|
+
statement.argument.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
114
|
+
isReactCreateElementCall(statement.argument)) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
function isFunctionNode(node) {
|
|
122
|
+
if (!node)
|
|
123
|
+
return false;
|
|
124
|
+
return (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
125
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
126
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration);
|
|
127
|
+
}
|
|
128
|
+
function getCalleeName(callee) {
|
|
129
|
+
const unwrapped = unwrapExpression(callee);
|
|
130
|
+
if (!unwrapped)
|
|
131
|
+
return null;
|
|
132
|
+
if (unwrapped.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
133
|
+
return unwrapped.name;
|
|
134
|
+
}
|
|
135
|
+
if (unwrapped.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
136
|
+
!unwrapped.computed &&
|
|
137
|
+
unwrapped.property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
138
|
+
const prop = unwrapped.property.name;
|
|
139
|
+
if (unwrapped.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
140
|
+
unwrapped.object.name) {
|
|
141
|
+
return `${unwrapped.object.name}.${prop}`;
|
|
142
|
+
}
|
|
143
|
+
return prop;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
function getFunctionFromCall(call) {
|
|
148
|
+
const calleeName = getCalleeName(call.callee);
|
|
149
|
+
const firstArg = unwrapExpression(call.arguments[0] ?? null);
|
|
150
|
+
if (!firstArg || !isFunctionNode(firstArg)) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
if (calleeName === 'useCallback' ||
|
|
154
|
+
calleeName === 'React.useCallback' ||
|
|
155
|
+
calleeName === 'useMemo' ||
|
|
156
|
+
calleeName === 'React.useMemo' ||
|
|
157
|
+
calleeName === 'memo' ||
|
|
158
|
+
calleeName === 'React.memo' ||
|
|
159
|
+
calleeName === 'forwardRef' ||
|
|
160
|
+
calleeName === 'React.forwardRef') {
|
|
161
|
+
return firstArg;
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
function getFunctionFromInit(init) {
|
|
166
|
+
const unwrapped = unwrapExpression(init);
|
|
167
|
+
if (!unwrapped)
|
|
168
|
+
return undefined;
|
|
169
|
+
if (isFunctionNode(unwrapped)) {
|
|
170
|
+
return unwrapped;
|
|
171
|
+
}
|
|
172
|
+
if (unwrapped.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
173
|
+
return getFunctionFromCall(unwrapped);
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
function isInModuleScope(node) {
|
|
178
|
+
let current = node.parent;
|
|
179
|
+
while (current) {
|
|
180
|
+
if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
181
|
+
current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
182
|
+
current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
183
|
+
current.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
if (current.type === utils_1.AST_NODE_TYPES.Program) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
current = current.parent;
|
|
190
|
+
}
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
function isComponentLikeFunction(fn, displayName) {
|
|
194
|
+
const body = fn.body;
|
|
195
|
+
const hasJSX = !!body && ASTHelpers_1.ASTHelpers.returnsJSX(body);
|
|
196
|
+
const expressionBody = fn.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
197
|
+
fn.body.type !== utils_1.AST_NODE_TYPES.BlockStatement
|
|
198
|
+
? fn.body
|
|
199
|
+
: null;
|
|
200
|
+
const hasCreateElement = (expressionBody && isReactCreateElementCall(expressionBody)) ||
|
|
201
|
+
(body ? returnsCreateElement(body) : false);
|
|
202
|
+
const looksLikeComponent = displayName ? isPascalCase(displayName) : false;
|
|
203
|
+
return hasJSX || hasCreateElement || looksLikeComponent;
|
|
204
|
+
}
|
|
205
|
+
function findVariableInScopes(context, identifier) {
|
|
206
|
+
const sourceCode = context.sourceCode;
|
|
207
|
+
let scope = sourceCode.getScope?.(identifier) ?? context.getScope();
|
|
208
|
+
while (scope) {
|
|
209
|
+
const variable = scope.variables.find((v) => v.name === identifier.name);
|
|
210
|
+
if (variable)
|
|
211
|
+
return variable;
|
|
212
|
+
scope = scope.upper;
|
|
213
|
+
}
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
function findObjectPropertyFunction(objExpr, propertyName) {
|
|
217
|
+
for (const prop of objExpr.properties) {
|
|
218
|
+
if (prop.type !== utils_1.AST_NODE_TYPES.Property)
|
|
219
|
+
continue;
|
|
220
|
+
if (prop.computed)
|
|
221
|
+
continue;
|
|
222
|
+
if (prop.key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
223
|
+
if (prop.key.name !== propertyName)
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
else if (prop.key.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
227
|
+
prop.key.value === propertyName) {
|
|
228
|
+
// match
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const value = unwrapExpression(prop.value);
|
|
234
|
+
if (value && isFunctionNode(value)) {
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
exports.noInlineComponentProp = (0, createRule_1.createRule)({
|
|
241
|
+
name: 'no-inline-component-prop',
|
|
242
|
+
meta: {
|
|
243
|
+
type: 'suggestion',
|
|
244
|
+
docs: {
|
|
245
|
+
description: 'Prevent inline function components defined in render from being passed to component-type props like CatalogWrapper to avoid remounts and UI flashes.',
|
|
246
|
+
recommended: 'error',
|
|
247
|
+
},
|
|
248
|
+
fixable: undefined,
|
|
249
|
+
schema: [
|
|
250
|
+
{
|
|
251
|
+
type: 'object',
|
|
252
|
+
properties: {
|
|
253
|
+
props: {
|
|
254
|
+
type: 'array',
|
|
255
|
+
items: { type: 'string' },
|
|
256
|
+
default: DEFAULT_PROP_PATTERNS,
|
|
257
|
+
},
|
|
258
|
+
allowRenderProps: {
|
|
259
|
+
type: 'boolean',
|
|
260
|
+
default: true,
|
|
261
|
+
},
|
|
262
|
+
allowModuleScopeFactories: {
|
|
263
|
+
type: 'boolean',
|
|
264
|
+
default: true,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
additionalProperties: false,
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
messages: {
|
|
271
|
+
inlineComponentProp: 'Inline component "{{componentName}}" is created inside a render scope and passed to component-type prop "{{propName}}". React treats it as a new component whenever the scope re-runs, remounting its subtree and causing UI flashes. Move the wrapper to module scope (optionally memoize with React.memo) and pass changing data via props or context instead.',
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
defaultOptions: [DEFAULT_OPTIONS],
|
|
275
|
+
create(context, [options]) {
|
|
276
|
+
const resolvedOptions = { ...DEFAULT_OPTIONS, ...(options ?? {}) };
|
|
277
|
+
const propPatterns = resolvedOptions.props ?? DEFAULT_PROP_PATTERNS;
|
|
278
|
+
const parserServices = context.parserServices;
|
|
279
|
+
const checker = parserServices?.program?.getTypeChecker();
|
|
280
|
+
const esTreeNodeToTSNodeMap = parserServices?.esTreeNodeToTSNodeMap;
|
|
281
|
+
function isComponentTypeByTypeInfo(attribute) {
|
|
282
|
+
if (!checker || !esTreeNodeToTSNodeMap)
|
|
283
|
+
return false;
|
|
284
|
+
const tsNode = esTreeNodeToTSNodeMap.get(attribute);
|
|
285
|
+
if (!tsNode)
|
|
286
|
+
return false;
|
|
287
|
+
let type;
|
|
288
|
+
if (ts.isExpression(tsNode)) {
|
|
289
|
+
type =
|
|
290
|
+
checker.getContextualType(tsNode) ??
|
|
291
|
+
checker.getTypeAtLocation(tsNode);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
type = checker.getTypeAtLocation(tsNode);
|
|
295
|
+
}
|
|
296
|
+
if (!type)
|
|
297
|
+
return false;
|
|
298
|
+
const typeText = checker.typeToString(type);
|
|
299
|
+
return (typeText.includes('ComponentType') ||
|
|
300
|
+
typeText.includes('FunctionComponent') ||
|
|
301
|
+
typeText.includes('ReactElement') ||
|
|
302
|
+
typeText.includes('FC'));
|
|
303
|
+
}
|
|
304
|
+
function isTargetProp(attribute, name) {
|
|
305
|
+
if (resolvedOptions.allowRenderProps && isRenderPropName(name)) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
const patternMatch = propPatterns.some((pattern) => matchesPattern(name, pattern));
|
|
309
|
+
const looksComponent = isPascalCase(name) ||
|
|
310
|
+
name.endsWith('Wrapper') ||
|
|
311
|
+
name.endsWith('Component');
|
|
312
|
+
if (patternMatch) {
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
if (looksComponent) {
|
|
316
|
+
return isComponentTypeByTypeInfo(attribute);
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
function shouldReportDefinition(definition, displayName) {
|
|
321
|
+
if (definition.type === 'ImportBinding' ||
|
|
322
|
+
definition.type === 'Parameter' ||
|
|
323
|
+
definition.type === 'Type') {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
const defNode = definition.node;
|
|
327
|
+
const moduleScoped = isInModuleScope(defNode);
|
|
328
|
+
if (moduleScoped && resolvedOptions.allowModuleScopeFactories) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
let fnNode;
|
|
332
|
+
if (defNode.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
333
|
+
fnNode = defNode;
|
|
334
|
+
}
|
|
335
|
+
else if (defNode.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
336
|
+
fnNode = getFunctionFromInit(defNode.init);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
if (!fnNode)
|
|
342
|
+
return false;
|
|
343
|
+
return isComponentLikeFunction(fnNode, displayName);
|
|
344
|
+
}
|
|
345
|
+
function report(node, propName, componentName) {
|
|
346
|
+
context.report({
|
|
347
|
+
node,
|
|
348
|
+
messageId: 'inlineComponentProp',
|
|
349
|
+
data: { propName, componentName },
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
function handleIdentifierExpression(identifier, propName) {
|
|
353
|
+
const variable = findVariableInScopes(context, identifier);
|
|
354
|
+
if (!variable)
|
|
355
|
+
return;
|
|
356
|
+
const definition = variable.defs.find((def) => (def.node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
357
|
+
def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) &&
|
|
358
|
+
def.type !== 'Parameter' &&
|
|
359
|
+
def.type !== 'ImportBinding');
|
|
360
|
+
if (!definition)
|
|
361
|
+
return;
|
|
362
|
+
if (shouldReportDefinition(definition, identifier.name)) {
|
|
363
|
+
report(identifier, propName, identifier.name);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function handleMemberExpression(member, propName) {
|
|
367
|
+
if (member.computed)
|
|
368
|
+
return;
|
|
369
|
+
if (member.property.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
370
|
+
return;
|
|
371
|
+
const objectId = member.object;
|
|
372
|
+
if (objectId.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
373
|
+
return;
|
|
374
|
+
const variable = findVariableInScopes(context, objectId);
|
|
375
|
+
if (!variable)
|
|
376
|
+
return;
|
|
377
|
+
const definition = variable.defs.find((def) => def.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
378
|
+
def.node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
379
|
+
def.node.id.name === objectId.name &&
|
|
380
|
+
def.type !== 'ImportBinding' &&
|
|
381
|
+
def.type !== 'Parameter' &&
|
|
382
|
+
def.type !== 'Type');
|
|
383
|
+
if (!definition)
|
|
384
|
+
return;
|
|
385
|
+
const defNode = definition.node;
|
|
386
|
+
if (!defNode ||
|
|
387
|
+
defNode.type !== utils_1.AST_NODE_TYPES.VariableDeclarator ||
|
|
388
|
+
!defNode.init ||
|
|
389
|
+
defNode.init.type !== utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (isInModuleScope(defNode) &&
|
|
393
|
+
resolvedOptions.allowModuleScopeFactories) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const fnNode = findObjectPropertyFunction(defNode.init, member.property.name);
|
|
397
|
+
if (fnNode && isComponentLikeFunction(fnNode, member.property.name)) {
|
|
398
|
+
report(member, propName, member.property.name);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function handleInlineFunctionExpression(fn, propName) {
|
|
402
|
+
const explicitName = fn.type === utils_1.AST_NODE_TYPES.FunctionExpression ? fn.id?.name : undefined;
|
|
403
|
+
if (!isComponentLikeFunction(fn, explicitName)) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const displayName = (fn.type === utils_1.AST_NODE_TYPES.FunctionExpression && fn.id?.name) ||
|
|
407
|
+
INLINE_COMPONENT_NAME;
|
|
408
|
+
report(fn, propName, displayName);
|
|
409
|
+
}
|
|
410
|
+
function handleCallExpression(call, propName) {
|
|
411
|
+
const fnNode = getFunctionFromCall(call);
|
|
412
|
+
if (!fnNode)
|
|
413
|
+
return;
|
|
414
|
+
if (isComponentLikeFunction(fnNode)) {
|
|
415
|
+
report(call, propName, INLINE_COMPONENT_NAME);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
JSXAttribute(node) {
|
|
420
|
+
if (!node.name || node.name.type !== utils_1.AST_NODE_TYPES.JSXIdentifier) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const propName = node.name.name;
|
|
424
|
+
if (!isTargetProp(node, propName))
|
|
425
|
+
return;
|
|
426
|
+
if (!node.value ||
|
|
427
|
+
node.value.type !== utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (node.value.expression.type === utils_1.AST_NODE_TYPES.JSXEmptyExpression) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const expression = unwrapExpression(node.value.expression);
|
|
434
|
+
if (!expression)
|
|
435
|
+
return;
|
|
436
|
+
if (expression.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
437
|
+
expression.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
438
|
+
handleInlineFunctionExpression(expression, propName);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (expression.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
442
|
+
handleCallExpression(expression, propName);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (expression.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
446
|
+
handleIdentifierExpression(expression, propName);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (expression.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
450
|
+
handleMemberExpression(expression, propName);
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
//# sourceMappingURL=no-inline-component-prop.js.map
|
|
@@ -135,7 +135,7 @@ exports.noJsxInHooks = (0, createRule_1.createRule)({
|
|
|
135
135
|
},
|
|
136
136
|
schema: [],
|
|
137
137
|
messages: {
|
|
138
|
-
noJsxInHooks: 'Hooks
|
|
138
|
+
noJsxInHooks: 'Hook "{{hookName}}" returns JSX. Hooks are meant to expose data and behavior; embedding JSX turns the hook into a hidden component and forces callers to use a hook where a component should render. Move the JSX into a component and have the hook return the values and callbacks that component needs instead.',
|
|
139
139
|
},
|
|
140
140
|
},
|
|
141
141
|
defaultOptions: [],
|
|
@@ -148,6 +148,7 @@ exports.noJsxInHooks = (0, createRule_1.createRule)({
|
|
|
148
148
|
context.report({
|
|
149
149
|
node: node.id,
|
|
150
150
|
messageId: 'noJsxInHooks',
|
|
151
|
+
data: { hookName: node.id.name },
|
|
151
152
|
});
|
|
152
153
|
return;
|
|
153
154
|
}
|
|
@@ -157,6 +158,7 @@ exports.noJsxInHooks = (0, createRule_1.createRule)({
|
|
|
157
158
|
context.report({
|
|
158
159
|
node: node.id,
|
|
159
160
|
messageId: 'noJsxInHooks',
|
|
161
|
+
data: { hookName: node.id.name },
|
|
160
162
|
});
|
|
161
163
|
}
|
|
162
164
|
}
|
|
@@ -173,6 +175,7 @@ exports.noJsxInHooks = (0, createRule_1.createRule)({
|
|
|
173
175
|
context.report({
|
|
174
176
|
node: parent.id,
|
|
175
177
|
messageId: 'noJsxInHooks',
|
|
178
|
+
data: { hookName: parent.id.name },
|
|
176
179
|
});
|
|
177
180
|
return;
|
|
178
181
|
}
|
|
@@ -181,6 +184,7 @@ exports.noJsxInHooks = (0, createRule_1.createRule)({
|
|
|
181
184
|
context.report({
|
|
182
185
|
node: parent.id,
|
|
183
186
|
messageId: 'noJsxInHooks',
|
|
187
|
+
data: { hookName: parent.id.name },
|
|
184
188
|
});
|
|
185
189
|
return;
|
|
186
190
|
}
|
|
@@ -190,6 +194,7 @@ exports.noJsxInHooks = (0, createRule_1.createRule)({
|
|
|
190
194
|
context.report({
|
|
191
195
|
node: parent.id,
|
|
192
196
|
messageId: 'noJsxInHooks',
|
|
197
|
+
data: { hookName: parent.id.name },
|
|
193
198
|
});
|
|
194
199
|
}
|
|
195
200
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.noJsxWhitespaceLiteral = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
4
5
|
const createRule_1 = require("../utils/createRule");
|
|
5
6
|
exports.noJsxWhitespaceLiteral = (0, createRule_1.createRule)({
|
|
6
7
|
name: 'no-jsx-whitespace-literal',
|
|
@@ -12,19 +13,24 @@ exports.noJsxWhitespaceLiteral = (0, createRule_1.createRule)({
|
|
|
12
13
|
},
|
|
13
14
|
schema: [],
|
|
14
15
|
messages: {
|
|
15
|
-
noWhitespaceLiteral: '
|
|
16
|
+
noWhitespaceLiteral: 'Whitespace-only JSX expression {{literal}} inserts fragile spacer nodes → React treats the whitespace as a separate text child that shifts, disappears, or duplicates when child elements are reordered, translated, or dynamically rendered → Place spacing inside text content (e.g., "Hello ") or use CSS spacing such as gap, margin, or padding.',
|
|
16
17
|
},
|
|
17
18
|
},
|
|
18
19
|
defaultOptions: [],
|
|
19
20
|
create(context) {
|
|
21
|
+
const typedContext = context;
|
|
22
|
+
const sourceCode = typedContext.sourceCode ?? context.getSourceCode();
|
|
20
23
|
return {
|
|
21
24
|
JSXExpressionContainer(node) {
|
|
22
|
-
if (node.expression.type ===
|
|
25
|
+
if (node.expression.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
23
26
|
typeof node.expression.value === 'string' &&
|
|
24
27
|
node.expression.value.trim() === '') {
|
|
25
28
|
context.report({
|
|
26
29
|
node,
|
|
27
30
|
messageId: 'noWhitespaceLiteral',
|
|
31
|
+
data: {
|
|
32
|
+
literal: sourceCode.getText(node),
|
|
33
|
+
},
|
|
28
34
|
});
|
|
29
35
|
}
|
|
30
36
|
},
|
|
@@ -39,7 +39,7 @@ exports.noMarginProperties = (0, createRule_1.createRule)({
|
|
|
39
39
|
meta: {
|
|
40
40
|
type: 'suggestion',
|
|
41
41
|
docs: {
|
|
42
|
-
description: '
|
|
42
|
+
description: 'Prevent margin properties (margin, marginLeft, marginRight, marginTop, marginBottom, mx, my, etc.) in MUI styling because margins fight container-controlled spacing, double gutters, and misaligned breakpoints; keep spacing centralized with padding, gap, or spacing props instead.',
|
|
43
43
|
recommended: 'warn',
|
|
44
44
|
},
|
|
45
45
|
schema: [
|
|
@@ -55,7 +55,7 @@ exports.noMarginProperties = (0, createRule_1.createRule)({
|
|
|
55
55
|
},
|
|
56
56
|
],
|
|
57
57
|
messages: {
|
|
58
|
-
noMarginProperties: '
|
|
58
|
+
noMarginProperties: 'Margin property "{{property}}" in MUI styling fights container-controlled spacing (Stack/Grid spacing, gap, responsive gutters) and produces double gutters, misalignment, and overflow as layouts shift. Keep spacing inside the component with padding or let the parent handle separation via gap/spacing so layout remains predictable.',
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
61
|
defaultOptions: [{ autofix: false }],
|
|
@@ -233,7 +233,7 @@ exports.noMarginProperties = (0, createRule_1.createRule)({
|
|
|
233
233
|
if (node.init?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
234
234
|
node.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
235
235
|
const variableName = node.id.name;
|
|
236
|
-
const
|
|
236
|
+
const sourceText = context.sourceCode.getText();
|
|
237
237
|
// Check for margin properties in the object
|
|
238
238
|
node.init.properties.forEach((prop) => {
|
|
239
239
|
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
@@ -246,9 +246,9 @@ exports.noMarginProperties = (0, createRule_1.createRule)({
|
|
|
246
246
|
}
|
|
247
247
|
if (propertyName && checkProperty(propertyName)) {
|
|
248
248
|
// Check if this variable is used in an sx prop
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
if (sourceText.includes(`sx={${variableName}}`) ||
|
|
250
|
+
sourceText.includes(`sx={{ ...${variableName}`) ||
|
|
251
|
+
sourceText.includes(`sx={Object.assign({}, ${variableName}`)) {
|
|
252
252
|
context.report({
|
|
253
253
|
node: prop,
|
|
254
254
|
messageId: 'noMarginProperties',
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.noMemoizeOnStatic = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const getMethodName_1 = require("../utils/getMethodName");
|
|
6
7
|
exports.noMemoizeOnStatic = (0, createRule_1.createRule)({
|
|
7
8
|
name: 'no-memoize-on-static',
|
|
8
9
|
meta: {
|
|
@@ -13,11 +14,12 @@ exports.noMemoizeOnStatic = (0, createRule_1.createRule)({
|
|
|
13
14
|
},
|
|
14
15
|
schema: [],
|
|
15
16
|
messages: {
|
|
16
|
-
noMemoizeOnStatic: '@Memoize()
|
|
17
|
+
noMemoizeOnStatic: 'Static member "{{methodName}}" uses @Memoize(), which shares one cache across every caller instead of per instance. Static memoization can leak stale or cross-tenant data because the cache survives across requests for the lifetime of the process. Remove @Memoize() or move the logic into an instance method so each consumer gets its own cache lifecycle.',
|
|
17
18
|
},
|
|
18
19
|
},
|
|
19
20
|
defaultOptions: [],
|
|
20
21
|
create(context) {
|
|
22
|
+
const sourceCode = context.getSourceCode();
|
|
21
23
|
// Track renamed imports of Memoize
|
|
22
24
|
const memoizeAliases = new Set(['Memoize']);
|
|
23
25
|
return {
|
|
@@ -43,6 +45,12 @@ exports.noMemoizeOnStatic = (0, createRule_1.createRule)({
|
|
|
43
45
|
context.report({
|
|
44
46
|
node: decorator,
|
|
45
47
|
messageId: 'noMemoizeOnStatic',
|
|
48
|
+
data: {
|
|
49
|
+
methodName: (0, getMethodName_1.getMethodName)(node, sourceCode, {
|
|
50
|
+
privateIdentifierPrefix: '#',
|
|
51
|
+
computedFallbackToText: false,
|
|
52
|
+
}) || '<unknown>',
|
|
53
|
+
},
|
|
46
54
|
});
|
|
47
55
|
}
|
|
48
56
|
}
|
|
@@ -210,7 +210,7 @@ exports.noMisleadingBooleanPrefixes = (0, createRule_1.createRule)({
|
|
|
210
210
|
meta: {
|
|
211
211
|
type: 'problem',
|
|
212
212
|
docs: {
|
|
213
|
-
description: 'Reserve boolean-style prefixes (is/has/should) for functions that actually return boolean values.',
|
|
213
|
+
description: 'Reserve boolean-style prefixes (is/has/should) for functions that actually return boolean values to avoid misleading call sites.',
|
|
214
214
|
recommended: 'error',
|
|
215
215
|
},
|
|
216
216
|
schema: [
|
|
@@ -226,7 +226,7 @@ exports.noMisleadingBooleanPrefixes = (0, createRule_1.createRule)({
|
|
|
226
226
|
},
|
|
227
227
|
],
|
|
228
228
|
messages: {
|
|
229
|
-
nonBooleanReturn: 'Function "{{name}}" uses a boolean-style prefix but
|
|
229
|
+
nonBooleanReturn: 'Function "{{name}}" uses a boolean-style prefix but its return value is not guaranteed to be boolean. Boolean prefixes promise a yes/no answer; returning strings, objects, or void misleads callers and hides incorrect branching. Return a real boolean (e.g., add an explicit comparison or Boolean(...)) or rename the function to drop the boolean-style prefix (prefixes treated as boolean: {{prefixes}}).',
|
|
230
230
|
},
|
|
231
231
|
},
|
|
232
232
|
defaultOptions: [{ prefixes: DEFAULT_PREFIXES }],
|
|
@@ -236,7 +236,11 @@ exports.noMisleadingBooleanPrefixes = (0, createRule_1.createRule)({
|
|
|
236
236
|
return startsWithBooleanPrefix(name, prefixes);
|
|
237
237
|
}
|
|
238
238
|
function report(node, name) {
|
|
239
|
-
context.report({
|
|
239
|
+
context.report({
|
|
240
|
+
node,
|
|
241
|
+
messageId: 'nonBooleanReturn',
|
|
242
|
+
data: { name, prefixes: prefixes.join(', ') },
|
|
243
|
+
});
|
|
240
244
|
}
|
|
241
245
|
function checkFunctionLike(node, name, reportNode) {
|
|
242
246
|
if (!shouldCheckName(name))
|