@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.
- package/README.md +1 -0
- package/lib/index.js +44 -2
- package/lib/rules/consistent-callback-naming.d.ts +2 -0
- package/lib/rules/consistent-callback-naming.js +84 -0
- package/lib/rules/enforce-callable-types.d.ts +3 -0
- package/lib/rules/enforce-callable-types.js +97 -0
- package/lib/rules/enforce-callback-memo.d.ts +3 -0
- package/lib/rules/enforce-callback-memo.js +79 -0
- package/lib/rules/enforce-dynamic-firebase-imports.d.ts +4 -0
- package/lib/rules/enforce-dynamic-firebase-imports.js +49 -0
- package/lib/rules/enforce-identifiable-firestore-type.d.ts +3 -0
- package/lib/rules/enforce-identifiable-firestore-type.js +89 -0
- package/lib/rules/enforce-serializable-params.d.ts +9 -0
- package/lib/rules/enforce-serializable-params.js +127 -0
- package/lib/rules/global-const-style.d.ts +5 -0
- package/lib/rules/global-const-style.js +77 -0
- package/lib/rules/no-jsx-whitespace-literal.d.ts +1 -0
- package/lib/rules/no-jsx-whitespace-literal.js +34 -0
- package/lib/rules/no-unused-props.d.ts +6 -0
- package/lib/rules/no-unused-props.js +91 -0
- package/lib/rules/require-dynamic-firebase-imports.d.ts +6 -0
- package/lib/rules/require-dynamic-firebase-imports.js +84 -0
- package/lib/rules/require-https-error.d.ts +6 -0
- package/lib/rules/require-https-error.js +74 -0
- package/lib/rules/require-image-overlayed.d.ts +7 -0
- package/lib/rules/require-image-overlayed.js +80 -0
- package/lib/rules/require-memo.js +3 -3
- package/lib/rules/require-usememo-object-literals.d.ts +4 -0
- package/lib/rules/require-usememo-object-literals.js +53 -0
- package/lib/rules/use-custom-link.d.ts +1 -0
- package/lib/rules/use-custom-link.js +49 -0
- package/lib/rules/use-custom-router.d.ts +1 -0
- package/lib/rules/use-custom-router.js +65 -0
- package/lib/utils/ASTHelpers.js +2 -0
- package/lib/utils/graph/ClassGraphBuilder.js +4 -0
- package/lib/utils/graph/ClassGraphSorterReadability.js +8 -3
- 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
|
package/lib/utils/ASTHelpers.js
CHANGED
|
@@ -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)
|
|
69
|
+
if (!node || visited.has(node.name)) {
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
72
|
visited.add(node.name);
|
|
73
73
|
dfsSortedNodes.push(node);
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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.
|
|
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",
|