@blumintinc/eslint-plugin-blumint 1.8.0 → 1.8.1
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 +84 -39
- package/lib/index.js +50 -2
- package/lib/rules/enforce-css-media-queries.d.ts +5 -0
- package/lib/rules/enforce-css-media-queries.js +87 -0
- package/lib/rules/enforce-dynamic-imports.d.ts +9 -0
- package/lib/rules/enforce-dynamic-imports.js +84 -0
- package/lib/rules/enforce-id-capitalization.d.ts +6 -0
- package/lib/rules/enforce-id-capitalization.js +78 -0
- package/lib/rules/enforce-mui-rounded-icons.d.ts +1 -0
- package/lib/rules/enforce-mui-rounded-icons.js +54 -0
- package/lib/rules/enforce-positive-naming.js +30 -6
- package/lib/rules/enforce-react-type-naming.d.ts +3 -0
- package/lib/rules/enforce-react-type-naming.js +191 -0
- package/lib/rules/enforce-singular-type-names.d.ts +2 -0
- package/lib/rules/enforce-singular-type-names.js +112 -0
- package/lib/rules/enforce-verb-noun-naming.js +5 -2
- package/lib/rules/ensure-pointer-events-none.d.ts +1 -0
- package/lib/rules/ensure-pointer-events-none.js +211 -0
- package/lib/rules/key-only-outermost-element.d.ts +3 -0
- package/lib/rules/key-only-outermost-element.js +152 -0
- package/lib/rules/no-always-true-false-conditions.js +19 -0
- package/lib/rules/no-circular-references.d.ts +1 -0
- package/lib/rules/no-circular-references.js +523 -0
- package/lib/rules/no-hungarian.d.ts +5 -0
- package/lib/rules/no-hungarian.js +223 -0
- package/lib/rules/no-object-values-on-strings.d.ts +2 -0
- package/lib/rules/no-object-values-on-strings.js +294 -0
- package/lib/rules/no-type-assertion-returns.js +10 -0
- package/lib/rules/no-unnecessary-destructuring.d.ts +5 -0
- package/lib/rules/no-unnecessary-destructuring.js +69 -0
- package/lib/rules/no-unused-props.js +10 -5
- package/lib/rules/no-unused-usestate.d.ts +8 -0
- package/lib/rules/no-unused-usestate.js +84 -0
- package/lib/rules/omit-index-html.d.ts +7 -0
- package/lib/rules/omit-index-html.js +91 -0
- package/lib/rules/prefer-usememo-over-useeffect-usestate.d.ts +5 -0
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +129 -0
- package/lib/rules/react-usememo-should-be-component.js +369 -2
- package/package.json +7 -4
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noUnusedUseState = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/BluMintInc/eslint-custom-rules/blob/main/docs/rules/${name}.md`);
|
|
6
|
+
/**
|
|
7
|
+
* Rule to detect and remove unused useState hooks in React components
|
|
8
|
+
* This rule identifies cases where the state variable from useState is ignored (e.g., replaced with _)
|
|
9
|
+
*/
|
|
10
|
+
exports.noUnusedUseState = createRule({
|
|
11
|
+
name: 'no-unused-usestate',
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'suggestion',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Disallow unused useState hooks',
|
|
16
|
+
recommended: 'error',
|
|
17
|
+
},
|
|
18
|
+
fixable: 'code',
|
|
19
|
+
messages: {
|
|
20
|
+
unusedUseState: 'The state variable is ignored. Remove the unused useState hook.',
|
|
21
|
+
},
|
|
22
|
+
schema: [],
|
|
23
|
+
},
|
|
24
|
+
defaultOptions: [],
|
|
25
|
+
create(context) {
|
|
26
|
+
return {
|
|
27
|
+
// Look for variable declarations that destructure from useState
|
|
28
|
+
VariableDeclarator(node) {
|
|
29
|
+
// Check if it's an array pattern (destructuring)
|
|
30
|
+
if (node.id.type === utils_1.TSESTree.AST_NODE_TYPES.ArrayPattern &&
|
|
31
|
+
node.init?.type === utils_1.TSESTree.AST_NODE_TYPES.CallExpression) {
|
|
32
|
+
const callExpression = node.init;
|
|
33
|
+
// Check if the call is to useState
|
|
34
|
+
if (callExpression.callee.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
|
|
35
|
+
callExpression.callee.name === 'useState') {
|
|
36
|
+
const arrayPattern = node.id;
|
|
37
|
+
// Check if the first element is ignored (named _ or unused)
|
|
38
|
+
if (arrayPattern.elements.length > 0 &&
|
|
39
|
+
arrayPattern.elements[0] &&
|
|
40
|
+
arrayPattern.elements[0].type === utils_1.TSESTree.AST_NODE_TYPES.Identifier &&
|
|
41
|
+
(arrayPattern.elements[0].name === '_' || arrayPattern.elements[0].name.startsWith('_'))) {
|
|
42
|
+
context.report({
|
|
43
|
+
node,
|
|
44
|
+
messageId: 'unusedUseState',
|
|
45
|
+
fix: (fixer) => {
|
|
46
|
+
// Remove the entire useState declaration
|
|
47
|
+
const sourceCode = context.getSourceCode();
|
|
48
|
+
const parentStatement = node.parent;
|
|
49
|
+
if (parentStatement && parentStatement.type === utils_1.TSESTree.AST_NODE_TYPES.VariableDeclaration) {
|
|
50
|
+
// If this is the only declarator, remove the entire statement and any extra whitespace
|
|
51
|
+
if (parentStatement.declarations.length === 1) {
|
|
52
|
+
// Get the next token after the statement to handle whitespace properly
|
|
53
|
+
const nextToken = sourceCode.getTokenAfter(parentStatement, { includeComments: true });
|
|
54
|
+
if (nextToken) {
|
|
55
|
+
// Remove the statement and any whitespace up to the next token
|
|
56
|
+
return fixer.removeRange([parentStatement.range[0], nextToken.range[0]]);
|
|
57
|
+
}
|
|
58
|
+
return fixer.remove(parentStatement);
|
|
59
|
+
}
|
|
60
|
+
// Otherwise, just remove this declarator and any trailing comma
|
|
61
|
+
const declaratorRange = node.range;
|
|
62
|
+
// Check if there's a comma after this declarator
|
|
63
|
+
const tokenAfter = sourceCode.getTokenAfter(node);
|
|
64
|
+
if (tokenAfter && tokenAfter.value === ',') {
|
|
65
|
+
return fixer.removeRange([declaratorRange[0], tokenAfter.range[1]]);
|
|
66
|
+
}
|
|
67
|
+
// Check if there's a comma before this declarator
|
|
68
|
+
const tokenBefore = sourceCode.getTokenBefore(node);
|
|
69
|
+
if (tokenBefore && tokenBefore.value === ',') {
|
|
70
|
+
return fixer.removeRange([tokenBefore.range[0], declaratorRange[1]]);
|
|
71
|
+
}
|
|
72
|
+
return fixer.remove(node);
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
//# sourceMappingURL=no-unused-usestate.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type Options = [
|
|
2
|
+
{
|
|
3
|
+
allowWithQueryOrHash?: boolean;
|
|
4
|
+
}
|
|
5
|
+
];
|
|
6
|
+
export declare const omitIndexHtml: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"omitIndexHtml", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.omitIndexHtml = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
/**
|
|
6
|
+
* Checks if a string is a URL by looking for common URL patterns
|
|
7
|
+
* and excludes file system paths
|
|
8
|
+
*/
|
|
9
|
+
function isLikelyUrl(str) {
|
|
10
|
+
// Check if it's a URL
|
|
11
|
+
const isUrl = str.startsWith('http://') ||
|
|
12
|
+
str.startsWith('https://') ||
|
|
13
|
+
str.startsWith('/');
|
|
14
|
+
// Exclude file system paths (containing directories like /usr/, /var/, etc.)
|
|
15
|
+
const isFilePath = /^\/(?:usr|var|etc|home|opt|tmp|bin|lib|mnt|media|root|srv|sys|boot|dev)\//.test(str);
|
|
16
|
+
return isUrl && !isFilePath;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Checks if a URL has query parameters or hash fragments
|
|
20
|
+
*/
|
|
21
|
+
function hasQueryOrHash(url) {
|
|
22
|
+
return url.includes('?') || url.includes('#');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Removes 'index.html' from a URL and ensures proper trailing slash
|
|
26
|
+
*/
|
|
27
|
+
function fixUrl(url) {
|
|
28
|
+
// Replace /index.html with /
|
|
29
|
+
return url.replace(/\/index\.html($|[?#])/, '/$1');
|
|
30
|
+
}
|
|
31
|
+
exports.omitIndexHtml = (0, createRule_1.createRule)({
|
|
32
|
+
name: 'omit-index-html',
|
|
33
|
+
meta: {
|
|
34
|
+
type: 'suggestion',
|
|
35
|
+
docs: {
|
|
36
|
+
description: 'Disallow the use of "index.html" in URLs',
|
|
37
|
+
recommended: 'error',
|
|
38
|
+
},
|
|
39
|
+
fixable: 'code',
|
|
40
|
+
schema: [
|
|
41
|
+
{
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
allowWithQueryOrHash: {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
additionalProperties: false,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
messages: {
|
|
52
|
+
omitIndexHtml: 'Avoid using "index.html" in URLs. Many web servers automatically resolve directory requests to index.html.',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
defaultOptions: [{ allowWithQueryOrHash: true }],
|
|
56
|
+
create(context, [options]) {
|
|
57
|
+
const allowWithQueryOrHash = options.allowWithQueryOrHash !== false;
|
|
58
|
+
return {
|
|
59
|
+
Literal(node) {
|
|
60
|
+
if (typeof node.value !== 'string')
|
|
61
|
+
return;
|
|
62
|
+
const value = node.value;
|
|
63
|
+
// Check if it's a URL and contains index.html
|
|
64
|
+
if (isLikelyUrl(value) && value.includes('/index.html')) {
|
|
65
|
+
// Skip if it has query parameters or hash fragments and we're allowing those
|
|
66
|
+
if (allowWithQueryOrHash && hasQueryOrHash(value))
|
|
67
|
+
return;
|
|
68
|
+
context.report({
|
|
69
|
+
node,
|
|
70
|
+
messageId: 'omitIndexHtml',
|
|
71
|
+
fix: (fixer) => {
|
|
72
|
+
return fixer.replaceText(node, `"${fixUrl(value)}"`);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
TemplateLiteral(node) {
|
|
78
|
+
// For template literals, we can only check if the static parts contain index.html
|
|
79
|
+
const value = node.quasis.map(q => q.value.raw).join('');
|
|
80
|
+
if (value.includes('/index.html')) {
|
|
81
|
+
context.report({
|
|
82
|
+
node,
|
|
83
|
+
messageId: 'omitIndexHtml',
|
|
84
|
+
// No automatic fix for template literals as they may contain dynamic parts
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=omit-index-html.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { TSESTree } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const preferUseMemoOverUseEffectUseState: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"preferUseMemo", never[], {
|
|
3
|
+
VariableDeclarator(node: TSESTree.VariableDeclarator): void;
|
|
4
|
+
CallExpression(node: TSESTree.CallExpression): void;
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preferUseMemoOverUseEffectUseState = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
exports.preferUseMemoOverUseEffectUseState = (0, createRule_1.createRule)({
|
|
6
|
+
name: 'prefer-usememo-over-useeffect-usestate',
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'suggestion',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Prefer useMemo over useEffect + useState for pure computations. Using useEffect to update state with a pure computation causes unnecessary re-renders.',
|
|
11
|
+
recommended: 'error',
|
|
12
|
+
},
|
|
13
|
+
messages: {
|
|
14
|
+
preferUseMemo: 'Prefer useMemo over useEffect + useState for pure computations to avoid unnecessary re-renders',
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
defaultOptions: [],
|
|
19
|
+
create(context) {
|
|
20
|
+
// Track useState declarations to match with useEffect
|
|
21
|
+
const stateSetters = new Map();
|
|
22
|
+
// Helper to check if a node is a pure computation (no side effects)
|
|
23
|
+
const isPureComputation = (node) => {
|
|
24
|
+
// If it's an arrow function or function expression, it's not pure for our purposes
|
|
25
|
+
if (node.type === 'ArrowFunctionExpression' ||
|
|
26
|
+
node.type === 'FunctionExpression') {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
// If it's a call expression, check if it's likely to be pure
|
|
30
|
+
if (node.type === 'CallExpression') {
|
|
31
|
+
// Allow certain known pure functions
|
|
32
|
+
const callee = node.callee;
|
|
33
|
+
if (callee.type === 'Identifier') {
|
|
34
|
+
// Allow functions that start with "compute", "calculate", "format", etc.
|
|
35
|
+
const pureNamePatterns = /^(compute|calculate|format|transform|convert|get|derive|create|expensive)/i;
|
|
36
|
+
if (pureNamePatterns.test(callee.name)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
// Consider all other named functions as potentially having side effects
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// Allow array methods like map, filter, reduce which are pure
|
|
43
|
+
if (callee.type === 'MemberExpression' &&
|
|
44
|
+
callee.property.type === 'Identifier') {
|
|
45
|
+
const arrayMethods = [
|
|
46
|
+
'map',
|
|
47
|
+
'filter',
|
|
48
|
+
'reduce',
|
|
49
|
+
'some',
|
|
50
|
+
'every',
|
|
51
|
+
'find',
|
|
52
|
+
'findIndex',
|
|
53
|
+
];
|
|
54
|
+
if (arrayMethods.includes(callee.property.name)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// For this rule, we'll consider all other function calls as potentially having side effects
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
// If it's an await expression, it's not pure
|
|
62
|
+
if (node.type === 'AwaitExpression') {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// If it contains assignments to variables outside its scope, it's not pure
|
|
66
|
+
if (node.type === 'AssignmentExpression') {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
// Object and array literals are pure
|
|
70
|
+
if (node.type === 'ObjectExpression' || node.type === 'ArrayExpression') {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
// Allow basic expressions and literals
|
|
74
|
+
return true;
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
// Track useState declarations
|
|
78
|
+
VariableDeclarator(node) {
|
|
79
|
+
if (node.init?.type === 'CallExpression' &&
|
|
80
|
+
node.init.callee.type === 'Identifier' &&
|
|
81
|
+
node.init.callee.name === 'useState') {
|
|
82
|
+
if (node.id.type === 'ArrayPattern' &&
|
|
83
|
+
node.id.elements.length === 2 &&
|
|
84
|
+
node.id.elements[0]?.type === 'Identifier' &&
|
|
85
|
+
node.id.elements[1]?.type === 'Identifier') {
|
|
86
|
+
const stateName = node.id.elements[0].name;
|
|
87
|
+
const setterName = node.id.elements[1].name;
|
|
88
|
+
const initialValue = node.init.arguments[0] || null;
|
|
89
|
+
stateSetters.set(setterName, { stateName, initialValue });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
// Check useEffect calls
|
|
94
|
+
CallExpression(node) {
|
|
95
|
+
if (node.callee.type === 'Identifier' &&
|
|
96
|
+
node.callee.name === 'useEffect' &&
|
|
97
|
+
node.arguments.length > 0 &&
|
|
98
|
+
(node.arguments[0].type === 'ArrowFunctionExpression' ||
|
|
99
|
+
node.arguments[0].type === 'FunctionExpression')) {
|
|
100
|
+
const effectCallback = node.arguments[0];
|
|
101
|
+
// Check if the effect body is a block with a single statement
|
|
102
|
+
if (effectCallback.body.type === 'BlockStatement' &&
|
|
103
|
+
effectCallback.body.body.length === 1) {
|
|
104
|
+
const statement = effectCallback.body.body[0];
|
|
105
|
+
// Check if the statement is a call to a state setter
|
|
106
|
+
if (statement.type === 'ExpressionStatement' &&
|
|
107
|
+
statement.expression.type === 'CallExpression' &&
|
|
108
|
+
statement.expression.callee.type === 'Identifier') {
|
|
109
|
+
const setterName = statement.expression.callee.name;
|
|
110
|
+
const stateInfo = stateSetters.get(setterName);
|
|
111
|
+
if (stateInfo && statement.expression.arguments.length === 1) {
|
|
112
|
+
const computation = statement.expression.arguments[0];
|
|
113
|
+
// Check if the computation is pure
|
|
114
|
+
if (isPureComputation(computation)) {
|
|
115
|
+
// Report the issue but without autofixing
|
|
116
|
+
context.report({
|
|
117
|
+
node,
|
|
118
|
+
messageId: 'preferUseMemo',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=prefer-usememo-over-useeffect-usestate.js.map
|