@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.
Files changed (35) hide show
  1. package/lib/index.js +7 -1
  2. package/lib/rules/array-methods-this-context.js +2 -2
  3. package/lib/rules/enforce-callback-memo.js +3 -3
  4. package/lib/rules/enforce-dynamic-firebase-imports.js +2 -2
  5. package/lib/rules/enforce-exported-function-types.js +88 -85
  6. package/lib/rules/enforce-firestore-doc-ref-generic.js +2 -2
  7. package/lib/rules/enforce-firestore-path-utils.js +2 -2
  8. package/lib/rules/enforce-firestore-set-merge.js +35 -4
  9. package/lib/rules/enforce-identifiable-firestore-type.js +2 -2
  10. package/lib/rules/enforce-memoize-async.js +2 -2
  11. package/lib/rules/enforce-mock-firestore.js +3 -3
  12. package/lib/rules/enforce-realtimedb-path-utils.js +1 -1
  13. package/lib/rules/enforce-safe-stringify.js +2 -2
  14. package/lib/rules/enforce-serializable-params.js +3 -3
  15. package/lib/rules/enforce-verb-noun-naming.js +39 -7
  16. package/lib/rules/no-async-array-filter.js +2 -2
  17. package/lib/rules/no-async-foreach.js +1 -1
  18. package/lib/rules/no-class-instance-destructuring.d.ts +1 -0
  19. package/lib/rules/no-class-instance-destructuring.js +95 -0
  20. package/lib/rules/no-compositing-layer-props.js +1 -1
  21. package/lib/rules/no-conditional-literals-in-jsx.js +1 -1
  22. package/lib/rules/no-entire-object-hook-deps.js +1 -1
  23. package/lib/rules/no-explicit-return-type.d.ts +1 -0
  24. package/lib/rules/no-explicit-return-type.js +5 -2
  25. package/lib/rules/no-filter-without-return.js +1 -1
  26. package/lib/rules/no-misused-switch-case.js +2 -2
  27. package/lib/rules/no-redundant-param-types.d.ts +2 -0
  28. package/lib/rules/no-redundant-param-types.js +129 -0
  29. package/lib/rules/no-unused-props.js +1 -1
  30. package/lib/rules/no-useless-fragment.js +1 -1
  31. package/lib/rules/prefer-destructuring-no-class.d.ts +8 -0
  32. package/lib/rules/prefer-destructuring-no-class.js +200 -0
  33. package/lib/rules/require-usememo-object-literals.js +1 -1
  34. package/lib/rules/semantic-function-prefixes.js +43 -4
  35. 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 expression is a sibling of raw text and must be wrapped in <div> or <span>',
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 types on functions',
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: 'An array filter callback with a block statement must contain a return statement',
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 in switch case. Use cascading cases instead.',
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,2 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const noRedundantParamTypes: TSESLint.RuleModule<"redundantParamType", [], TSESLint.RuleListener>;
@@ -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
- // Skip class getters
49
- if (node.parent?.type === utils_1.AST_NODE_TYPES.MethodDefinition && node.parent.kind === 'get') {
50
- return;
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 (functionName.toLowerCase().startsWith(prefix.toLowerCase())) {
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",