@blumintinc/eslint-plugin-blumint 1.9.1 → 1.11.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 (72) hide show
  1. package/README.md +99 -83
  2. package/lib/index.js +16 -1
  3. package/lib/rules/class-methods-read-top-to-bottom.js +10 -0
  4. package/lib/rules/consistent-callback-naming.js +20 -16
  5. package/lib/rules/enforce-assert-throws.js +10 -5
  6. package/lib/rules/enforce-boolean-naming-prefixes.d.ts +7 -0
  7. package/lib/rules/enforce-boolean-naming-prefixes.js +462 -0
  8. package/lib/rules/enforce-callable-types.d.ts +1 -1
  9. package/lib/rules/enforce-callable-types.js +13 -13
  10. package/lib/rules/enforce-callback-memo.js +6 -3
  11. package/lib/rules/enforce-centralized-mock-firestore.js +8 -6
  12. package/lib/rules/enforce-css-media-queries.js +3 -3
  13. package/lib/rules/enforce-dynamic-file-naming.d.ts +3 -0
  14. package/lib/rules/enforce-dynamic-file-naming.js +82 -0
  15. package/lib/rules/enforce-dynamic-imports.js +1 -1
  16. package/lib/rules/enforce-firestore-doc-ref-generic.js +16 -12
  17. package/lib/rules/enforce-firestore-facade.js +7 -3
  18. package/lib/rules/enforce-firestore-path-utils.js +5 -2
  19. package/lib/rules/enforce-id-capitalization.js +59 -0
  20. package/lib/rules/enforce-identifiable-firestore-type.js +17 -10
  21. package/lib/rules/enforce-memoize-async.js +2 -2
  22. package/lib/rules/enforce-positive-naming.js +71 -27
  23. package/lib/rules/enforce-props-argument-name.js +31 -9
  24. package/lib/rules/enforce-realtimedb-path-utils.js +2 -1
  25. package/lib/rules/enforce-singular-type-names.js +4 -1
  26. package/lib/rules/enforce-timestamp-now.js +3 -4
  27. package/lib/rules/enforce-verb-noun-naming.js +18 -8
  28. package/lib/rules/ensure-pointer-events-none.js +20 -11
  29. package/lib/rules/extract-global-constants.js +25 -10
  30. package/lib/rules/fast-deep-equal-over-microdiff.d.ts +3 -0
  31. package/lib/rules/fast-deep-equal-over-microdiff.js +182 -0
  32. package/lib/rules/key-only-outermost-element.js +13 -7
  33. package/lib/rules/no-always-true-false-conditions.js +189 -5
  34. package/lib/rules/no-async-array-filter.js +1 -1
  35. package/lib/rules/no-circular-references.js +121 -59
  36. package/lib/rules/no-complex-cloud-params.js +4 -2
  37. package/lib/rules/no-entire-object-hook-deps.js +113 -29
  38. package/lib/rules/no-explicit-return-type.js +3 -2
  39. package/lib/rules/no-firestore-jest-mock.js +1 -1
  40. package/lib/rules/no-firestore-object-arrays.js +2 -1
  41. package/lib/rules/no-hungarian.js +18 -1
  42. package/lib/rules/no-jsx-in-hooks.js +4 -3
  43. package/lib/rules/no-margin-properties.d.ts +1 -0
  44. package/lib/rules/no-margin-properties.js +314 -0
  45. package/lib/rules/no-mixed-firestore-transactions.js +8 -4
  46. package/lib/rules/no-mock-firebase-admin.js +6 -1
  47. package/lib/rules/no-object-values-on-strings.js +28 -10
  48. package/lib/rules/no-type-assertion-returns.js +47 -6
  49. package/lib/rules/no-unnecessary-destructuring.js +1 -1
  50. package/lib/rules/no-unnecessary-verb-suffix.js +0 -31
  51. package/lib/rules/no-unused-props.js +10 -7
  52. package/lib/rules/no-unused-usestate.js +19 -6
  53. package/lib/rules/omit-index-html.js +1 -1
  54. package/lib/rules/prefer-batch-operations.js +19 -6
  55. package/lib/rules/prefer-clone-deep.js +16 -11
  56. package/lib/rules/prefer-destructuring-no-class.js +19 -0
  57. package/lib/rules/prefer-fragment-component.js +19 -16
  58. package/lib/rules/prefer-global-router-state-key.js +6 -4
  59. package/lib/rules/prefer-settings-object.js +1 -1
  60. package/lib/rules/prefer-usecallback-over-usememo-for-functions.d.ts +8 -0
  61. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +180 -0
  62. package/lib/rules/react-usememo-should-be-component.js +139 -17
  63. package/lib/rules/require-hooks-default-params.js +25 -15
  64. package/lib/rules/require-usememo-object-literals.js +3 -2
  65. package/lib/rules/semantic-function-prefixes.js +15 -3
  66. package/lib/rules/sync-onwrite-name-func.js +5 -3
  67. package/lib/utils/ASTHelpers.js +6 -2
  68. package/lib/utils/graph/ClassGraphBuilder.js +4 -1
  69. package/lib/utils/graph/ClassGraphSorterReadability.js +3 -1
  70. package/package.json +3 -3
  71. package/lib/rules/require-image-overlayed.d.ts +0 -7
  72. package/lib/rules/require-image-overlayed.js +0 -82
