@blumintinc/eslint-plugin-blumint 1.4.0 → 1.5.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/lib/index.js +7 -1
- package/lib/rules/array-methods-this-context.js +2 -2
- package/lib/rules/enforce-callback-memo.js +3 -3
- package/lib/rules/enforce-dynamic-firebase-imports.js +2 -2
- package/lib/rules/enforce-exported-function-types.js +88 -85
- package/lib/rules/enforce-firestore-doc-ref-generic.js +2 -2
- package/lib/rules/enforce-firestore-path-utils.js +2 -2
- package/lib/rules/enforce-firestore-set-merge.js +35 -4
- package/lib/rules/enforce-identifiable-firestore-type.js +2 -2
- package/lib/rules/enforce-memoize-async.js +2 -2
- package/lib/rules/enforce-mock-firestore.js +3 -3
- package/lib/rules/enforce-realtimedb-path-utils.js +1 -1
- package/lib/rules/enforce-safe-stringify.js +2 -2
- package/lib/rules/enforce-serializable-params.js +3 -3
- package/lib/rules/enforce-verb-noun-naming.js +39 -7
- package/lib/rules/no-async-array-filter.js +2 -2
- package/lib/rules/no-async-foreach.js +1 -1
- package/lib/rules/no-class-instance-destructuring.d.ts +1 -0
- package/lib/rules/no-class-instance-destructuring.js +95 -0
- package/lib/rules/no-compositing-layer-props.js +1 -1
- package/lib/rules/no-conditional-literals-in-jsx.js +1 -1
- package/lib/rules/no-entire-object-hook-deps.js +1 -1
- package/lib/rules/no-explicit-return-type.d.ts +1 -0
- package/lib/rules/no-explicit-return-type.js +5 -2
- package/lib/rules/no-filter-without-return.js +1 -1
- package/lib/rules/no-misused-switch-case.js +2 -2
- package/lib/rules/no-redundant-param-types.d.ts +2 -0
- package/lib/rules/no-redundant-param-types.js +129 -0
- package/lib/rules/no-unused-props.js +1 -1
- package/lib/rules/no-useless-fragment.js +1 -1
- package/lib/rules/prefer-destructuring-no-class.d.ts +8 -0
- package/lib/rules/prefer-destructuring-no-class.js +200 -0
- package/lib/rules/require-usememo-object-literals.js +1 -1
- package/lib/rules/semantic-function-prefixes.js +43 -4
- package/package.json +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noClassInstanceDestructuring = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
exports.noClassInstanceDestructuring = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'no-class-instance-destructuring',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Disallow destructuring of class instances to prevent loss of `this` context',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
noClassInstanceDestructuring: 'Avoid destructuring class instances as it can lead to loss of `this` context. Use direct property access instead.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
function isClassInstance(node) {
|
|
23
|
+
// Check for new expressions
|
|
24
|
+
if (node.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
// Check for identifiers that might be class instances
|
|
28
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
29
|
+
const variableDef = context
|
|
30
|
+
.getScope()
|
|
31
|
+
.variables.find((variableDef) => variableDef.name === node.name);
|
|
32
|
+
if (variableDef?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
33
|
+
const init = variableDef.defs[0].node
|
|
34
|
+
.init;
|
|
35
|
+
return init?.type === utils_1.AST_NODE_TYPES.NewExpression;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
VariableDeclarator(node) {
|
|
42
|
+
if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
|
|
43
|
+
node.init &&
|
|
44
|
+
isClassInstance(node.init)) {
|
|
45
|
+
const objectPattern = node.id;
|
|
46
|
+
context.report({
|
|
47
|
+
node,
|
|
48
|
+
messageId: 'noClassInstanceDestructuring',
|
|
49
|
+
fix(fixer) {
|
|
50
|
+
const sourceCode = context.getSourceCode();
|
|
51
|
+
const properties = objectPattern.properties;
|
|
52
|
+
// Skip if there's no init expression
|
|
53
|
+
if (!node.init)
|
|
54
|
+
return null;
|
|
55
|
+
// For single property, use simple replacement
|
|
56
|
+
if (properties.length === 1) {
|
|
57
|
+
const prop = properties[0];
|
|
58
|
+
if (prop.type === utils_1.AST_NODE_TYPES.Property) {
|
|
59
|
+
const key = prop.key.type === utils_1.AST_NODE_TYPES.Identifier
|
|
60
|
+
? prop.key.name
|
|
61
|
+
: sourceCode.getText(prop.key);
|
|
62
|
+
const value = prop.value.type === utils_1.AST_NODE_TYPES.Identifier
|
|
63
|
+
? prop.value.name
|
|
64
|
+
: sourceCode.getText(prop.value);
|
|
65
|
+
const initText = sourceCode.getText(node.init);
|
|
66
|
+
return fixer.replaceText(node, `${value} = ${initText}.${key}`);
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// For multiple properties, create multiple declarations
|
|
71
|
+
const declarations = properties
|
|
72
|
+
.filter((prop) => prop.type === utils_1.AST_NODE_TYPES.Property)
|
|
73
|
+
.map((prop) => {
|
|
74
|
+
const key = prop.key.type === utils_1.AST_NODE_TYPES.Identifier
|
|
75
|
+
? prop.key.name
|
|
76
|
+
: sourceCode.getText(prop.key);
|
|
77
|
+
const value = prop.value.type === utils_1.AST_NODE_TYPES.Identifier
|
|
78
|
+
? prop.value.name
|
|
79
|
+
: sourceCode.getText(prop.value);
|
|
80
|
+
const initText = sourceCode.getText(node.init);
|
|
81
|
+
return `${value} = ${initText}.${key}`;
|
|
82
|
+
})
|
|
83
|
+
.join(';\nconst ');
|
|
84
|
+
// Only apply the fix if we have valid declarations
|
|
85
|
+
if (!declarations)
|
|
86
|
+
return null;
|
|
87
|
+
return fixer.replaceText(node, declarations);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=no-class-instance-destructuring.js.map
|
|
@@ -37,7 +37,7 @@ exports.noCompositingLayerProps = (0, createRule_1.createRule)({
|
|
|
37
37
|
meta: {
|
|
38
38
|
type: 'suggestion',
|
|
39
39
|
docs: {
|
|
40
|
-
description: 'Warn when using CSS properties that trigger compositing layers',
|
|
40
|
+
description: 'Warn when using CSS properties that trigger compositing layers, which can impact performance. Properties like transform, opacity, filter, and will-change create new GPU layers. While sometimes beneficial for animations, excessive layer creation can increase memory usage and hurt performance. Consider alternatives or explicitly document intentional layer promotion.',
|
|
41
41
|
recommended: 'error',
|
|
42
42
|
},
|
|
43
43
|
schema: [],
|
|
@@ -14,7 +14,7 @@ exports.noConditionalLiteralsInJsx = (0, createRule_1.createRule)({
|
|
|
14
14
|
},
|
|
15
15
|
schema: [],
|
|
16
16
|
messages: {
|
|
17
|
-
unexpected: 'Conditional
|
|
17
|
+
unexpected: 'Conditional text literals must be wrapped in a container element when next to other text. Instead of `<div>text {condition && "more text"}</div>`, use `<div>text <span>{condition && "more text"}</span></div>` to prevent React hydration issues.',
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
defaultOptions: [],
|
|
@@ -136,7 +136,7 @@ exports.noEntireObjectHookDeps = (0, createRule_1.createRule)({
|
|
|
136
136
|
meta: {
|
|
137
137
|
type: 'suggestion',
|
|
138
138
|
docs: {
|
|
139
|
-
description: 'Avoid using entire objects in React hook dependency arrays when only specific fields are used. Requires TypeScript and `parserOptions.project` to be configured.',
|
|
139
|
+
description: 'Avoid using entire objects in React hook dependency arrays when only specific fields are used, as this can cause unnecessary re-renders. When a hook only uses obj.name but obj is in the deps array, any change to obj.age will trigger the hook. Use individual fields (obj.name) instead of the entire object. Requires TypeScript and `parserOptions.project` to be configured.',
|
|
140
140
|
recommended: 'error',
|
|
141
141
|
requiresTypeChecking: true,
|
|
142
142
|
},
|
|
@@ -6,6 +6,7 @@ type Options = [
|
|
|
6
6
|
allowInterfaceMethodSignatures?: boolean;
|
|
7
7
|
allowAbstractMethodSignatures?: boolean;
|
|
8
8
|
allowDtsFiles?: boolean;
|
|
9
|
+
allowFirestoreFunctionFiles?: boolean;
|
|
9
10
|
}
|
|
10
11
|
];
|
|
11
12
|
export declare const noExplicitReturnType: TSESLint.RuleModule<'noExplicitReturnType', Options>;
|
|
@@ -9,6 +9,7 @@ const defaultOptions = {
|
|
|
9
9
|
allowInterfaceMethodSignatures: true,
|
|
10
10
|
allowAbstractMethodSignatures: true,
|
|
11
11
|
allowDtsFiles: true,
|
|
12
|
+
allowFirestoreFunctionFiles: true,
|
|
12
13
|
};
|
|
13
14
|
function isRecursiveFunction(node) {
|
|
14
15
|
const functionName = node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration
|
|
@@ -91,7 +92,7 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
91
92
|
meta: {
|
|
92
93
|
type: 'suggestion',
|
|
93
94
|
docs: {
|
|
94
|
-
description: 'Disallow explicit return
|
|
95
|
+
description: 'Disallow explicit return type annotations on functions when TypeScript can infer them. This reduces code verbosity and maintenance burden while leveraging TypeScript\'s powerful type inference. Exceptions are made for recursive functions, overloaded functions, interface methods, and abstract methods where explicit types improve clarity.',
|
|
95
96
|
recommended: 'error',
|
|
96
97
|
requiresTypeChecking: false,
|
|
97
98
|
extendsBaseRule: false,
|
|
@@ -106,6 +107,7 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
106
107
|
allowInterfaceMethodSignatures: { type: 'boolean' },
|
|
107
108
|
allowAbstractMethodSignatures: { type: 'boolean' },
|
|
108
109
|
allowDtsFiles: { type: 'boolean' },
|
|
110
|
+
allowFirestoreFunctionFiles: { type: 'boolean' },
|
|
109
111
|
},
|
|
110
112
|
additionalProperties: false,
|
|
111
113
|
},
|
|
@@ -118,7 +120,8 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
118
120
|
create(context, [options]) {
|
|
119
121
|
const mergedOptions = { ...defaultOptions, ...options };
|
|
120
122
|
const filename = context.getFilename();
|
|
121
|
-
if (mergedOptions.allowDtsFiles && filename.endsWith('.d.ts'))
|
|
123
|
+
if ((mergedOptions.allowDtsFiles && filename.endsWith('.d.ts')) ||
|
|
124
|
+
(mergedOptions.allowFirestoreFunctionFiles && filename.endsWith('.f.ts'))) {
|
|
122
125
|
return {};
|
|
123
126
|
}
|
|
124
127
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -33,7 +33,7 @@ exports.noFilterWithoutReturn = (0, createRule_1.createRule)({
|
|
|
33
33
|
},
|
|
34
34
|
schema: [],
|
|
35
35
|
messages: {
|
|
36
|
-
unexpected: '
|
|
36
|
+
unexpected: 'Array.filter callbacks with block statements must contain a return statement. Instead of `array.filter(x => { doSomething(x); })`, use `array.filter(x => { doSomething(x); return someCondition; })` or use implicit return `array.filter(x => someCondition)`.',
|
|
37
37
|
},
|
|
38
38
|
},
|
|
39
39
|
defaultOptions: [],
|
|
@@ -7,12 +7,12 @@ exports.noMisusedSwitchCase = (0, createRule_1.createRule)({
|
|
|
7
7
|
meta: {
|
|
8
8
|
type: 'problem',
|
|
9
9
|
docs: {
|
|
10
|
-
description: 'Prevent misuse of logical OR in switch case statements',
|
|
10
|
+
description: 'Prevent misuse of logical OR (||) in switch case statements, which can lead to confusing and error-prone code. Instead of using OR operators in case expressions, use multiple case statements in sequence to handle multiple values. This improves code readability and follows the standard switch-case pattern.',
|
|
11
11
|
recommended: 'error',
|
|
12
12
|
},
|
|
13
13
|
schema: [],
|
|
14
14
|
messages: {
|
|
15
|
-
noMisusedSwitchCase: 'Avoid using logical OR
|
|
15
|
+
noMisusedSwitchCase: 'Avoid using logical OR (||) in switch case statements. Instead of `case x || y:`, use cascading cases like `case x: case y:`.',
|
|
16
16
|
},
|
|
17
17
|
},
|
|
18
18
|
defaultOptions: [],
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noRedundantParamTypes = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
function isIdentifierWithTypeAnnotation(node) {
|
|
7
|
+
return (node.type === utils_1.AST_NODE_TYPES.Identifier && node.typeAnnotation !== undefined);
|
|
8
|
+
}
|
|
9
|
+
function isRestElementWithTypeAnnotation(node) {
|
|
10
|
+
return (node.type === utils_1.AST_NODE_TYPES.RestElement &&
|
|
11
|
+
node.typeAnnotation !== undefined);
|
|
12
|
+
}
|
|
13
|
+
function isObjectPatternWithTypeAnnotation(node) {
|
|
14
|
+
return (node.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
|
|
15
|
+
node.typeAnnotation !== undefined);
|
|
16
|
+
}
|
|
17
|
+
function isArrayPatternWithTypeAnnotation(node) {
|
|
18
|
+
return (node.type === utils_1.AST_NODE_TYPES.ArrayPattern &&
|
|
19
|
+
node.typeAnnotation !== undefined);
|
|
20
|
+
}
|
|
21
|
+
function isAssignmentPatternWithTypeAnnotation(node) {
|
|
22
|
+
return (node.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
|
|
23
|
+
node.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
24
|
+
node.left.typeAnnotation !== undefined);
|
|
25
|
+
}
|
|
26
|
+
function removeTypeAnnotation(fixer, typeAnnotation, sourceCode) {
|
|
27
|
+
const typeStart = typeAnnotation.range[0];
|
|
28
|
+
const typeEnd = typeAnnotation.range[1];
|
|
29
|
+
// Check if there's a question mark before the type annotation
|
|
30
|
+
const hasQuestionMark = typeStart > 0 && sourceCode.getText().charAt(typeStart - 1) === '?';
|
|
31
|
+
const startPos = hasQuestionMark ? typeStart - 1 : typeStart;
|
|
32
|
+
return fixer.removeRange([startPos, typeEnd]);
|
|
33
|
+
}
|
|
34
|
+
function hasRedundantTypeAnnotation(node) {
|
|
35
|
+
const parent = node.parent;
|
|
36
|
+
if (!parent)
|
|
37
|
+
return false;
|
|
38
|
+
// Check variable declarations
|
|
39
|
+
if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
40
|
+
parent.id.typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// Check class property assignments
|
|
44
|
+
if (parent.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
|
|
45
|
+
parent.typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
// Check assignments
|
|
49
|
+
if (parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
|
|
50
|
+
parent.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
51
|
+
parent.left.typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
exports.noRedundantParamTypes = (0, createRule_1.createRule)({
|
|
57
|
+
name: 'no-redundant-param-types',
|
|
58
|
+
meta: {
|
|
59
|
+
type: 'suggestion',
|
|
60
|
+
docs: {
|
|
61
|
+
description: 'Disallow redundant parameter type annotations',
|
|
62
|
+
recommended: 'error',
|
|
63
|
+
},
|
|
64
|
+
fixable: 'code',
|
|
65
|
+
schema: [],
|
|
66
|
+
messages: {
|
|
67
|
+
redundantParamType: 'Parameter type annotation is redundant',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
defaultOptions: [],
|
|
71
|
+
create(context) {
|
|
72
|
+
return {
|
|
73
|
+
ArrowFunctionExpression(node) {
|
|
74
|
+
if (!hasRedundantTypeAnnotation(node))
|
|
75
|
+
return;
|
|
76
|
+
const params = node.params;
|
|
77
|
+
params.forEach((param) => {
|
|
78
|
+
if (isIdentifierWithTypeAnnotation(param)) {
|
|
79
|
+
context.report({
|
|
80
|
+
node: param,
|
|
81
|
+
messageId: 'redundantParamType',
|
|
82
|
+
fix(fixer) {
|
|
83
|
+
return removeTypeAnnotation(fixer, param.typeAnnotation, context.getSourceCode());
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else if (isRestElementWithTypeAnnotation(param)) {
|
|
88
|
+
context.report({
|
|
89
|
+
node: param,
|
|
90
|
+
messageId: 'redundantParamType',
|
|
91
|
+
fix(fixer) {
|
|
92
|
+
return removeTypeAnnotation(fixer, param.typeAnnotation, context.getSourceCode());
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else if (isObjectPatternWithTypeAnnotation(param)) {
|
|
97
|
+
context.report({
|
|
98
|
+
node: param,
|
|
99
|
+
messageId: 'redundantParamType',
|
|
100
|
+
fix(fixer) {
|
|
101
|
+
return removeTypeAnnotation(fixer, param.typeAnnotation, context.getSourceCode());
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
else if (isArrayPatternWithTypeAnnotation(param)) {
|
|
106
|
+
context.report({
|
|
107
|
+
node: param,
|
|
108
|
+
messageId: 'redundantParamType',
|
|
109
|
+
fix(fixer) {
|
|
110
|
+
return removeTypeAnnotation(fixer, param.typeAnnotation, context.getSourceCode());
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
else if (isAssignmentPatternWithTypeAnnotation(param)) {
|
|
115
|
+
const { left } = param;
|
|
116
|
+
context.report({
|
|
117
|
+
node: param,
|
|
118
|
+
messageId: 'redundantParamType',
|
|
119
|
+
fix(fixer) {
|
|
120
|
+
return removeTypeAnnotation(fixer, left.typeAnnotation, context.getSourceCode());
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=no-redundant-param-types.js.map
|
|
@@ -13,7 +13,7 @@ exports.noUnusedProps = (0, createRule_1.createRule)({
|
|
|
13
13
|
},
|
|
14
14
|
schema: [],
|
|
15
15
|
messages: {
|
|
16
|
-
unusedProp: 'Prop "{{propName}}" is defined in type but not used in component',
|
|
16
|
+
unusedProp: 'Prop "{{propName}}" is defined in the Props type but not used in the component. Either use the prop in your component or remove it from the Props type. If you need to forward all props, use a rest spread operator: `const MyComponent = ({ usedProp, ...rest }: Props) => ...`',
|
|
17
17
|
},
|
|
18
18
|
fixable: 'code',
|
|
19
19
|
},
|
|
@@ -38,7 +38,7 @@ exports.noUselessFragment = {
|
|
|
38
38
|
recommended: 'error',
|
|
39
39
|
},
|
|
40
40
|
messages: {
|
|
41
|
-
noUselessFragment: 'React fragment is unnecessary when wrapping a single child',
|
|
41
|
+
noUselessFragment: 'React fragment is unnecessary when wrapping a single child. Instead of `<>{"text"}</> or <Fragment>{"text"}</Fragment>`, use just `{"text"}`. Fragments are only needed when returning multiple elements.',
|
|
42
42
|
},
|
|
43
43
|
schema: [],
|
|
44
44
|
fixable: 'code',
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type Options = [
|
|
2
|
+
{
|
|
3
|
+
object?: boolean;
|
|
4
|
+
enforceForRenamedProperties?: boolean;
|
|
5
|
+
}
|
|
6
|
+
];
|
|
7
|
+
export declare const preferDestructuringNoClass: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"preferDestructuring", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preferDestructuringNoClass = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const defaultOptions = [
|
|
7
|
+
{
|
|
8
|
+
object: true,
|
|
9
|
+
enforceForRenamedProperties: false,
|
|
10
|
+
},
|
|
11
|
+
];
|
|
12
|
+
function isClassInstance(node, context) {
|
|
13
|
+
// Check if node is a MemberExpression
|
|
14
|
+
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
15
|
+
const object = node.object;
|
|
16
|
+
// If object is a NewExpression, it's a class instance
|
|
17
|
+
if (object.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
// If object is an identifier, check if it refers to a class instance
|
|
21
|
+
if (object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
22
|
+
const variable = object.name;
|
|
23
|
+
const scope = context.getScope();
|
|
24
|
+
const ref = scope.references.find((ref) => ref.identifier.name === variable);
|
|
25
|
+
if (ref?.resolved?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
26
|
+
const init = ref.resolved.defs[0].node.init;
|
|
27
|
+
return init?.type === utils_1.AST_NODE_TYPES.NewExpression;
|
|
28
|
+
}
|
|
29
|
+
// Check if the identifier refers to a class (not an instance)
|
|
30
|
+
if (ref?.resolved?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.ClassDeclaration) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Recursively check if parent object is a class instance
|
|
35
|
+
if (object.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
36
|
+
return isClassInstance(object, context);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
function isStaticClassMember(node, context) {
|
|
42
|
+
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
43
|
+
const object = node.object;
|
|
44
|
+
if (object.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
45
|
+
const variable = object.name;
|
|
46
|
+
const scope = context.getScope();
|
|
47
|
+
const ref = scope.references.find((ref) => ref.identifier.name === variable);
|
|
48
|
+
return (ref?.resolved?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.ClassDeclaration);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if the property name matches the variable name in an assignment
|
|
55
|
+
*/
|
|
56
|
+
function isMatchingPropertyName(propertyNode, variableName) {
|
|
57
|
+
if (propertyNode.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
58
|
+
return propertyNode.name === variableName;
|
|
59
|
+
}
|
|
60
|
+
if (propertyNode.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
61
|
+
return propertyNode.value === variableName;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get the property text for destructuring
|
|
67
|
+
*/
|
|
68
|
+
function getPropertyText(property, computed, sourceCode) {
|
|
69
|
+
if (computed) {
|
|
70
|
+
return sourceCode.getText(property);
|
|
71
|
+
}
|
|
72
|
+
if (property.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
73
|
+
return property.name;
|
|
74
|
+
}
|
|
75
|
+
if (property.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
76
|
+
return String(property.value);
|
|
77
|
+
}
|
|
78
|
+
// For any other type, use the source text
|
|
79
|
+
return sourceCode.getText(property);
|
|
80
|
+
}
|
|
81
|
+
exports.preferDestructuringNoClass = (0, createRule_1.createRule)({
|
|
82
|
+
name: 'prefer-destructuring-no-class',
|
|
83
|
+
meta: {
|
|
84
|
+
type: 'suggestion',
|
|
85
|
+
docs: {
|
|
86
|
+
description: 'Enforce destructuring when accessing object properties, except for class instances',
|
|
87
|
+
recommended: 'error',
|
|
88
|
+
},
|
|
89
|
+
fixable: 'code',
|
|
90
|
+
schema: [
|
|
91
|
+
{
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
object: {
|
|
95
|
+
type: 'boolean',
|
|
96
|
+
default: true,
|
|
97
|
+
},
|
|
98
|
+
enforceForRenamedProperties: {
|
|
99
|
+
type: 'boolean',
|
|
100
|
+
default: false,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
additionalProperties: false,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
messages: {
|
|
107
|
+
preferDestructuring: 'Use destructuring instead of accessing the property directly.',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
defaultOptions,
|
|
111
|
+
create(context) {
|
|
112
|
+
const options = {
|
|
113
|
+
object: defaultOptions[0].object,
|
|
114
|
+
enforceForRenamedProperties: defaultOptions[0].enforceForRenamedProperties,
|
|
115
|
+
...context.options[0],
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Check if destructuring should be used for this node
|
|
119
|
+
*/
|
|
120
|
+
function shouldUseDestructuring(node, leftNode) {
|
|
121
|
+
// Skip if this is a class instance or static class member
|
|
122
|
+
if (isClassInstance(node, context) ||
|
|
123
|
+
isStaticClassMember(node, context)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
// Check object destructuring
|
|
127
|
+
if (options.object) {
|
|
128
|
+
if (options.enforceForRenamedProperties) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
// Only suggest destructuring when property name matches variable name
|
|
132
|
+
if (leftNode.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
133
|
+
return isMatchingPropertyName(node.property, leftNode.name);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
VariableDeclarator(node) {
|
|
140
|
+
// Skip if variable is declared without assignment or if init is not a MemberExpression
|
|
141
|
+
if (!node.init)
|
|
142
|
+
return;
|
|
143
|
+
if (node.init.type !== utils_1.AST_NODE_TYPES.MemberExpression)
|
|
144
|
+
return;
|
|
145
|
+
if (shouldUseDestructuring(node.init, node.id)) {
|
|
146
|
+
const sourceCode = context.getSourceCode();
|
|
147
|
+
const objectText = sourceCode.getText(node.init.object);
|
|
148
|
+
const propertyText = getPropertyText(node.init.property, node.init.computed, sourceCode);
|
|
149
|
+
context.report({
|
|
150
|
+
node,
|
|
151
|
+
messageId: 'preferDestructuring',
|
|
152
|
+
fix(fixer) {
|
|
153
|
+
// Get the variable declaration kind (const, let, var)
|
|
154
|
+
const parentNode = node.parent;
|
|
155
|
+
if (!parentNode ||
|
|
156
|
+
parentNode.type !== utils_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const kind = parentNode.kind;
|
|
160
|
+
// Handle renamed properties
|
|
161
|
+
if (options.enforceForRenamedProperties &&
|
|
162
|
+
node.id.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
163
|
+
node.init &&
|
|
164
|
+
node.init.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
165
|
+
!isMatchingPropertyName(node.init.property, node.id.name)) {
|
|
166
|
+
return fixer.replaceText(parentNode, `${kind} { ${propertyText}: ${node.id.name} } = ${objectText};`);
|
|
167
|
+
}
|
|
168
|
+
return fixer.replaceText(parentNode, `${kind} { ${propertyText} } = ${objectText};`);
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
AssignmentExpression(node) {
|
|
174
|
+
if (node.operator === '=' &&
|
|
175
|
+
node.right.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
176
|
+
if (shouldUseDestructuring(node.right, node.left)) {
|
|
177
|
+
const sourceCode = context.getSourceCode();
|
|
178
|
+
const objectText = sourceCode.getText(node.right.object);
|
|
179
|
+
const propertyText = getPropertyText(node.right.property, node.right.computed, sourceCode);
|
|
180
|
+
context.report({
|
|
181
|
+
node,
|
|
182
|
+
messageId: 'preferDestructuring',
|
|
183
|
+
fix(fixer) {
|
|
184
|
+
// Handle renamed properties
|
|
185
|
+
if (options.enforceForRenamedProperties &&
|
|
186
|
+
node.left.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
187
|
+
node.right.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
188
|
+
!isMatchingPropertyName(node.right.property, node.left.name)) {
|
|
189
|
+
return fixer.replaceText(node, `({ ${propertyText}: ${node.left.name} } = ${objectText})`);
|
|
190
|
+
}
|
|
191
|
+
return fixer.replaceText(node, `({ ${propertyText} } = ${objectText})`);
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
//# sourceMappingURL=prefer-destructuring-no-class.js.map
|
|
@@ -7,7 +7,7 @@ exports.requireUseMemoObjectLiterals = (0, createRule_1.createRule)({
|
|
|
7
7
|
meta: {
|
|
8
8
|
type: 'suggestion',
|
|
9
9
|
docs: {
|
|
10
|
-
description: 'Enforce using useMemo for inline object literals passed as props to JSX components',
|
|
10
|
+
description: 'Enforce using useMemo for inline object/array literals passed as props to JSX components to prevent unnecessary re-renders. When object/array literals are defined inline in JSX, they create new references on every render, causing child components to re-render even if the values haven\'t changed. Wrap them in useMemo to maintain referential equality.',
|
|
11
11
|
recommended: 'error',
|
|
12
12
|
},
|
|
13
13
|
messages: {
|
|
@@ -27,6 +27,40 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
|
|
|
27
27
|
},
|
|
28
28
|
defaultOptions: [],
|
|
29
29
|
create(context) {
|
|
30
|
+
function checkMethodName(node) {
|
|
31
|
+
// Skip getters and setters
|
|
32
|
+
if (node.kind === 'get' || node.kind === 'set') {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const methodName = node.key.type === utils_1.AST_NODE_TYPES.Identifier ? node.key.name : '';
|
|
36
|
+
if (!methodName)
|
|
37
|
+
return;
|
|
38
|
+
// Skip if method starts with 'is' (boolean check methods are okay)
|
|
39
|
+
if (methodName.startsWith('is'))
|
|
40
|
+
return;
|
|
41
|
+
// Extract first word from PascalCase/camelCase
|
|
42
|
+
let firstWord = methodName;
|
|
43
|
+
for (let i = 1; i < methodName.length; i++) {
|
|
44
|
+
if (methodName[i] >= 'A' && methodName[i] <= 'Z') {
|
|
45
|
+
firstWord = methodName.substring(0, i);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Check for disallowed prefixes
|
|
50
|
+
for (const prefix of DISALLOWED_PREFIXES) {
|
|
51
|
+
if (firstWord.toLowerCase() === prefix.toLowerCase()) {
|
|
52
|
+
context.report({
|
|
53
|
+
node: node.key,
|
|
54
|
+
messageId: 'avoidGenericPrefix',
|
|
55
|
+
data: {
|
|
56
|
+
prefix,
|
|
57
|
+
alternatives: SUGGESTED_ALTERNATIVES[prefix].join(', '),
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
30
64
|
function checkFunctionName(node) {
|
|
31
65
|
// Skip anonymous functions
|
|
32
66
|
if (!node.id && node.parent?.type !== utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
@@ -45,13 +79,17 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
|
|
|
45
79
|
// Skip if function starts with 'is' (boolean check functions are okay)
|
|
46
80
|
if (functionName.startsWith('is'))
|
|
47
81
|
return;
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
82
|
+
// Extract first word from PascalCase/camelCase
|
|
83
|
+
let firstWord = functionName;
|
|
84
|
+
for (let i = 1; i < functionName.length; i++) {
|
|
85
|
+
if (functionName[i] >= 'A' && functionName[i] <= 'Z') {
|
|
86
|
+
firstWord = functionName.substring(0, i);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
51
89
|
}
|
|
52
90
|
// Check for disallowed prefixes
|
|
53
91
|
for (const prefix of DISALLOWED_PREFIXES) {
|
|
54
|
-
if (
|
|
92
|
+
if (firstWord.toLowerCase() === prefix.toLowerCase()) {
|
|
55
93
|
context.report({
|
|
56
94
|
node: node.id || node,
|
|
57
95
|
messageId: 'avoidGenericPrefix',
|
|
@@ -68,6 +106,7 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
|
|
|
68
106
|
FunctionDeclaration: checkFunctionName,
|
|
69
107
|
FunctionExpression: checkFunctionName,
|
|
70
108
|
ArrowFunctionExpression: checkFunctionName,
|
|
109
|
+
MethodDefinition: checkMethodName,
|
|
71
110
|
};
|
|
72
111
|
},
|
|
73
112
|
});
|