@blumintinc/eslint-plugin-blumint 1.0.5 → 1.1.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 (37) hide show
  1. package/README.md +1 -0
  2. package/lib/index.js +44 -2
  3. package/lib/rules/consistent-callback-naming.d.ts +2 -0
  4. package/lib/rules/consistent-callback-naming.js +84 -0
  5. package/lib/rules/enforce-callable-types.d.ts +3 -0
  6. package/lib/rules/enforce-callable-types.js +97 -0
  7. package/lib/rules/enforce-callback-memo.d.ts +3 -0
  8. package/lib/rules/enforce-callback-memo.js +79 -0
  9. package/lib/rules/enforce-dynamic-firebase-imports.d.ts +4 -0
  10. package/lib/rules/enforce-dynamic-firebase-imports.js +49 -0
  11. package/lib/rules/enforce-identifiable-firestore-type.d.ts +3 -0
  12. package/lib/rules/enforce-identifiable-firestore-type.js +89 -0
  13. package/lib/rules/enforce-serializable-params.d.ts +9 -0
  14. package/lib/rules/enforce-serializable-params.js +127 -0
  15. package/lib/rules/global-const-style.d.ts +5 -0
  16. package/lib/rules/global-const-style.js +77 -0
  17. package/lib/rules/no-jsx-whitespace-literal.d.ts +1 -0
  18. package/lib/rules/no-jsx-whitespace-literal.js +34 -0
  19. package/lib/rules/no-unused-props.d.ts +6 -0
  20. package/lib/rules/no-unused-props.js +91 -0
  21. package/lib/rules/require-dynamic-firebase-imports.d.ts +6 -0
  22. package/lib/rules/require-dynamic-firebase-imports.js +84 -0
  23. package/lib/rules/require-https-error.d.ts +6 -0
  24. package/lib/rules/require-https-error.js +74 -0
  25. package/lib/rules/require-image-overlayed.d.ts +7 -0
  26. package/lib/rules/require-image-overlayed.js +80 -0
  27. package/lib/rules/require-memo.js +3 -3
  28. package/lib/rules/require-usememo-object-literals.d.ts +4 -0
  29. package/lib/rules/require-usememo-object-literals.js +53 -0
  30. package/lib/rules/use-custom-link.d.ts +1 -0
  31. package/lib/rules/use-custom-link.js +49 -0
  32. package/lib/rules/use-custom-router.d.ts +1 -0
  33. package/lib/rules/use-custom-router.js +65 -0
  34. package/lib/utils/ASTHelpers.js +2 -0
  35. package/lib/utils/graph/ClassGraphBuilder.js +4 -0
  36. package/lib/utils/graph/ClassGraphSorterReadability.js +8 -3
  37. package/package.json +2 -2
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireUseMemoObjectLiterals = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ exports.requireUseMemoObjectLiterals = (0, createRule_1.createRule)({
6
+ name: 'require-usememo-object-literals',
7
+ meta: {
8
+ type: 'suggestion',
9
+ docs: {
10
+ description: 'Enforce using useMemo for inline object literals passed as props to JSX components',
11
+ recommended: 'error',
12
+ },
13
+ messages: {
14
+ requireUseMemo: 'Inline object/array literals in JSX props should be wrapped in useMemo to prevent unnecessary re-renders',
15
+ },
16
+ schema: [],
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ return {
21
+ JSXAttribute(node) {
22
+ // Skip if the value is not an expression
23
+ if (!node.value || node.value.type !== 'JSXExpressionContainer') {
24
+ return;
25
+ }
26
+ const { expression } = node.value;
27
+ // Check if the expression is an object or array literal
28
+ if ((expression.type === 'ObjectExpression' ||
29
+ expression.type === 'ArrayExpression') &&
30
+ // Ensure we're in a function component context
31
+ context
32
+ .getAncestors()
33
+ .some((ancestor) => ancestor.type === 'FunctionDeclaration' ||
34
+ ancestor.type === 'ArrowFunctionExpression' ||
35
+ ancestor.type === 'FunctionExpression')) {
36
+ // Check if the parent component name starts with an uppercase letter
37
+ // to ensure it's a React component
38
+ const jsxElement = node.parent;
39
+ const elementName = jsxElement.name.type === 'JSXIdentifier'
40
+ ? jsxElement.name.name
41
+ : '';
42
+ if (elementName && /^[A-Z]/.test(elementName)) {
43
+ context.report({
44
+ node: expression,
45
+ messageId: 'requireUseMemo',
46
+ });
47
+ }
48
+ }
49
+ },
50
+ };
51
+ },
52
+ });
53
+ //# sourceMappingURL=require-usememo-object-literals.js.map
@@ -0,0 +1 @@
1
+ export declare const useCustomLink: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"useCustomLink", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useCustomLink = void 0;
4
+ const createRule_1 = require("../utils/createRule");
5
+ exports.useCustomLink = (0, createRule_1.createRule)({
6
+ name: 'use-custom-link',
7
+ meta: {
8
+ type: 'problem',
9
+ docs: {
10
+ description: 'Enforce using src/components/Link instead of next/link',
11
+ recommended: 'error',
12
+ },
13
+ fixable: 'code',
14
+ schema: [],
15
+ messages: {
16
+ useCustomLink: 'Import Link from src/components/Link instead of next/link',
17
+ },
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ return {
22
+ ImportDeclaration(node) {
23
+ if (node.source.value === 'next/link') {
24
+ const importSpecifiers = node.specifiers;
25
+ // Handle different import types (default, named, namespace)
26
+ const defaultSpecifier = importSpecifiers.find((specifier) => specifier.type === 'ImportDefaultSpecifier');
27
+ const defaultAsSpecifier = importSpecifiers.find((specifier) => specifier.type === 'ImportSpecifier' &&
28
+ specifier.imported.name === 'default');
29
+ if (defaultSpecifier || defaultAsSpecifier) {
30
+ context.report({
31
+ node,
32
+ messageId: 'useCustomLink',
33
+ fix(fixer) {
34
+ // Get the local name of the imported Link component
35
+ const localName = defaultSpecifier?.local?.name ||
36
+ defaultAsSpecifier?.local?.name ||
37
+ 'Link';
38
+ // Create the new import statement
39
+ const newImport = `import ${localName} from 'src/components/Link';`;
40
+ return fixer.replaceText(node, newImport);
41
+ },
42
+ });
43
+ }
44
+ }
45
+ },
46
+ };
47
+ },
48
+ });
49
+ //# sourceMappingURL=use-custom-link.js.map
@@ -0,0 +1 @@
1
+ export declare const useCustomRouter: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"useCustomRouter", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useCustomRouter = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ exports.useCustomRouter = (0, createRule_1.createRule)({
7
+ name: 'use-custom-router',
8
+ meta: {
9
+ type: 'suggestion',
10
+ docs: {
11
+ description: 'Enforce using src/hooks/routing/useRouter instead of next/router',
12
+ recommended: 'error',
13
+ },
14
+ fixable: 'code',
15
+ schema: [],
16
+ messages: {
17
+ useCustomRouter: 'Import useRouter from src/hooks/routing/useRouter instead of next/router',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ return {
23
+ ImportDeclaration(node) {
24
+ if (node.source.value === 'next/router') {
25
+ const specifiers = node.specifiers.filter((specifier) => specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier &&
26
+ specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
27
+ specifier.imported.name === 'useRouter');
28
+ if (specifiers.length > 0) {
29
+ context.report({
30
+ node,
31
+ messageId: 'useCustomRouter',
32
+ fix(fixer) {
33
+ // If there are other imports from next/router, keep them
34
+ const otherSpecifiers = node.specifiers.filter((specifier) => specifier.type !== utils_1.AST_NODE_TYPES.ImportSpecifier ||
35
+ (specifier.imported.type === utils_1.AST_NODE_TYPES.Identifier &&
36
+ specifier.imported.name !== 'useRouter'));
37
+ if (otherSpecifiers.length === 0) {
38
+ // If useRouter is the only import, replace the entire import
39
+ return fixer.replaceText(node, `import { ${specifiers
40
+ .map((s) => s.local.name !== s.imported.name
41
+ ? `useRouter as ${s.local.name}`
42
+ : 'useRouter')
43
+ .join(', ')} } from 'src/hooks/routing/useRouter';`);
44
+ }
45
+ else {
46
+ // Create a new import for useRouter and keep other imports
47
+ const useRouterImport = `import { ${specifiers
48
+ .map((s) => s.local.name !== s.imported.name
49
+ ? `useRouter as ${s.local.name}`
50
+ : 'useRouter')
51
+ .join(', ')} } from 'src/hooks/routing/useRouter';\n`;
52
+ const otherImports = `import { ${otherSpecifiers
53
+ .map((s) => s.local.name)
54
+ .join(', ')} } from 'next/router';`;
55
+ return fixer.replaceText(node, useRouterImport + otherImports);
56
+ }
57
+ },
58
+ });
59
+ }
60
+ }
61
+ },
62
+ };
63
+ },
64
+ });
65
+ //# sourceMappingURL=use-custom-router.js.map
@@ -84,6 +84,8 @@ class ASTHelpers {
84
84
  return (this.declarationIncludesIdentifier(node.test) ||
85
85
  this.declarationIncludesIdentifier(node.consequent) ||
86
86
  this.declarationIncludesIdentifier(node.alternate));
87
+ case 'TemplateLiteral':
88
+ return node.expressions.some((expr) => this.declarationIncludesIdentifier(expr));
87
89
  case 'TSAsExpression':
88
90
  return this.declarationIncludesIdentifier(node.expression);
89
91
  default:
@@ -65,6 +65,10 @@ class ClassGraphBuilder {
65
65
  addDependencies(node, methodName) {
66
66
  const newDependencies = ASTHelpers_1.ASTHelpers.classMethodDependenciesOf(node, this.graph, this.className).filter((name) => !!this.graph[name] && name !== methodName);
67
67
  if (this.graph[methodName]) {
68
+ // Ensure dependencies is initialized as an array
69
+ if (!Array.isArray(this.graph[methodName].dependencies)) {
70
+ this.graph[methodName].dependencies = [];
71
+ }
68
72
  this.graph[methodName].dependencies.push(...newDependencies);
69
73
  }
70
74
  }
@@ -66,13 +66,18 @@ class ClassGraphSorterReadability extends ClassGraphSorter_1.ClassGraphSorter {
66
66
  const visited = new Set();
67
67
  const dfsSortedNodes = [];
68
68
  const dfs = (node) => {
69
- if (visited.has(node.name) || !node) {
69
+ if (!node || visited.has(node.name)) {
70
70
  return;
71
71
  }
72
72
  visited.add(node.name);
73
73
  dfsSortedNodes.push(node);
74
- for (const dep of node.dependencies) {
75
- dfs(this.graph[String(dep)]);
74
+ // Ensure node.dependencies exists and is an array before iterating
75
+ const dependencies = Array.isArray(node.dependencies) ? node.dependencies : [];
76
+ for (const dep of dependencies) {
77
+ const depNode = this.graph[String(dep)];
78
+ if (depNode) {
79
+ dfs(depNode);
80
+ }
76
81
  }
77
82
  };
78
83
  searchNodes.forEach((node) => dfs(node));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -63,7 +63,7 @@
63
63
  "cz-conventional-changelog": "3.3.0",
64
64
  "del-cli": "4.0.1",
65
65
  "dotenv-cli": "5.0.0",
66
- "eslint": "8.11.0",
66
+ "eslint": "8.57.0",
67
67
  "eslint-config-prettier": "8.5.0",
68
68
  "eslint-doc-generator": "1.0.0",
69
69
  "eslint-import-resolver-typescript": "3.5.5",