@blumintinc/eslint-plugin-blumint 1.14.0 → 1.16.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 +36 -14
- package/lib/index.js +20 -12
- package/lib/rules/avoid-utils-directory.js +0 -4
- package/lib/rules/consistent-callback-naming.js +68 -3
- package/lib/rules/dynamic-https-errors.d.ts +1 -1
- package/lib/rules/dynamic-https-errors.js +119 -49
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +1 -0
- package/lib/rules/enforce-boolean-naming-prefixes.js +86 -331
- package/lib/rules/enforce-date-ttime.d.ts +1 -0
- package/lib/rules/enforce-date-ttime.js +156 -0
- package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
- package/lib/rules/enforce-dynamic-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-imports.js +42 -21
- package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
- package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
- package/lib/rules/enforce-global-constants.js +3 -3
- package/lib/rules/enforce-id-capitalization.js +1 -1
- package/lib/rules/enforce-memoize-async.js +66 -15
- package/lib/rules/enforce-mui-rounded-icons.js +42 -1
- package/lib/rules/enforce-props-argument-name.js +42 -16
- package/lib/rules/enforce-stable-hash-spread-props.js +3 -3
- package/lib/rules/enforce-transform-memoization.js +1 -1
- package/lib/rules/enforce-verb-noun-naming.js +3817 -4641
- package/lib/rules/global-const-style.js +25 -4
- package/lib/rules/logical-top-to-bottom-grouping.js +80 -2
- package/lib/rules/memo-compare-deeply-complex-props.js +183 -6
- package/lib/rules/memo-nested-react-components.js +243 -103
- package/lib/rules/no-array-length-in-deps.js +74 -3
- package/lib/rules/no-async-foreach.js +7 -2
- package/lib/rules/no-circular-references.d.ts +2 -1
- package/lib/rules/no-circular-references.js +150 -489
- package/lib/rules/no-compositing-layer-props.js +31 -0
- package/lib/rules/no-console-error.js +12 -10
- package/lib/rules/no-empty-dependency-use-callbacks.js +1 -1
- package/lib/rules/no-entire-object-hook-deps.js +147 -65
- package/lib/rules/no-excessive-parent-chain.js +3 -0
- package/lib/rules/no-explicit-return-type.js +6 -0
- package/lib/rules/no-hungarian.js +119 -24
- package/lib/rules/no-inline-component-prop.js +16 -7
- package/lib/rules/no-margin-properties.js +7 -38
- package/lib/rules/no-passthrough-getters.d.ts +2 -2
- package/lib/rules/no-passthrough-getters.js +83 -1
- package/lib/rules/no-redundant-this-params.js +50 -1
- package/lib/rules/no-unmemoized-memo-without-props.js +1 -1
- package/lib/rules/no-unnecessary-destructuring-rename.js +2 -5
- package/lib/rules/no-unnecessary-verb-suffix.js +79 -0
- package/lib/rules/no-unused-props.js +215 -37
- package/lib/rules/no-useless-fragment.js +10 -2
- package/lib/rules/parallelize-async-operations.js +117 -54
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +109 -4
- package/lib/rules/prefer-params-over-parent-id.js +1 -1
- package/lib/rules/prefer-settings-object.js +27 -10
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +75 -4
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -14
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
- package/lib/rules/prevent-children-clobber.js +9 -5
- package/lib/rules/react-memoize-literals.js +218 -13
- package/lib/rules/require-https-error-cause.js +30 -11
- package/lib/rules/require-memo.js +17 -9
- package/lib/rules/require-migration-script-metadata.d.ts +9 -0
- package/lib/rules/require-migration-script-metadata.js +206 -0
- package/lib/rules/warn-https-error-message-user-friendly.d.ts +1 -0
- package/lib/rules/warn-https-error-message-user-friendly.js +239 -0
- package/lib/utils/ASTHelpers.d.ts +49 -1
- package/lib/utils/ASTHelpers.js +394 -112
- package/package.json +7 -6
- package/release-manifest.json +166 -0
|
@@ -17,8 +17,10 @@ const COMMON_TYPES = [
|
|
|
17
17
|
'Symbol',
|
|
18
18
|
'BigInt',
|
|
19
19
|
];
|
|
20
|
-
//
|
|
21
|
-
|
|
20
|
+
// Abbreviation type markers (e.g. str, arr, obj). No English word is spelled this
|
|
21
|
+
// way, so their presence as a segment — even in the middle of a name — is
|
|
22
|
+
// unambiguously a type tag (strName, USER_STR_NAME, ConfigArrSettings).
|
|
23
|
+
const ABBREVIATION_MARKERS = [
|
|
22
24
|
'str',
|
|
23
25
|
'num',
|
|
24
26
|
'int',
|
|
@@ -27,6 +29,10 @@ const TYPE_MARKERS = [
|
|
|
27
29
|
'obj',
|
|
28
30
|
'fn',
|
|
29
31
|
'func',
|
|
32
|
+
];
|
|
33
|
+
// Combined type markers (former Hungarian prefixes and type suffixes)
|
|
34
|
+
const TYPE_MARKERS = [
|
|
35
|
+
...ABBREVIATION_MARKERS,
|
|
30
36
|
'array',
|
|
31
37
|
...COMMON_TYPES,
|
|
32
38
|
'Class',
|
|
@@ -34,6 +40,20 @@ const TYPE_MARKERS = [
|
|
|
34
40
|
//'Type', people like to use 'type' as a general purpose noun
|
|
35
41
|
'Enum',
|
|
36
42
|
];
|
|
43
|
+
const ABBREVIATION_MARKER_SET = new Set(ABBREVIATION_MARKERS);
|
|
44
|
+
// Single-letter Hungarian type prefixes (b=boolean, i=integer/index).
|
|
45
|
+
// Only matched as a strict camelCase prefix (e.g. bIsActive, iCount); never as
|
|
46
|
+
// a suffix/middle/SCREAMING_SNAKE segment, where a lone letter is almost always
|
|
47
|
+
// a real word fragment (tab, lib, ui) rather than a type tag.
|
|
48
|
+
const SINGLE_LETTER_PREFIXES = new Set(['b', 'i']);
|
|
49
|
+
// Full type-concept words (spelled out). When one of these appears as a clean
|
|
50
|
+
// PascalCase segment inside a multi-word TYPE name (alias/interface/class), it
|
|
51
|
+
// denotes a type concept/relation (e.g. StringToNumber, CapitalizedString,
|
|
52
|
+
// PromiseOrValue) rather than a redundant Hungarian type tag — comparable to the
|
|
53
|
+
// allowed compound noun PhoneNumber. Abbreviation markers (str/arr/obj/...) are
|
|
54
|
+
// deliberately excluded: no English word is spelled that way, so their presence
|
|
55
|
+
// as a segment is unambiguously a type tag even inside a type name.
|
|
56
|
+
const FULL_TYPE_WORDS = new Set([...COMMON_TYPES, 'Func'].map((word) => word.toLowerCase()));
|
|
37
57
|
// Allowed descriptive suffixes that should not be flagged as Hungarian notation
|
|
38
58
|
const ALLOWED_SUFFIXES = [
|
|
39
59
|
'Formatted',
|
|
@@ -190,6 +210,30 @@ const BUILT_IN_METHODS = new Set([
|
|
|
190
210
|
'catch',
|
|
191
211
|
'finally',
|
|
192
212
|
]);
|
|
213
|
+
// Split a PascalCase/camelCase identifier into its word segments
|
|
214
|
+
// (e.g. "StringToNumber" -> ["String","To","Number"], "FuncKeys" -> ["Func","Keys"]).
|
|
215
|
+
function splitCamelSegments(name) {
|
|
216
|
+
return name.match(/[A-Z]+(?![a-z])|[A-Z]?[a-z0-9]+|[A-Z]/g) ?? [];
|
|
217
|
+
}
|
|
218
|
+
// A TYPE name (alias/interface/class) is exempt from a full-type-word marker when
|
|
219
|
+
// that marker is one clean PascalCase segment among OTHER descriptive segments —
|
|
220
|
+
// i.e. the word denotes a type concept/relation, not a redundant type tag.
|
|
221
|
+
// Examples: StringToNumber, CapitalizedString, PromiseOrValue, FuncKeys.
|
|
222
|
+
// Abbreviation markers (str/arr/obj/...) never qualify, so genuine Hungarian type
|
|
223
|
+
// names like UserStrName / ConfigArrSettings / UserObjData still fire.
|
|
224
|
+
function isSemanticTypeConcept(typeName) {
|
|
225
|
+
const segments = splitCamelSegments(typeName);
|
|
226
|
+
if (segments.length < 2) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
const fullTypeWordSegments = segments.filter((segment) => FULL_TYPE_WORDS.has(segment.toLowerCase()));
|
|
230
|
+
if (fullTypeWordSegments.length === 0) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
// At least one segment must be a non-type-word descriptor so the name reads as a
|
|
234
|
+
// concept (e.g. Extract+Number) rather than bare type tags glued together.
|
|
235
|
+
return segments.some((segment) => !FULL_TYPE_WORDS.has(segment.toLowerCase()));
|
|
236
|
+
}
|
|
193
237
|
exports.noHungarian = (0, createRule_1.createRule)({
|
|
194
238
|
name: 'no-hungarian',
|
|
195
239
|
meta: {
|
|
@@ -208,8 +252,28 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
208
252
|
create(context) {
|
|
209
253
|
// Track identifiers that have already been checked to prevent double reporting
|
|
210
254
|
const checkedIdentifiers = new Set();
|
|
211
|
-
//
|
|
212
|
-
|
|
255
|
+
// Names declared as generic type parameters (e.g. TNumber, TKey). The leading
|
|
256
|
+
// `T` is the TypeScript convention for "Type parameter", not a Hungarian tag,
|
|
257
|
+
// so neither the declaration nor any reference to it is ever flagged.
|
|
258
|
+
const typeParameterNames = new Set();
|
|
259
|
+
// Check if a variable name contains a type marker with proper word boundaries.
|
|
260
|
+
// `isTypeName` is true for PascalCase type declarations (type aliases,
|
|
261
|
+
// interfaces, classes), enabling the semantic-type-concept exemption.
|
|
262
|
+
function hasTypeMarker(variableName, isTypeName = false) {
|
|
263
|
+
// Type names whose type-word denotes a concept/relation (StringToNumber,
|
|
264
|
+
// CapitalizedString, FuncKeys, PromiseOrValue) are not Hungarian — the word
|
|
265
|
+
// is part of the type's meaning, like the allowed compound noun PhoneNumber.
|
|
266
|
+
if (isTypeName && isSemanticTypeConcept(variableName)) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
// Single-letter Hungarian prefixes (bIsActive, iCount): a lone b/i directly
|
|
270
|
+
// followed by an uppercase letter is a type tag. Restricted to camelCase
|
|
271
|
+
// (lowercase first letter) to avoid PascalCase type names like IButton.
|
|
272
|
+
if (variableName.length > 1 &&
|
|
273
|
+
SINGLE_LETTER_PREFIXES.has(variableName[0]) &&
|
|
274
|
+
/[A-Z]/.test(variableName[1])) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
213
277
|
// Check if the variable name is exactly one of the allowed compound nouns
|
|
214
278
|
// or if it contains one of the allowed compound nouns but is not a prefix like "strPhoneNumber"
|
|
215
279
|
for (const compoundNoun of ALLOWED_COMPOUND_NOUNS) {
|
|
@@ -244,22 +308,28 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
244
308
|
if (!variableName.includes('_')) {
|
|
245
309
|
return false;
|
|
246
310
|
}
|
|
311
|
+
const parts = variableName.split('_');
|
|
312
|
+
const lastIndex = parts.length - 1;
|
|
247
313
|
return TYPE_MARKERS.some((marker) => {
|
|
248
314
|
const markerUpper = marker.toUpperCase();
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
315
|
+
const normalizedMarker = marker.toLowerCase();
|
|
316
|
+
const isAbbreviation = ABBREVIATION_MARKER_SET.has(normalizedMarker);
|
|
317
|
+
return parts.some((part, index) => {
|
|
318
|
+
if (part !== markerUpper) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
// Abbreviation markers (STR/ARR/OBJ/...) are type tags in any
|
|
322
|
+
// position — no English word is spelled that way.
|
|
323
|
+
if (isAbbreviation) {
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
// A FULL type word tags the entity's type only at the start (prefix),
|
|
327
|
+
// the end (suffix), or directly before the final noun
|
|
328
|
+
// (..._STRING_NAME). Buried deeper (EDITABLE_WRAPPER_NUMBER_PROPS_…)
|
|
329
|
+
// it qualifies an intermediate segment — a descriptive variant, not a
|
|
330
|
+
// type tag.
|
|
331
|
+
return (index === 0 || index === lastIndex || index === lastIndex - 1);
|
|
332
|
+
});
|
|
263
333
|
});
|
|
264
334
|
}
|
|
265
335
|
// For camelCase, PascalCase, etc.
|
|
@@ -328,9 +398,27 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
328
398
|
}
|
|
329
399
|
return false;
|
|
330
400
|
}
|
|
331
|
-
//
|
|
332
|
-
function
|
|
401
|
+
// Determine whether an identifier is (or references) a generic type parameter.
|
|
402
|
+
function isTypeParameter(node) {
|
|
403
|
+
// The type-parameter declaration itself: <TNumber>
|
|
404
|
+
if (node.parent &&
|
|
405
|
+
node.parent.type === utils_1.AST_NODE_TYPES.TSTypeParameter &&
|
|
406
|
+
node.parent.name === node) {
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
// A reference to a declared type parameter (e.g. x: TNumber).
|
|
410
|
+
return typeParameterNames.has(node.name);
|
|
411
|
+
}
|
|
412
|
+
// Check identifier for type markers (Hungarian notation).
|
|
413
|
+
// `isTypeName` enables the semantic-type-concept exemption for type
|
|
414
|
+
// declarations (aliases, interfaces, classes).
|
|
415
|
+
function checkIdentifier(node, isTypeName = false) {
|
|
333
416
|
const name = node.name;
|
|
417
|
+
// Generic type parameters (TNumber, TKey, ...) are a TypeScript naming
|
|
418
|
+
// convention, never Hungarian — skip the declaration and all references.
|
|
419
|
+
if (isTypeParameter(node)) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
334
422
|
// Create a unique ID for this node to avoid checking it twice
|
|
335
423
|
// Use the name along with source location for uniqueness
|
|
336
424
|
const nodeId = `${name}:${node.loc.start.line}:${node.loc.start.column}`;
|
|
@@ -344,7 +432,7 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
344
432
|
if (isExternalOrBuiltIn(node))
|
|
345
433
|
return;
|
|
346
434
|
// Check for type markers
|
|
347
|
-
if (hasTypeMarker(name)) {
|
|
435
|
+
if (hasTypeMarker(name, isTypeName)) {
|
|
348
436
|
context.report({
|
|
349
437
|
node,
|
|
350
438
|
messageId: 'noHungarian',
|
|
@@ -388,10 +476,17 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
388
476
|
}
|
|
389
477
|
}
|
|
390
478
|
},
|
|
479
|
+
// Record generic type-parameter names so neither the declaration nor any
|
|
480
|
+
// reference to them is flagged (the leading `T` is a TS convention).
|
|
481
|
+
TSTypeParameter(node) {
|
|
482
|
+
if (node.name.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
483
|
+
typeParameterNames.add(node.name.name);
|
|
484
|
+
}
|
|
485
|
+
},
|
|
391
486
|
// Check class declarations
|
|
392
487
|
ClassDeclaration(node) {
|
|
393
488
|
if (node.id) {
|
|
394
|
-
checkIdentifier(node.id);
|
|
489
|
+
checkIdentifier(node.id, true);
|
|
395
490
|
}
|
|
396
491
|
// Check class methods and properties
|
|
397
492
|
for (const member of node.body.body) {
|
|
@@ -421,11 +516,11 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
421
516
|
},
|
|
422
517
|
// Check type aliases
|
|
423
518
|
TSTypeAliasDeclaration(node) {
|
|
424
|
-
checkIdentifier(node.id);
|
|
519
|
+
checkIdentifier(node.id, true);
|
|
425
520
|
},
|
|
426
521
|
// Check interface declarations
|
|
427
522
|
TSInterfaceDeclaration(node) {
|
|
428
|
-
checkIdentifier(node.id);
|
|
523
|
+
checkIdentifier(node.id, true);
|
|
429
524
|
},
|
|
430
525
|
};
|
|
431
526
|
},
|
|
@@ -190,9 +190,9 @@ function isInModuleScope(node) {
|
|
|
190
190
|
}
|
|
191
191
|
return true;
|
|
192
192
|
}
|
|
193
|
-
function isComponentLikeFunction(fn, displayName) {
|
|
193
|
+
function isComponentLikeFunction(fn, context, displayName) {
|
|
194
194
|
const body = fn.body;
|
|
195
|
-
const hasJSX = !!body && ASTHelpers_1.ASTHelpers.returnsJSX(body);
|
|
195
|
+
const hasJSX = !!body && ASTHelpers_1.ASTHelpers.returnsJSX(body, context);
|
|
196
196
|
const expressionBody = fn.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
197
197
|
fn.body.type !== utils_1.AST_NODE_TYPES.BlockStatement
|
|
198
198
|
? fn.body
|
|
@@ -204,7 +204,15 @@ function isComponentLikeFunction(fn, displayName) {
|
|
|
204
204
|
}
|
|
205
205
|
function findVariableInScopes(context, identifier) {
|
|
206
206
|
const sourceCode = context.sourceCode;
|
|
207
|
-
let scope =
|
|
207
|
+
let scope = null;
|
|
208
|
+
try {
|
|
209
|
+
// Tolerate ESLint API variations across versions/configurations.
|
|
210
|
+
// Some contexts may not provide getScope or may throw unexpectedly.
|
|
211
|
+
scope = sourceCode.getScope?.(identifier) ?? context.getScope();
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
scope = null;
|
|
215
|
+
}
|
|
208
216
|
while (scope) {
|
|
209
217
|
const variable = scope.variables.find((v) => v.name === identifier.name);
|
|
210
218
|
if (variable)
|
|
@@ -340,7 +348,7 @@ exports.noInlineComponentProp = (0, createRule_1.createRule)({
|
|
|
340
348
|
}
|
|
341
349
|
if (!fnNode)
|
|
342
350
|
return false;
|
|
343
|
-
return isComponentLikeFunction(fnNode, displayName);
|
|
351
|
+
return isComponentLikeFunction(fnNode, context, displayName);
|
|
344
352
|
}
|
|
345
353
|
function report(node, propName, componentName) {
|
|
346
354
|
context.report({
|
|
@@ -394,13 +402,14 @@ exports.noInlineComponentProp = (0, createRule_1.createRule)({
|
|
|
394
402
|
return;
|
|
395
403
|
}
|
|
396
404
|
const fnNode = findObjectPropertyFunction(defNode.init, member.property.name);
|
|
397
|
-
if (fnNode &&
|
|
405
|
+
if (fnNode &&
|
|
406
|
+
isComponentLikeFunction(fnNode, context, member.property.name)) {
|
|
398
407
|
report(member, propName, member.property.name);
|
|
399
408
|
}
|
|
400
409
|
}
|
|
401
410
|
function handleInlineFunctionExpression(fn, propName) {
|
|
402
411
|
const explicitName = fn.type === utils_1.AST_NODE_TYPES.FunctionExpression ? fn.id?.name : undefined;
|
|
403
|
-
if (!isComponentLikeFunction(fn, explicitName)) {
|
|
412
|
+
if (!isComponentLikeFunction(fn, context, explicitName)) {
|
|
404
413
|
return;
|
|
405
414
|
}
|
|
406
415
|
const displayName = (fn.type === utils_1.AST_NODE_TYPES.FunctionExpression && fn.id?.name) ||
|
|
@@ -411,7 +420,7 @@ exports.noInlineComponentProp = (0, createRule_1.createRule)({
|
|
|
411
420
|
const fnNode = getFunctionFromCall(call);
|
|
412
421
|
if (!fnNode)
|
|
413
422
|
return;
|
|
414
|
-
if (isComponentLikeFunction(fnNode)) {
|
|
423
|
+
if (isComponentLikeFunction(fnNode, context)) {
|
|
415
424
|
report(call, propName, INLINE_COMPONENT_NAME);
|
|
416
425
|
}
|
|
417
426
|
}
|
|
@@ -67,7 +67,13 @@ exports.noMarginProperties = (0, createRule_1.createRule)({
|
|
|
67
67
|
const normalizedName = normalizePropertyName(propertyName);
|
|
68
68
|
return MARGIN_PROPERTIES.has(normalizedName);
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* True if node is in a flagged MUI styling context (sx attribute, sx
|
|
72
|
+
* object property, or css() call). Excludes createTheme styleOverrides:
|
|
73
|
+
* theme margins ARE the container-controlled styling (viewport insets,
|
|
74
|
+
* resets of MUI's built-in margins, internal layout), not the
|
|
75
|
+
* sibling-spacing this rule targets.
|
|
76
|
+
*/
|
|
71
77
|
function isMuiStylingContext(node) {
|
|
72
78
|
let current = node;
|
|
73
79
|
while (current?.parent) {
|
|
@@ -83,13 +89,6 @@ exports.noMarginProperties = (0, createRule_1.createRule)({
|
|
|
83
89
|
current.parent.key.name === 'sx') {
|
|
84
90
|
return true;
|
|
85
91
|
}
|
|
86
|
-
// Check for theme overrides (MUI's createTheme)
|
|
87
|
-
if (current.parent.type === utils_1.AST_NODE_TYPES.Property &&
|
|
88
|
-
current.parent.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
89
|
-
(current.parent.key.name === 'styleOverrides' ||
|
|
90
|
-
current.parent.key.name === 'components')) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
92
|
// Check for MUI's css function
|
|
94
93
|
if (current.parent.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
95
94
|
current.parent.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
@@ -290,36 +289,6 @@ exports.noMarginProperties = (0, createRule_1.createRule)({
|
|
|
290
289
|
checkObjectExpression(arg);
|
|
291
290
|
}
|
|
292
291
|
}
|
|
293
|
-
// Handle createTheme for MUI theme overrides
|
|
294
|
-
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
295
|
-
node.callee.name === 'createTheme' &&
|
|
296
|
-
node.arguments.length > 0 &&
|
|
297
|
-
node.arguments[0].type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
298
|
-
const themeObj = node.arguments[0];
|
|
299
|
-
// Find components property in theme object
|
|
300
|
-
const componentsProperty = themeObj.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
301
|
-
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
302
|
-
prop.key.name === 'components' &&
|
|
303
|
-
prop.value.type === utils_1.AST_NODE_TYPES.ObjectExpression);
|
|
304
|
-
if (componentsProperty &&
|
|
305
|
-
componentsProperty.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
306
|
-
// Check each component override
|
|
307
|
-
componentsProperty.value.properties.forEach((componentProp) => {
|
|
308
|
-
if (componentProp.type === utils_1.AST_NODE_TYPES.Property &&
|
|
309
|
-
componentProp.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
310
|
-
// Find styleOverrides property
|
|
311
|
-
const styleOverrides = componentProp.value.properties.find((prop) => prop.type === utils_1.AST_NODE_TYPES.Property &&
|
|
312
|
-
prop.key.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
313
|
-
prop.key.name === 'styleOverrides' &&
|
|
314
|
-
prop.value.type === utils_1.AST_NODE_TYPES.ObjectExpression);
|
|
315
|
-
if (styleOverrides &&
|
|
316
|
-
styleOverrides.value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
317
|
-
checkObjectExpression(styleOverrides.value);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
292
|
},
|
|
324
293
|
};
|
|
325
294
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TSESTree } from '@typescript-eslint/utils';
|
|
2
|
-
export declare const noPassthroughGetters:
|
|
1
|
+
import { TSESTree, TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const noPassthroughGetters: TSESLint.RuleModule<"noPassthroughGetter", never[], {
|
|
3
3
|
MethodDefinition(node: TSESTree.MethodDefinition): void;
|
|
4
4
|
}>;
|
|
@@ -1,9 +1,33 @@
|
|
|
1
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
|
+
};
|
|
2
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
26
|
exports.noPassthroughGetters = void 0;
|
|
4
27
|
const createRule_1 = require("../utils/createRule");
|
|
5
28
|
const utils_1 = require("@typescript-eslint/utils");
|
|
6
29
|
const getMethodName_1 = require("../utils/getMethodName");
|
|
30
|
+
const ts = __importStar(require("typescript"));
|
|
7
31
|
const COMPLEX_EXPRESSION_TYPES = new Set([
|
|
8
32
|
utils_1.AST_NODE_TYPES.CallExpression,
|
|
9
33
|
utils_1.AST_NODE_TYPES.TemplateLiteral,
|
|
@@ -14,7 +38,7 @@ const COMPLEX_EXPRESSION_TYPES = new Set([
|
|
|
14
38
|
]);
|
|
15
39
|
exports.noPassthroughGetters = (0, createRule_1.createRule)({
|
|
16
40
|
create(context) {
|
|
17
|
-
const sourceCode = context.
|
|
41
|
+
const sourceCode = context.sourceCode;
|
|
18
42
|
return {
|
|
19
43
|
// Target getter methods in classes
|
|
20
44
|
MethodDefinition(node) {
|
|
@@ -26,6 +50,10 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
|
|
|
26
50
|
if (node.decorators && node.decorators.length > 0) {
|
|
27
51
|
return;
|
|
28
52
|
}
|
|
53
|
+
// Check if this getter satisfies an interface or overrides a base class member
|
|
54
|
+
if (isRequiredByInterfaceOrBaseClass(node, sourceCode)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
29
57
|
const methodBody = node.value.body;
|
|
30
58
|
if (!methodBody) {
|
|
31
59
|
return;
|
|
@@ -74,6 +102,60 @@ exports.noPassthroughGetters = (0, createRule_1.createRule)({
|
|
|
74
102
|
}
|
|
75
103
|
},
|
|
76
104
|
};
|
|
105
|
+
/**
|
|
106
|
+
* Check if the getter is required by an implemented interface or overrides a base class member
|
|
107
|
+
*/
|
|
108
|
+
function isRequiredByInterfaceOrBaseClass(node, sourceCode) {
|
|
109
|
+
const parserServices = sourceCode.parserServices;
|
|
110
|
+
if (!parserServices ||
|
|
111
|
+
!parserServices.program ||
|
|
112
|
+
!parserServices.esTreeNodeToTSNodeMap) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const checker = parserServices.program.getTypeChecker();
|
|
116
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
117
|
+
if (!tsNode) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const symbol = checker.getSymbolAtLocation(tsNode.name);
|
|
121
|
+
if (!symbol ||
|
|
122
|
+
!tsNode.parent ||
|
|
123
|
+
!(ts.isClassDeclaration(tsNode.parent) ||
|
|
124
|
+
ts.isClassExpression(tsNode.parent))) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const classNode = tsNode.parent;
|
|
128
|
+
const classType = checker.getTypeAtLocation(classNode);
|
|
129
|
+
const classSymbol = classType.getSymbol();
|
|
130
|
+
if (!classSymbol) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const instanceType = checker.getDeclaredTypeOfSymbol(classSymbol);
|
|
134
|
+
const name = symbol.getName();
|
|
135
|
+
// Check base classes
|
|
136
|
+
const baseTypes = instanceType.getBaseTypes() || [];
|
|
137
|
+
for (const baseType of baseTypes) {
|
|
138
|
+
if (baseType.getProperty(name)) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Check interfaces
|
|
143
|
+
if (classNode.heritageClauses) {
|
|
144
|
+
for (const clause of classNode.heritageClauses) {
|
|
145
|
+
// Only check implemented interfaces as base classes are already checked via getBaseTypes()
|
|
146
|
+
if (clause.token !== ts.SyntaxKind.ImplementsKeyword) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
for (const typeNode of clause.types) {
|
|
150
|
+
const type = checker.getTypeAtLocation(typeNode);
|
|
151
|
+
if (type.getProperty(name)) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
77
159
|
/**
|
|
78
160
|
* Check if the node is a simple property access from a constructor parameter
|
|
79
161
|
* like this.settings.property or this.settings['property'] or this.settings.nested.deep.property
|
|
@@ -21,6 +21,7 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
|
|
|
21
21
|
defaultOptions: [],
|
|
22
22
|
create(context) {
|
|
23
23
|
const classInfoMap = new WeakMap();
|
|
24
|
+
const classStatsMap = new WeakMap();
|
|
24
25
|
function isFunctionLike(node) {
|
|
25
26
|
return (node?.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
26
27
|
node?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
@@ -412,6 +413,24 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
|
|
|
412
413
|
return {
|
|
413
414
|
'ClassDeclaration, ClassExpression'(node) {
|
|
414
415
|
collectClassInfo(node);
|
|
416
|
+
classStatsMap.set(node, { methods: new Map() });
|
|
417
|
+
},
|
|
418
|
+
'ClassDeclaration, ClassExpression:exit'(node) {
|
|
419
|
+
const stats = classStatsMap.get(node);
|
|
420
|
+
if (!stats) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
for (const [methodName, methodStats] of stats.methods) {
|
|
424
|
+
for (const [argIndex, argProperties] of methodStats.argStats) {
|
|
425
|
+
for (const [, propStats] of argProperties) {
|
|
426
|
+
if (propStats.callsWithProperty === methodStats.totalCalls) {
|
|
427
|
+
for (const violation of propStats.violations) {
|
|
428
|
+
reportAccess(methodName, violation.methodMeta, argIndex, violation.access);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
415
434
|
},
|
|
416
435
|
CallExpression(node) {
|
|
417
436
|
const methodName = getMethodNameFromCallee(node.callee);
|
|
@@ -442,6 +461,18 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
|
|
|
442
461
|
return;
|
|
443
462
|
}
|
|
444
463
|
}
|
|
464
|
+
const stats = classStatsMap.get(classNode);
|
|
465
|
+
if (!stats) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
let methodStats = stats.methods.get(methodName);
|
|
469
|
+
if (!methodStats) {
|
|
470
|
+
methodStats = { totalCalls: 0, argStats: new Map() };
|
|
471
|
+
stats.methods.set(methodName, methodStats);
|
|
472
|
+
}
|
|
473
|
+
const currentMethodStats = methodStats;
|
|
474
|
+
currentMethodStats.totalCalls++;
|
|
475
|
+
const seenInThisCall = new Set();
|
|
445
476
|
node.arguments.forEach((arg, index) => {
|
|
446
477
|
if (!arg) {
|
|
447
478
|
return;
|
|
@@ -449,7 +480,25 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
|
|
|
449
480
|
const targetNode = arg.type === utils_1.AST_NODE_TYPES.SpreadElement ? arg.argument : arg;
|
|
450
481
|
const accesses = collectThisAccesses(targetNode);
|
|
451
482
|
for (const access of accesses) {
|
|
452
|
-
|
|
483
|
+
const key = `${index}:${access.propertyName}`;
|
|
484
|
+
let argMap = currentMethodStats.argStats.get(index);
|
|
485
|
+
if (!argMap) {
|
|
486
|
+
argMap = new Map();
|
|
487
|
+
currentMethodStats.argStats.set(index, argMap);
|
|
488
|
+
}
|
|
489
|
+
let propStats = argMap.get(access.propertyName);
|
|
490
|
+
if (!propStats) {
|
|
491
|
+
propStats = { callsWithProperty: 0, violations: [] };
|
|
492
|
+
argMap.set(access.propertyName, propStats);
|
|
493
|
+
}
|
|
494
|
+
propStats.violations.push({
|
|
495
|
+
methodMeta: methodMeta,
|
|
496
|
+
access,
|
|
497
|
+
});
|
|
498
|
+
if (!seenInThisCall.has(key)) {
|
|
499
|
+
propStats.callsWithProperty++;
|
|
500
|
+
seenInThisCall.add(key);
|
|
501
|
+
}
|
|
453
502
|
}
|
|
454
503
|
});
|
|
455
504
|
},
|
|
@@ -274,7 +274,7 @@ exports.noUnmemoizedMemoWithoutProps = (0, createRule_1.createRule)({
|
|
|
274
274
|
if (!node.body) {
|
|
275
275
|
return;
|
|
276
276
|
}
|
|
277
|
-
const returnsJsx = ASTHelpers_1.ASTHelpers.returnsJSX(node.body);
|
|
277
|
+
const returnsJsx = ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context);
|
|
278
278
|
if (!returnsJsx) {
|
|
279
279
|
return;
|
|
280
280
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.noUnnecessaryDestructuringRename = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
6
7
|
const BINDABLE_IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
7
8
|
const RESERVED_BINDINGS = new Set([
|
|
8
9
|
'break',
|
|
@@ -113,11 +114,7 @@ exports.noUnnecessaryDestructuringRename = (0, createRule_1.createRule)({
|
|
|
113
114
|
const sourceCode = context.getSourceCode();
|
|
114
115
|
const candidates = [];
|
|
115
116
|
const getDeclaredVariables = (node) => {
|
|
116
|
-
|
|
117
|
-
if (typeof sourceCodeWithDeclarations.getDeclaredVariables === 'function') {
|
|
118
|
-
return sourceCodeWithDeclarations.getDeclaredVariables(node);
|
|
119
|
-
}
|
|
120
|
-
return context.getDeclaredVariables(node);
|
|
117
|
+
return ASTHelpers_1.ASTHelpers.getDeclaredVariables(context, node);
|
|
121
118
|
};
|
|
122
119
|
function collectDeclaredVariablesUpTree(node) {
|
|
123
120
|
const variables = [];
|