@@ -25,7 +25,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
25
25
  function hasAllOptionalProperties(typeNode) {
26
26
  // Handle type literals directly
27
27
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
28
- return typeNode.members.every(member => {
28
+ return typeNode.members.every((member) => {
29
29
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
30
30
  return false;
31
31
  }
@@ -39,7 +39,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
39
39
  return false;
40
40
  }
41
41
  const scope = context.getScope();
42
- const variable = scope.variables.find(v => v.name === typeName.name);
42
+ const variable = scope.variables.find((v) => v.name === typeName.name);
43
43
  if (!variable || !variable.defs[0]?.node) {
44
44
  // If we can't find the type definition, assume it's a type with required properties
45
45
  // This handles cases where the type is imported from another module
@@ -50,7 +50,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
50
50
  return hasAllOptionalProperties(def.typeAnnotation);
51
51
  }
52
52
  else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
53
- return def.body.body.every(member => {
53
+ return def.body.body.every((member) => {
54
54
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
55
55
  return false;
56
56
  }
@@ -68,7 +68,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
68
68
  }
69
69
  // Handle interface declarations
70
70
  if (typeNode.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
71
- return typeNode.body.body.every(member => {
71
+ return typeNode.body.body.every((member) => {
72
72
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
73
73
  return false;
74
74
  }
@@ -106,13 +106,14 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
106
106
  return;
107
107
  }
108
108
  // Check if the parameter has a type annotation
109
- if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern && param.typeAnnotation) {
109
+ if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern &&
110
+ param.typeAnnotation) {
110
111
  const typeAnnotation = param.typeAnnotation.typeAnnotation;
111
112
  if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
112
113
  const typeName = typeAnnotation.typeName;
113
114
  if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
114
115
  const scope = context.getScope();
115
- const variable = scope.variables.find(v => v.name === typeName.name);
116
+ const variable = scope.variables.find((v) => v.name === typeName.name);
116
117
  if (variable && variable.defs[0]?.node) {
117
118
  const def = variable.defs[0].node;
118
119
  if (def.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
@@ -121,14 +122,16 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
121
122
  node: param,
122
123
  messageId: 'requireDefaultParams',
123
124
  fix(fixer) {
124
- const paramText = context.getSourceCode().getText(param);
125
+ const paramText = context
126
+ .getSourceCode()
127
+ .getText(param);
125
128
  return fixer.replaceText(param, `${paramText} = {}`);
126
129
  },
127
130
  });
128
131
  }
129
132
  }
130
133
  else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
131
- if (def.body.body.every(member => {
134
+ if (def.body.body.every((member) => {
132
135
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
133
136
  return false;
134
137
  }
@@ -138,7 +141,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
138
141
  node: param,
139
142
  messageId: 'requireDefaultParams',
140
143
  fix(fixer) {
141
- const paramText = context.getSourceCode().getText(param);
144
+ const paramText = context
145
+ .getSourceCode()
146
+ .getText(param);
142
147
  return fixer.replaceText(param, `${paramText} = {}`);
143
148
  },
144
149
  });
@@ -148,8 +153,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
148
153
  else {
149
154
  // If we can't find the type definition, check if it's defined in the same file
150
155
  const program = context.getSourceCode().ast;
151
- const typeDefinitions = program.body.filter(node => {
152
- if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration || node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
156
+ const typeDefinitions = program.body.filter((node) => {
157
+ if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration ||
158
+ node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
153
159
  if (node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
154
160
  return node.id.name === typeName.name;
155
161
  }
@@ -167,14 +173,16 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
167
173
  node: param,
168
174
  messageId: 'requireDefaultParams',
169
175
  fix(fixer) {
170
- const paramText = context.getSourceCode().getText(param);
176
+ const paramText = context
177
+ .getSourceCode()
178
+ .getText(param);
171
179
  return fixer.replaceText(param, `${paramText} = {}`);
172
180
  },
173
181
  });
174
182
  }
175
183
  }
176
184
  else if (def.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
177
- if (def.body.body.every(member => {
185
+ if (def.body.body.every((member) => {
178
186
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
179
187
  return false;
180
188
  }
@@ -184,7 +192,9 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
184
192
  node: param,
185
193
  messageId: 'requireDefaultParams',
186
194
  fix(fixer) {
187
- const paramText = context.getSourceCode().getText(param);
195
+ const paramText = context
196
+ .getSourceCode()
197
+ .getText(param);
188
198
  return fixer.replaceText(param, `${paramText} = {}`);
189
199
  },
190
200
  });
@@ -195,7 +205,7 @@ exports.requireHooksDefaultParams = (0, createRule_1.createRule)({
195
205
  }
196
206
  }
197
207
  else if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
198
- if (typeAnnotation.members.every(member => {
208
+ if (typeAnnotation.members.every((member) => {
199
209
  if (member.type !== utils_1.AST_NODE_TYPES.TSPropertySignature) {
200
210
  return false;
201
211
  }
@@ -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/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.',
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: {
@@ -25,7 +25,8 @@ exports.requireUseMemoObjectLiterals = (0, createRule_1.createRule)({
25
25
  }
26
26
  // Skip if the prop name is 'sx' or ends with 'Sx'
27
27
  const propName = node.name.name;
28
- if (typeof propName === 'string' && (propName === 'sx' || propName.endsWith('Sx'))) {
28
+ if (typeof propName === 'string' &&
29
+ (propName === 'sx' || propName.endsWith('Sx'))) {
29
30
  return;
30
31
  }
31
32
  const { expression } = node.value;
@@ -3,8 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.semanticFunctionPrefixes = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
- const DISALLOWED_PREFIXES = new Set(['get', 'update', 'check', 'manage', 'process', 'do']);
7
- const NEXTJS_DATA_FUNCTIONS = new Set(['getServerSideProps', 'getStaticProps', 'getStaticPaths']);
6
+ const DISALLOWED_PREFIXES = new Set([
7
+ 'get',
8
+ 'update',
9
+ 'check',
10
+ 'manage',
11
+ 'process',
12
+ 'do',
13
+ ]);
14
+ const NEXTJS_DATA_FUNCTIONS = new Set([
15
+ 'getServerSideProps',
16
+ 'getStaticProps',
17
+ 'getStaticPaths',
18
+ ]);
8
19
  const SUGGESTED_ALTERNATIVES = {
9
20
  get: ['fetch', 'retrieve', 'compute', 'derive'],
10
21
  update: ['modify', 'set', 'apply'],
@@ -75,7 +86,8 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
75
86
  if (node.id) {
76
87
  functionName = node.id.name;
77
88
  }
78
- else if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator && node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
89
+ else if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
90
+ node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
79
91
  functionName = node.parent.id.name;
80
92
  }
81
93
  if (!functionName)
@@ -40,14 +40,16 @@ exports.syncOnwriteNameFunc = (0, createRule_1.createRule)({
40
40
  if (!nameProperty || !funcProperty) {
41
41
  return;
42
42
  }
43
- const nameValue = nameProperty.value.value;
43
+ const nameValue = nameProperty.value
44
+ .value;
44
45
  let funcName;
45
46
  // Handle variable references
46
47
  if (funcProperty.value.type === utils_1.AST_NODE_TYPES.Identifier) {
47
48
  const funcIdentifier = funcProperty.value;
48
49
  const scope = context.getScope();
49
- const variable = scope.references.find(ref => ref.identifier === funcIdentifier)?.resolved;
50
- if (variable?.defs[0]?.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
50
+ const variable = scope.references.find((ref) => ref.identifier === funcIdentifier)?.resolved;
51
+ if (variable?.defs[0]?.node.type ===
52
+ utils_1.AST_NODE_TYPES.VariableDeclarator &&
51
53
  variable.defs[0].node.init?.type === utils_1.AST_NODE_TYPES.Identifier) {
52
54
  // If the variable is initialized with another identifier, use that name
53
55
  funcName = variable.defs[0].node.init.name;
@@ -262,9 +262,13 @@ class ASTHelpers {
262
262
  default:
263
263
  break;
264
264
  }
265
- // Removing duplicates
265
+ // Removing duplicates and ensuring exact matches only
266
266
  return [
267
- ...new Set(dependencies.filter((dep) => graph?.[dep]?.type !== 'property')),
267
+ ...new Set(dependencies.filter((dep) => {
268
+ // Only include dependencies that exist exactly in the graph
269
+ // This prevents substring matches (e.g., 'nextMatches' vs 'nextMatchesWithResults')
270
+ return (graph?.[dep] !== undefined && graph?.[dep]?.type !== 'property');
271
+ })),
268
272
  ];
269
273
  }
270
274
  static isNode(value) {
@@ -63,7 +63,10 @@ class ClassGraphBuilder {
63
63
  return node.type === 'MethodDefinition';
64
64
  }
65
65
  addDependencies(node, methodName) {
66
- const newDependencies = ASTHelpers_1.ASTHelpers.classMethodDependenciesOf(node, this.graph, this.className).filter((name) => !!this.graph[name] && name !== methodName);
66
+ const newDependencies = ASTHelpers_1.ASTHelpers.classMethodDependenciesOf(node, this.graph, this.className).filter((name) => {
67
+ // Only include dependencies that exist exactly in the graph and aren't self-references
68
+ return !!this.graph[name] && name !== methodName;
69
+ });
67
70
  if (this.graph[methodName]) {
68
71
  // Ensure dependencies is initialized as an array
69
72
  if (!Array.isArray(this.graph[methodName].dependencies)) {
@@ -72,7 +72,9 @@ class ClassGraphSorterReadability extends ClassGraphSorter_1.ClassGraphSorter {
72
72
  visited.add(node.name);
73
73
  dfsSortedNodes.push(node);
74
74
  // Ensure node.dependencies exists and is an array before iterating
75
- const dependencies = Array.isArray(node.dependencies) ? node.dependencies : [];
75
+ const dependencies = Array.isArray(node.dependencies)
76
+ ? node.dependencies
77
+ : [];
76
78
  for (const dep of dependencies) {
77
79
  const depNode = this.graph[String(dep)];
78
80
  if (depNode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.9.1",
3
+ "version": "1.11.0",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -30,11 +30,11 @@
30
30
  "lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"",
31
31
  "lint:js": "eslint ./src",
32
32
  "lint:shell": "shellcheck .devcontainer/git-flow-completion.bash",
33
- "lint:fix": "tsc && eslint ./**/* --quiet --fix",
33
+ "lint:fix": "tsc && eslint \"./src/**/*\" --quiet --fix",
34
34
  "test": "jest --passWithNoTests --reporters=default --reporters=jest-junit",
35
35
  "test:ci": "jest --ci --passWithNoTests --reporters=default --reporters=jest-junit",
36
36
  "docs": "./scripts/make-docs.sh && npm run update:eslint-docs",
37
- "update:eslint-docs": "eslint-doc-generator",
37
+ "update:eslint-docs": "eslint-doc-generator --init-rule-docs",
38
38
  "build": "tsc",
39
39
  "prepare": "husky install && npm run build",
40
40
  "version": "git add -A src",
@@ -1,7 +0,0 @@
1
- declare const _default: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"useImageOverlayed", {
2
- componentPath: string;
3
- }[], {
4
- JSXElement(node: any): void;
5
- ImportDeclaration(node: any): void;
6
- }>;
7
- export = _default;
@@ -1,82 +0,0 @@
1
- "use strict";
2
- const createRule_1 = require("../utils/createRule");
3
- module.exports = (0, createRule_1.createRule)({
4
- name: 'require-image-overlayed',
5
- meta: {
6
- type: 'problem',
7
- docs: {
8
- description: 'Enforce using ImageOverlayed component instead of next/image or img tags',
9
- recommended: 'error',
10
- requiresTypeChecking: false,
11
- },
12
- fixable: 'code',
13
- schema: [
14
- {
15
- type: 'object',
16
- properties: {
17
- componentPath: {
18
- type: 'string',
19
- description: 'The import path for the ImageOverlayed component',
20
- default: 'src/components/ImageOverlayed',
21
- },
22
- },
23
- additionalProperties: false,
24
- },
25
- ],
26
- messages: {
27
- useImageOverlayed: 'Use ImageOverlayed component from {{ componentPath }} instead of {{ component }}',
28
- },
29
- },
30
- defaultOptions: [{ componentPath: 'src/components/ImageOverlayed' }],
31
- create(context) {
32
- const options = context.options[0] || {
33
- componentPath: 'src/components/ImageOverlayed',
34
- };
35
- const sourceCode = context.getSourceCode();
36
- return {
37
- // Handle JSX img elements
38
- JSXElement(node) {
39
- if (node.openingElement.name.name === 'img') {
40
- context.report({
41
- node,
42
- messageId: 'useImageOverlayed',
43
- data: {
44
- componentPath: options.componentPath,
45
- component: 'img tag',
46
- },
47
- fix(fixer) {
48
- const attributes = node.openingElement.attributes
49
- .map((attr) => sourceCode.getText(attr))
50
- .join(' ');
51
- return fixer.replaceText(node, `<ImageOverlayed ${attributes} />`);
52
- },
53
- });
54
- }
55
- },
56
- // Handle next/image imports and usage
57
- ImportDeclaration(node) {
58
- if (node.source.value === 'next/image' && node.specifiers.length > 0) {
59
- const imageSpecifier = node.specifiers.find((spec) => (spec.type === 'ImportDefaultSpecifier' ||
60
- spec.type === 'ImportSpecifier') &&
61
- (spec.local.name === 'Image' || spec.imported?.name === 'Image'));
62
- if (imageSpecifier) {
63
- const localName = imageSpecifier.local.name;
64
- // Report the import
65
- context.report({
66
- node,
67
- messageId: 'useImageOverlayed',
68
- data: {
69
- componentPath: options.componentPath,
70
- component: 'next/image',
71
- },
72
- fix(fixer) {
73
- return fixer.replaceText(node, `import ${localName} from '${options.componentPath}';`);
74
- },
75
- });
76
- }
77
- }
78
- },
79
- };
80
- },
81
- });
82
- //# sourceMappingURL=require-image-overlayed.js.map