@blumintinc/eslint-plugin-blumint 1.12.5 → 1.13.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 +140 -99
- package/lib/index.js +111 -20
- package/lib/rules/array-methods-this-context.js +20 -2
- package/lib/rules/avoid-utils-directory.js +11 -2
- package/lib/rules/dynamic-https-errors.d.ts +3 -1
- package/lib/rules/dynamic-https-errors.js +48 -10
- 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} +51 -95
- 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 +832 -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 +4 -1
- package/lib/rules/enforce-console-error.d.ts +3 -0
- package/lib/rules/enforce-console-error.js +250 -0
- package/lib/rules/enforce-css-media-queries.js +24 -46
- package/lib/rules/enforce-dynamic-firebase-imports.js +100 -19
- package/lib/rules/enforce-dynamic-imports.js +1 -1
- package/lib/rules/enforce-exported-function-types.js +3 -3
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.d.ts +2 -0
- package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +212 -0
- package/lib/rules/enforce-firestore-doc-ref-generic.js +335 -0
- package/lib/rules/enforce-firestore-facade.js +347 -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-global-constants.d.ts +4 -1
- package/lib/rules/enforce-global-constants.js +240 -12
- package/lib/rules/enforce-memoize-async.d.ts +2 -1
- package/lib/rules/enforce-memoize-async.js +96 -27
- package/lib/rules/enforce-memoize-getters.d.ts +3 -0
- package/lib/rules/enforce-memoize-getters.js +123 -0
- package/lib/rules/enforce-microdiff.js +2 -1
- package/lib/rules/enforce-positive-naming.js +5 -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-singular-type-names.js +9 -5
- package/lib/rules/enforce-typescript-markdown-code-blocks.d.ts +1 -0
- package/lib/rules/enforce-typescript-markdown-code-blocks.js +96 -0
- package/lib/rules/enforce-verb-noun-naming.js +85 -10
- package/lib/rules/extract-global-constants.js +57 -45
- package/lib/rules/fast-deep-equal-over-microdiff.d.ts +2 -1
- package/lib/rules/fast-deep-equal-over-microdiff.js +356 -78
- package/lib/rules/firestore-transaction-reads-before-writes.d.ts +1 -0
- package/lib/rules/firestore-transaction-reads-before-writes.js +231 -0
- package/lib/rules/generic-starts-with-t.js +8 -2
- 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-array-length-in-deps.d.ts +12 -0
- package/lib/rules/no-array-length-in-deps.js +308 -0
- package/lib/rules/no-async-array-filter.js +5 -2
- package/lib/rules/no-complex-cloud-params.js +15 -2
- package/lib/rules/no-compositing-layer-props.js +4 -2
- package/lib/rules/no-entire-object-hook-deps.d.ts +3 -1
- package/lib/rules/no-entire-object-hook-deps.js +317 -63
- 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-firestore-object-arrays.js +345 -30
- package/lib/rules/no-hungarian.js +13 -13
- package/lib/rules/no-margin-properties.d.ts +7 -1
- package/lib/rules/no-margin-properties.js +16 -3
- package/lib/rules/no-misleading-boolean-prefixes.d.ts +7 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +327 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.d.ts +1 -0
- package/lib/rules/no-overridable-method-calls-in-constructor.js +260 -0
- package/lib/rules/no-passthrough-getters.d.ts +4 -0
- package/lib/rules/no-passthrough-getters.js +170 -0
- package/lib/rules/no-redundant-usecallback-wrapper.d.ts +8 -0
- package/lib/rules/no-redundant-usecallback-wrapper.js +315 -0
- package/lib/rules/no-restricted-properties-fix.d.ts +12 -0
- package/lib/rules/no-restricted-properties-fix.js +172 -0
- package/lib/rules/no-separate-loading-state.d.ts +8 -0
- package/lib/rules/no-separate-loading-state.js +142 -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-undefined-null-passthrough.js +166 -19
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -2
- package/lib/rules/no-unsafe-firestore-spread.js +8 -5
- package/lib/rules/no-unused-props.d.ts +3 -0
- package/lib/rules/no-unused-props.js +213 -78
- package/lib/rules/optimize-object-boolean-conditions.d.ts +1 -0
- package/lib/rules/optimize-object-boolean-conditions.js +234 -0
- package/lib/rules/parallelize-async-operations.d.ts +8 -0
- package/lib/rules/parallelize-async-operations.js +375 -0
- package/lib/rules/prefer-batch-operations.js +199 -33
- package/lib/rules/prefer-block-comments-for-declarations.js +13 -3
- 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 +250 -0
- package/lib/rules/prefer-fragment-component.js +3 -3
- package/lib/rules/prefer-global-router-state-key.d.ts +7 -3
- package/lib/rules/prefer-global-router-state-key.js +230 -31
- package/lib/rules/prefer-next-dynamic.d.ts +9 -0
- package/lib/rules/prefer-next-dynamic.js +366 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.d.ts +1 -0
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +430 -0
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +6 -0
- package/lib/rules/prefer-nullish-coalescing-override.js +182 -0
- package/lib/rules/prefer-params-over-parent-id.d.ts +5 -0
- package/lib/rules/prefer-params-over-parent-id.js +228 -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-url-tostring-over-tojson.d.ts +2 -0
- package/lib/rules/prefer-url-tostring-over-tojson.js +166 -0
- package/lib/rules/prefer-use-deep-compare-memo.d.ts +4 -0
- package/lib/rules/prefer-use-deep-compare-memo.js +428 -0
- package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +24 -1
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +24 -5
- 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-image-optimized.js +1 -1
- package/lib/rules/require-memo.js +11 -1
- package/lib/rules/require-usememo-object-literals.js +7 -1
- 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 +270 -0
- package/lib/utils/ASTHelpers.js +10 -0
- package/lib/utils/createRule.js +1 -1
- package/lib/utils/graph/ClassGraphSorterReadability.js +1 -1
- package/lib/utils/ruleTester.d.ts +1 -0
- package/lib/utils/ruleTester.js +4 -1
- package/package.json +21 -14
- package/lib/rules/enforce-assertSafe-object-key.d.ts +0 -2
- package/lib/rules/no-restricted-imports-dynamic.d.ts +0 -25
- package/lib/rules/no-restricted-imports-dynamic.js +0 -213
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noMisleadingBooleanPrefixes = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const DEFAULT_PREFIXES = ['is', 'has', 'should'];
|
|
7
|
+
function unwrapTypeNode(node) {
|
|
8
|
+
let current = node;
|
|
9
|
+
while (current.type === 'TSParenthesizedType' &&
|
|
10
|
+
current.typeAnnotation) {
|
|
11
|
+
current = current.typeAnnotation;
|
|
12
|
+
}
|
|
13
|
+
return current;
|
|
14
|
+
}
|
|
15
|
+
function isUppercaseLetter(char) {
|
|
16
|
+
return char >= 'A' && char <= 'Z';
|
|
17
|
+
}
|
|
18
|
+
function isDigit(char) {
|
|
19
|
+
return char >= '0' && char <= '9';
|
|
20
|
+
}
|
|
21
|
+
function startsWithBooleanPrefix(name, prefixes) {
|
|
22
|
+
if (!name)
|
|
23
|
+
return false;
|
|
24
|
+
const lower = name.toLowerCase();
|
|
25
|
+
for (const prefix of prefixes) {
|
|
26
|
+
const p = prefix.toLowerCase();
|
|
27
|
+
if (lower.startsWith(p)) {
|
|
28
|
+
if (name.length === p.length)
|
|
29
|
+
return true; // edge: exact match like "is"
|
|
30
|
+
const next = name[p.length];
|
|
31
|
+
if (isUppercaseLetter(next) || next === '_' || isDigit(next)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function isTsBooleanLike(typeNode) {
|
|
39
|
+
if (!typeNode)
|
|
40
|
+
return false;
|
|
41
|
+
typeNode = unwrapTypeNode(typeNode);
|
|
42
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword)
|
|
43
|
+
return true;
|
|
44
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
|
|
45
|
+
typeof typeNode.literal.value === 'boolean') {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const isBooleanishUnionMember = (t) => {
|
|
49
|
+
const unwrapped = unwrapTypeNode(t);
|
|
50
|
+
return (unwrapped.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword ||
|
|
51
|
+
(unwrapped.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
|
|
52
|
+
typeof unwrapped.literal.value ===
|
|
53
|
+
'boolean') ||
|
|
54
|
+
unwrapped.type === utils_1.AST_NODE_TYPES.TSUndefinedKeyword ||
|
|
55
|
+
unwrapped.type === utils_1.AST_NODE_TYPES.TSNullKeyword ||
|
|
56
|
+
unwrapped.type === utils_1.AST_NODE_TYPES.TSVoidKeyword);
|
|
57
|
+
};
|
|
58
|
+
// Allow unions like boolean | undefined | null | void
|
|
59
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSUnionType) {
|
|
60
|
+
return typeNode.types.every((t) => isBooleanishUnionMember(t));
|
|
61
|
+
}
|
|
62
|
+
// Promise<boolean> (or Promise<boolean | undefined | null>)
|
|
63
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
64
|
+
typeNode.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
65
|
+
typeNode.typeName.name === 'Promise' &&
|
|
66
|
+
typeNode.typeParameters?.params?.length) {
|
|
67
|
+
const inner = typeNode.typeParameters.params[0];
|
|
68
|
+
if (!inner)
|
|
69
|
+
return false;
|
|
70
|
+
const resolvedInner = unwrapTypeNode(inner);
|
|
71
|
+
if (isBooleanishUnionMember(resolvedInner))
|
|
72
|
+
return true;
|
|
73
|
+
const innerType = resolvedInner;
|
|
74
|
+
if (innerType.type === utils_1.AST_NODE_TYPES.TSUnionType &&
|
|
75
|
+
Array.isArray(innerType.types) &&
|
|
76
|
+
innerType.types.every((t) => isBooleanishUnionMember(t))) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
function isExpressionBooleanLike(expr) {
|
|
83
|
+
switch (expr.type) {
|
|
84
|
+
case utils_1.AST_NODE_TYPES.Literal:
|
|
85
|
+
return typeof expr.value === 'boolean' ? true : 'non';
|
|
86
|
+
case utils_1.AST_NODE_TYPES.TemplateLiteral:
|
|
87
|
+
return 'non';
|
|
88
|
+
case utils_1.AST_NODE_TYPES.ObjectExpression:
|
|
89
|
+
case utils_1.AST_NODE_TYPES.ArrayExpression:
|
|
90
|
+
case utils_1.AST_NODE_TYPES.NewExpression:
|
|
91
|
+
case utils_1.AST_NODE_TYPES.ClassExpression:
|
|
92
|
+
case utils_1.AST_NODE_TYPES.FunctionExpression:
|
|
93
|
+
case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
|
|
94
|
+
return 'non';
|
|
95
|
+
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
96
|
+
if (expr.operator === '!')
|
|
97
|
+
return true; // !x or !!x
|
|
98
|
+
if (expr.operator === 'void')
|
|
99
|
+
return 'non';
|
|
100
|
+
return 'unknown';
|
|
101
|
+
case utils_1.AST_NODE_TYPES.BinaryExpression: {
|
|
102
|
+
const cmp = ['===', '!==', '==', '!=', '>', '<', '>=', '<='];
|
|
103
|
+
return cmp.includes(expr.operator) ? true : 'unknown';
|
|
104
|
+
}
|
|
105
|
+
case utils_1.AST_NODE_TYPES.LogicalExpression:
|
|
106
|
+
// && and || often return non-boolean operands; don't infer true
|
|
107
|
+
return 'unknown';
|
|
108
|
+
case utils_1.AST_NODE_TYPES.MemberExpression: {
|
|
109
|
+
if (expr.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
110
|
+
expr.property.name === 'length') {
|
|
111
|
+
return 'non';
|
|
112
|
+
}
|
|
113
|
+
return 'unknown';
|
|
114
|
+
}
|
|
115
|
+
case utils_1.AST_NODE_TYPES.ConditionalExpression: {
|
|
116
|
+
const cons = isExpressionBooleanLike(expr.consequent);
|
|
117
|
+
const alt = isExpressionBooleanLike(expr.alternate);
|
|
118
|
+
if (cons === true && alt === true)
|
|
119
|
+
return true;
|
|
120
|
+
if (cons === 'non' || alt === 'non')
|
|
121
|
+
return 'non';
|
|
122
|
+
return 'unknown';
|
|
123
|
+
}
|
|
124
|
+
case utils_1.AST_NODE_TYPES.CallExpression: {
|
|
125
|
+
if (expr.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
126
|
+
expr.callee.name === 'Boolean') {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return 'unknown';
|
|
130
|
+
}
|
|
131
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
132
|
+
if (expr.name === 'undefined')
|
|
133
|
+
return 'non';
|
|
134
|
+
return 'unknown';
|
|
135
|
+
case utils_1.AST_NODE_TYPES.AwaitExpression:
|
|
136
|
+
return 'unknown';
|
|
137
|
+
default:
|
|
138
|
+
return 'unknown';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function getReturnTypeNode(node) {
|
|
142
|
+
if (node.returnType?.typeAnnotation)
|
|
143
|
+
return node.returnType.typeAnnotation;
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
function hasTypePredicate(node) {
|
|
147
|
+
return (node.returnType?.typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypePredicate);
|
|
148
|
+
}
|
|
149
|
+
function collectReturnExpressions(fn) {
|
|
150
|
+
const results = [];
|
|
151
|
+
const visited = new Set();
|
|
152
|
+
function visit(n) {
|
|
153
|
+
if (!n || visited.has(n))
|
|
154
|
+
return;
|
|
155
|
+
visited.add(n);
|
|
156
|
+
// Do not traverse into nested functions/classes
|
|
157
|
+
if (n.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
158
|
+
n.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
159
|
+
n.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
160
|
+
n.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
|
|
161
|
+
n.type === utils_1.AST_NODE_TYPES.ClassExpression) {
|
|
162
|
+
if (n === fn) {
|
|
163
|
+
// traverse this function's body
|
|
164
|
+
// continue
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (n.type === utils_1.AST_NODE_TYPES.ReturnStatement) {
|
|
171
|
+
if (n.argument && n.argument.type) {
|
|
172
|
+
results.push(n.argument);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// return; without value
|
|
176
|
+
// Represent as Identifier 'undefined' to treat as non-boolean
|
|
177
|
+
// We won't push undefined, but handle later by a flag
|
|
178
|
+
results.noValueReturn = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const key of Object.keys(n)) {
|
|
182
|
+
if (key === 'parent' || key === 'range' || key === 'loc')
|
|
183
|
+
continue;
|
|
184
|
+
const value = n[key];
|
|
185
|
+
if (!value)
|
|
186
|
+
continue;
|
|
187
|
+
if (Array.isArray(value)) {
|
|
188
|
+
for (const child of value) {
|
|
189
|
+
if (child && typeof child === 'object' && 'type' in child)
|
|
190
|
+
visit(child);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else if (value && typeof value === 'object' && 'type' in value) {
|
|
194
|
+
visit(value);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (fn.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression && fn.expression) {
|
|
199
|
+
// expression-bodied arrow function: synthesize a return
|
|
200
|
+
const bodyExpr = fn.body;
|
|
201
|
+
results.push(bodyExpr);
|
|
202
|
+
return results;
|
|
203
|
+
}
|
|
204
|
+
if (fn.body && fn.body.type === utils_1.AST_NODE_TYPES.BlockStatement)
|
|
205
|
+
visit(fn.body);
|
|
206
|
+
return results;
|
|
207
|
+
}
|
|
208
|
+
exports.noMisleadingBooleanPrefixes = (0, createRule_1.createRule)({
|
|
209
|
+
name: 'no-misleading-boolean-prefixes',
|
|
210
|
+
meta: {
|
|
211
|
+
type: 'problem',
|
|
212
|
+
docs: {
|
|
213
|
+
description: 'Reserve boolean-style prefixes (is/has/should) for functions that actually return boolean values.',
|
|
214
|
+
recommended: 'error',
|
|
215
|
+
},
|
|
216
|
+
schema: [
|
|
217
|
+
{
|
|
218
|
+
type: 'object',
|
|
219
|
+
properties: {
|
|
220
|
+
prefixes: {
|
|
221
|
+
type: 'array',
|
|
222
|
+
items: { type: 'string' },
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
additionalProperties: false,
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
messages: {
|
|
229
|
+
nonBooleanReturn: 'Function "{{name}}" uses a boolean-style prefix but does not return a boolean value.',
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
defaultOptions: [{ prefixes: DEFAULT_PREFIXES }],
|
|
233
|
+
create(context, [options]) {
|
|
234
|
+
const prefixes = (options && options.prefixes) || DEFAULT_PREFIXES;
|
|
235
|
+
function shouldCheckName(name) {
|
|
236
|
+
return startsWithBooleanPrefix(name, prefixes);
|
|
237
|
+
}
|
|
238
|
+
function report(node, name) {
|
|
239
|
+
context.report({ node, messageId: 'nonBooleanReturn', data: { name } });
|
|
240
|
+
}
|
|
241
|
+
function checkFunctionLike(node, name, reportNode) {
|
|
242
|
+
if (!shouldCheckName(name))
|
|
243
|
+
return;
|
|
244
|
+
// Type predicate allows boolean-like
|
|
245
|
+
if (hasTypePredicate(node))
|
|
246
|
+
return;
|
|
247
|
+
const typeNode = getReturnTypeNode(node);
|
|
248
|
+
if (typeNode) {
|
|
249
|
+
if (isTsBooleanLike(typeNode))
|
|
250
|
+
return;
|
|
251
|
+
// Explicit non-boolean annotation
|
|
252
|
+
report(reportNode, name);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const returns = collectReturnExpressions(node);
|
|
256
|
+
const noValueReturn = returns.noValueReturn === true;
|
|
257
|
+
if (noValueReturn) {
|
|
258
|
+
report(reportNode, name);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (returns.length === 0) {
|
|
262
|
+
// No returns implies void
|
|
263
|
+
report(reportNode, name);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
for (const expr of returns) {
|
|
267
|
+
const kind = isExpressionBooleanLike(expr);
|
|
268
|
+
if (kind === 'non') {
|
|
269
|
+
report(reportNode, name);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// If we can't determine it's non-boolean, do not report to avoid false positives
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
FunctionDeclaration(node) {
|
|
277
|
+
if (!node.id)
|
|
278
|
+
return;
|
|
279
|
+
checkFunctionLike(node, node.id.name, node.id);
|
|
280
|
+
},
|
|
281
|
+
FunctionExpression(node) {
|
|
282
|
+
// Prefer variable declarator or method/property name
|
|
283
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
284
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
285
|
+
checkFunctionLike(node, node.parent.id.name, node.parent.id);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
// If part of a property or method, let dedicated visitors handle it to avoid duplicates
|
|
289
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.Property)
|
|
290
|
+
return;
|
|
291
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.MethodDefinition)
|
|
292
|
+
return;
|
|
293
|
+
if (node.id) {
|
|
294
|
+
checkFunctionLike(node, node.id.name, node.id);
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
ArrowFunctionExpression(node) {
|
|
298
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
299
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
300
|
+
checkFunctionLike(node, node.parent.id.name, node.parent.id);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
// If part of a property, let the Property visitor handle it to avoid duplicates
|
|
304
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.Property)
|
|
305
|
+
return;
|
|
306
|
+
},
|
|
307
|
+
MethodDefinition(node) {
|
|
308
|
+
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
309
|
+
return;
|
|
310
|
+
const name = node.key.name;
|
|
311
|
+
const fn = node.value;
|
|
312
|
+
if (fn.type === utils_1.AST_NODE_TYPES.TSEmptyBodyFunctionExpression)
|
|
313
|
+
return;
|
|
314
|
+
checkFunctionLike(fn, name, node.key);
|
|
315
|
+
},
|
|
316
|
+
Property(node) {
|
|
317
|
+
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
318
|
+
return;
|
|
319
|
+
if (node.value.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
320
|
+
node.value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
|
|
321
|
+
checkFunctionLike(node.value, node.key.name, node.key);
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
//# sourceMappingURL=no-misleading-boolean-prefixes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const noOverridableMethodCallsInConstructor: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noOverridableMethodCallsInConstructor", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noOverridableMethodCallsInConstructor = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.noOverridableMethodCallsInConstructor = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'no-overridable-method-calls-in-constructor',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Disallow calling overridable methods in constructors to prevent unexpected behavior',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: undefined,
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
noOverridableMethodCallsInConstructor: 'Avoid calling overridable methods in constructors. This can lead to unexpected behavior when the method is overridden in a derived class.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a method is overridable (not private and not static)
|
|
24
|
+
*/
|
|
25
|
+
function isOverridableMethod(node) {
|
|
26
|
+
// Private methods are not overridable
|
|
27
|
+
if (node.accessibility === 'private') {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
// ES private methods (#foo) are non-overridable
|
|
31
|
+
if (node.key.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
// Static methods are not overridable in the same way as instance methods
|
|
35
|
+
if (node.static) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Checks if a method is abstract
|
|
42
|
+
*/
|
|
43
|
+
function isAbstractMethod(_node) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Collects all method names from a class, categorized by whether they are overridable
|
|
48
|
+
*/
|
|
49
|
+
function collectMethodNames(classNode) {
|
|
50
|
+
const overridableMethods = new Set();
|
|
51
|
+
const abstractMethods = new Set();
|
|
52
|
+
for (const member of classNode.body.body) {
|
|
53
|
+
if (member.type === utils_1.AST_NODE_TYPES.MethodDefinition) {
|
|
54
|
+
const methodName = getMethodName(member.key);
|
|
55
|
+
if (methodName) {
|
|
56
|
+
if (isAbstractMethod(member)) {
|
|
57
|
+
abstractMethods.add(methodName);
|
|
58
|
+
}
|
|
59
|
+
else if (isOverridableMethod(member)) {
|
|
60
|
+
overridableMethods.add(methodName);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (member.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition) {
|
|
65
|
+
const methodName = getMethodName(member.key);
|
|
66
|
+
if (methodName) {
|
|
67
|
+
abstractMethods.add(methodName);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { overridableMethods, abstractMethods };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Gets the method name from a property key
|
|
75
|
+
*/
|
|
76
|
+
function getMethodName(key) {
|
|
77
|
+
switch (key.type) {
|
|
78
|
+
case utils_1.AST_NODE_TYPES.Identifier:
|
|
79
|
+
return key.name;
|
|
80
|
+
case utils_1.AST_NODE_TYPES.Literal:
|
|
81
|
+
return typeof key.value === 'string'
|
|
82
|
+
? key.value
|
|
83
|
+
: String(key.value);
|
|
84
|
+
default:
|
|
85
|
+
return null; // Computed property names are not handled
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Checks if a call expression is calling a method on 'this' or 'super'
|
|
90
|
+
*/
|
|
91
|
+
function isThisOrSuperMethodCall(node) {
|
|
92
|
+
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
93
|
+
!node.callee.computed // Ignore computed properties like this[methodName]()
|
|
94
|
+
) {
|
|
95
|
+
const object = node.callee.object;
|
|
96
|
+
const property = node.callee.property;
|
|
97
|
+
// Check if it's a call on 'this'
|
|
98
|
+
if (object.type === utils_1.AST_NODE_TYPES.ThisExpression &&
|
|
99
|
+
property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
100
|
+
return {
|
|
101
|
+
isMethodCall: true,
|
|
102
|
+
methodName: property.name,
|
|
103
|
+
isSuper: false,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// Check if it's a call on 'super'
|
|
107
|
+
if (object.type === utils_1.AST_NODE_TYPES.Super &&
|
|
108
|
+
property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
109
|
+
return {
|
|
110
|
+
isMethodCall: true,
|
|
111
|
+
methodName: property.name,
|
|
112
|
+
isSuper: true,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// Check for method chaining: this.method().anotherMethod()
|
|
116
|
+
// In this case, object is a CallExpression
|
|
117
|
+
if (object.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
118
|
+
const nestedCall = isThisOrSuperMethodCall(object);
|
|
119
|
+
if (nestedCall.isMethodCall) {
|
|
120
|
+
return nestedCall; // Return the nested method call info
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { isMethodCall: false, methodName: null, isSuper: false };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Checks if a member expression is accessing a property on 'this' or 'super'
|
|
128
|
+
*/
|
|
129
|
+
function isThisOrSuperPropertyAccess(node) {
|
|
130
|
+
if (!node.computed) {
|
|
131
|
+
// Ignore computed properties like this[propertyName]
|
|
132
|
+
const object = node.object;
|
|
133
|
+
const property = node.property;
|
|
134
|
+
// Check if it's access on 'this'
|
|
135
|
+
if (object.type === utils_1.AST_NODE_TYPES.ThisExpression &&
|
|
136
|
+
property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
137
|
+
// Skip 'constructor' property as it's not an overridable method
|
|
138
|
+
if (property.name === 'constructor') {
|
|
139
|
+
return {
|
|
140
|
+
isPropertyAccess: false,
|
|
141
|
+
propertyName: null,
|
|
142
|
+
isSuper: false,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
isPropertyAccess: true,
|
|
147
|
+
propertyName: property.name,
|
|
148
|
+
isSuper: false,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// Check if it's access on 'super'
|
|
152
|
+
if (object.type === utils_1.AST_NODE_TYPES.Super &&
|
|
153
|
+
property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
154
|
+
return {
|
|
155
|
+
isPropertyAccess: true,
|
|
156
|
+
propertyName: property.name,
|
|
157
|
+
isSuper: true,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { isPropertyAccess: false, propertyName: null, isSuper: false };
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
// Process class declarations and expressions
|
|
165
|
+
'ClassDeclaration, ClassExpression'(node) {
|
|
166
|
+
const { overridableMethods, abstractMethods } = collectMethodNames(node);
|
|
167
|
+
// Find the constructor method
|
|
168
|
+
const ctorMethod = node.body.body.find((member) => member.type === utils_1.AST_NODE_TYPES.MethodDefinition &&
|
|
169
|
+
member.kind === 'constructor');
|
|
170
|
+
if (!ctorMethod || !ctorMethod.value.body) {
|
|
171
|
+
return; // No constructor or constructor has no body
|
|
172
|
+
}
|
|
173
|
+
// Function to recursively check for method calls in the constructor
|
|
174
|
+
function checkForMethodCalls(bodyNode) {
|
|
175
|
+
if (!bodyNode || typeof bodyNode !== 'object') {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Check for method calls
|
|
179
|
+
if (bodyNode.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
180
|
+
const { isMethodCall, methodName, isSuper } = isThisOrSuperMethodCall(bodyNode);
|
|
181
|
+
if (isMethodCall && methodName) {
|
|
182
|
+
// Abstract methods are always a problem when called from constructor
|
|
183
|
+
// This includes super calls to abstract methods
|
|
184
|
+
if (abstractMethods.has(methodName)) {
|
|
185
|
+
context.report({
|
|
186
|
+
node: bodyNode,
|
|
187
|
+
messageId: 'noOverridableMethodCallsInConstructor',
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
// Overridable methods are a problem when called on 'this'
|
|
191
|
+
else if (overridableMethods.has(methodName) && !isSuper) {
|
|
192
|
+
context.report({
|
|
193
|
+
node: bodyNode,
|
|
194
|
+
messageId: 'noOverridableMethodCallsInConstructor',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Check for property access (getters/setters) - only if not part of a call expression
|
|
200
|
+
else if (bodyNode.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
201
|
+
// Check if this MemberExpression is the callee of a CallExpression
|
|
202
|
+
// If so, skip it as it will be handled by the CallExpression check
|
|
203
|
+
const parent = bodyNode.parent;
|
|
204
|
+
if (parent &&
|
|
205
|
+
parent.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
206
|
+
parent.callee === bodyNode) {
|
|
207
|
+
return; // Skip this MemberExpression as it's part of a method call
|
|
208
|
+
}
|
|
209
|
+
const { isPropertyAccess, propertyName, isSuper } = isThisOrSuperPropertyAccess(bodyNode);
|
|
210
|
+
if (isPropertyAccess && propertyName) {
|
|
211
|
+
// Abstract methods are always a problem when accessed from constructor
|
|
212
|
+
if (abstractMethods.has(propertyName)) {
|
|
213
|
+
context.report({
|
|
214
|
+
node: bodyNode,
|
|
215
|
+
messageId: 'noOverridableMethodCallsInConstructor',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// Overridable methods are a problem when accessed on 'this'
|
|
219
|
+
else if (overridableMethods.has(propertyName) && !isSuper) {
|
|
220
|
+
context.report({
|
|
221
|
+
node: bodyNode,
|
|
222
|
+
messageId: 'noOverridableMethodCallsInConstructor',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Avoid circular references by not checking 'parent' property
|
|
228
|
+
const keysToSkip = new Set(['parent', 'loc', 'range']);
|
|
229
|
+
// Don't traverse into function expressions or arrow functions as they create new scopes
|
|
230
|
+
if (bodyNode.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
231
|
+
bodyNode.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
232
|
+
bodyNode.type === utils_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// Recursively check all child nodes
|
|
236
|
+
for (const key in bodyNode) {
|
|
237
|
+
if (keysToSkip.has(key))
|
|
238
|
+
continue;
|
|
239
|
+
const child = bodyNode[key];
|
|
240
|
+
if (child && typeof child === 'object') {
|
|
241
|
+
if ('type' in child) {
|
|
242
|
+
checkForMethodCalls(child);
|
|
243
|
+
}
|
|
244
|
+
else if (Array.isArray(child)) {
|
|
245
|
+
child.forEach((item) => {
|
|
246
|
+
if (item && typeof item === 'object' && 'type' in item) {
|
|
247
|
+
checkForMethodCalls(item);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Start checking from the constructor body
|
|
255
|
+
checkForMethodCalls(ctorMethod.value.body);
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
//# sourceMappingURL=no-overridable-method-calls-in-constructor.js.map
|