@blumintinc/eslint-plugin-blumint 1.8.0 → 1.8.2
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-assertSafe-object-key.js +1 -1
- 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 +118 -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-object-literal-as-const.js +37 -0
- package/lib/rules/enforce-positive-naming.js +116 -168
- 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 +143 -118
- 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 +109 -11
- 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,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceIdCapitalization = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
6
|
+
/**
|
|
7
|
+
* This rule ensures consistency in user-facing text by enforcing the use of "ID"
|
|
8
|
+
* instead of "id" when referring to identifiers in UI labels, instructions,
|
|
9
|
+
* error messages, and other visible strings.
|
|
10
|
+
*/
|
|
11
|
+
exports.enforceIdCapitalization = (0, createRule_1.createRule)({
|
|
12
|
+
name: 'enforce-id-capitalization',
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'suggestion',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'Enforce the use of "ID" instead of "id" in user-facing text',
|
|
17
|
+
recommended: 'error',
|
|
18
|
+
},
|
|
19
|
+
fixable: 'code',
|
|
20
|
+
schema: [],
|
|
21
|
+
messages: {
|
|
22
|
+
enforceIdCapitalization: 'Use "ID" instead of "id" in user-facing text for better readability',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultOptions: [],
|
|
26
|
+
create(context) {
|
|
27
|
+
// Regular expression to match standalone "id" surrounded by whitespace or punctuation
|
|
28
|
+
// This ensures we only match "id" as a word, not as part of another word
|
|
29
|
+
const idRegex = /(^|\s|[.,;:!?'"()\[\]{}])id(\s|$|[.,;:!?'"()\[\]{}])/g;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a node is in a context that should be excluded from the rule
|
|
32
|
+
* (e.g., parameter names, property names, type definitions)
|
|
33
|
+
*/
|
|
34
|
+
function isExcludedContext(node) {
|
|
35
|
+
// Check if the node is a property of an object pattern (destructuring)
|
|
36
|
+
if (node.parent &&
|
|
37
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.Property ||
|
|
38
|
+
node.parent.type === utils_1.AST_NODE_TYPES.PropertyDefinition) &&
|
|
39
|
+
node.parent.key === node) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
// Check if the node is a parameter name
|
|
43
|
+
if (node.parent &&
|
|
44
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
45
|
+
node.parent.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
46
|
+
node.parent.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) &&
|
|
47
|
+
node.parent.params.includes(node)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
// Check if the node is part of a type definition
|
|
51
|
+
if (node.parent &&
|
|
52
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.TSPropertySignature ||
|
|
53
|
+
node.parent.type === utils_1.AST_NODE_TYPES.TSParameterProperty ||
|
|
54
|
+
node.parent.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation ||
|
|
55
|
+
node.parent.type === utils_1.AST_NODE_TYPES.TSTypeReference)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
// Check if the node is a variable name
|
|
59
|
+
if (node.parent &&
|
|
60
|
+
node.parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
61
|
+
node.parent.id === node) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if a string contains "id" as a standalone word and report if found
|
|
68
|
+
*/
|
|
69
|
+
function checkForIdInString(node, value) {
|
|
70
|
+
if (typeof value !== 'string')
|
|
71
|
+
return;
|
|
72
|
+
// Skip checking if the node is in an excluded context
|
|
73
|
+
if (isExcludedContext(node))
|
|
74
|
+
return;
|
|
75
|
+
// Reset the regex lastIndex to ensure consistent behavior
|
|
76
|
+
idRegex.lastIndex = 0;
|
|
77
|
+
// Check if the string contains "id" as a standalone word
|
|
78
|
+
if (idRegex.test(value)) {
|
|
79
|
+
// Reset the regex lastIndex again before replacing
|
|
80
|
+
idRegex.lastIndex = 0;
|
|
81
|
+
const fixedText = value.replace(idRegex, (_match, prefix, suffix) => {
|
|
82
|
+
return `${prefix}ID${suffix}`;
|
|
83
|
+
});
|
|
84
|
+
context.report({
|
|
85
|
+
node,
|
|
86
|
+
messageId: 'enforceIdCapitalization',
|
|
87
|
+
fix: (fixer) => {
|
|
88
|
+
if (node.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
89
|
+
return fixer.replaceText(node, JSON.stringify(fixedText));
|
|
90
|
+
}
|
|
91
|
+
else if (node.type === utils_1.AST_NODE_TYPES.JSXText) {
|
|
92
|
+
return fixer.replaceText(node, fixedText);
|
|
93
|
+
}
|
|
94
|
+
else if (node.type === utils_1.AST_NODE_TYPES.TemplateElement) {
|
|
95
|
+
return fixer.replaceText(node, fixedText);
|
|
96
|
+
}
|
|
97
|
+
return fixer.replaceText(node, JSON.stringify(fixedText));
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
// Check string literals
|
|
104
|
+
Literal(node) {
|
|
105
|
+
if (typeof node.value === 'string') {
|
|
106
|
+
checkForIdInString(node, node.value);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
// Check JSX text
|
|
110
|
+
JSXText(node) {
|
|
111
|
+
checkForIdInString(node, node.value);
|
|
112
|
+
},
|
|
113
|
+
// We don't need a separate handler for CallExpression since we already handle Literals
|
|
114
|
+
// The Literal handler will catch the string arguments in t("user.profile.id")
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
//# sourceMappingURL=enforce-id-capitalization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const enforceMuiRoundedIcons: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"enforceRoundedVariant", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceMuiRoundedIcons = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
6
|
+
exports.enforceMuiRoundedIcons = (0, createRule_1.createRule)({
|
|
7
|
+
name: 'enforce-mui-rounded-icons',
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Enforce the use of -Rounded variant for MUI icons',
|
|
12
|
+
recommended: 'error',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
enforceRoundedVariant: 'Use the -Rounded variant for MUI icons (e.g., LogoutRounded instead of Logout)',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
create(context) {
|
|
22
|
+
return {
|
|
23
|
+
ImportDeclaration(node) {
|
|
24
|
+
// Only check imports from @mui/icons-material
|
|
25
|
+
if (node.source.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
26
|
+
typeof node.source.value === 'string' &&
|
|
27
|
+
node.source.value.startsWith('@mui/icons-material/')) {
|
|
28
|
+
const iconPath = node.source.value;
|
|
29
|
+
// Skip if already using -Rounded variant
|
|
30
|
+
if (iconPath.endsWith('Rounded')) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Extract the icon name from the path
|
|
34
|
+
const iconName = iconPath.split('/').pop();
|
|
35
|
+
// Skip if the icon name is not a string (shouldn't happen)
|
|
36
|
+
if (!iconName) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Create the rounded variant name
|
|
40
|
+
const roundedVariant = `${iconName}Rounded`;
|
|
41
|
+
context.report({
|
|
42
|
+
node,
|
|
43
|
+
messageId: 'enforceRoundedVariant',
|
|
44
|
+
fix: (fixer) => {
|
|
45
|
+
// Replace the import path with the rounded variant
|
|
46
|
+
return fixer.replaceText(node.source, `'@mui/icons-material/${roundedVariant}'`);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=enforce-mui-rounded-icons.js.map
|
|
@@ -18,6 +18,36 @@ exports.enforceObjectLiteralAsConst = (0, createRule_1.createRule)({
|
|
|
18
18
|
},
|
|
19
19
|
defaultOptions: [],
|
|
20
20
|
create(context) {
|
|
21
|
+
/**
|
|
22
|
+
* Checks if the node is inside a React hook like useMemo
|
|
23
|
+
*/
|
|
24
|
+
function isInsideReactHook(ancestors) {
|
|
25
|
+
for (let i = 0; i < ancestors.length; i++) {
|
|
26
|
+
const ancestor = ancestors[i];
|
|
27
|
+
// Check for CallExpression (function call)
|
|
28
|
+
if (ancestor.type === 'CallExpression') {
|
|
29
|
+
const callee = ancestor.callee;
|
|
30
|
+
// Check if it's a hook like useMemo, useCallback, etc.
|
|
31
|
+
if (callee.type === 'Identifier' &&
|
|
32
|
+
(callee.name === 'useMemo' ||
|
|
33
|
+
callee.name === 'useCallback' ||
|
|
34
|
+
callee.name.startsWith('use'))) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Checks if an array contains object literals that might be used as component props
|
|
43
|
+
*/
|
|
44
|
+
function isArrayWithObjectLiterals(node) {
|
|
45
|
+
if (node.type !== 'ArrayExpression') {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
// Check if any element in the array is an object literal
|
|
49
|
+
return node.elements.some((elem) => elem !== null && elem.type === 'ObjectExpression');
|
|
50
|
+
}
|
|
21
51
|
return {
|
|
22
52
|
ReturnStatement(node) {
|
|
23
53
|
// Skip if there's no argument in the return statement
|
|
@@ -66,6 +96,13 @@ exports.enforceObjectLiteralAsConst = (0, createRule_1.createRule)({
|
|
|
66
96
|
argument.elements.some((elem) => elem !== null && elem.type === 'SpreadElement'))) {
|
|
67
97
|
return;
|
|
68
98
|
}
|
|
99
|
+
// Skip arrays with object literals inside React hooks (likely component props)
|
|
100
|
+
if (isInsideReactHook(ancestors) &&
|
|
101
|
+
isArrayWithObjectLiterals(argument.type === 'TSAsExpression'
|
|
102
|
+
? argument.expression
|
|
103
|
+
: argument)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
69
106
|
// Report the issue and provide a fix
|
|
70
107
|
context.report({
|
|
71
108
|
node,
|
|
@@ -3,60 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.enforcePositiveNaming = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
-
// Common negative prefixes
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'incomplete',
|
|
12
|
-
'unpaid',
|
|
13
|
-
'inactive',
|
|
14
|
-
'disallowed',
|
|
15
|
-
'unauthorized',
|
|
16
|
-
'unverified',
|
|
17
|
-
'prevent',
|
|
18
|
-
'avoid',
|
|
19
|
-
'block',
|
|
20
|
-
'deny',
|
|
21
|
-
'reject',
|
|
22
|
-
'exclude',
|
|
23
|
-
'fail',
|
|
24
|
-
'missing',
|
|
25
|
-
'forbidden',
|
|
26
|
-
'impossible',
|
|
27
|
-
'error',
|
|
28
|
-
'broken',
|
|
29
|
-
'banned',
|
|
30
|
-
'restricted',
|
|
31
|
-
'limitation',
|
|
32
|
-
'limitation',
|
|
33
|
-
'blocked',
|
|
34
|
-
'prohibited',
|
|
35
|
-
];
|
|
36
|
-
// Whitelist of acceptable negative terms (technical terms, common patterns)
|
|
37
|
-
const ALLOWED_NEGATIVE_TERMS = new Set([
|
|
38
|
-
'isNaN',
|
|
39
|
-
'isNull',
|
|
40
|
-
'isUndefined',
|
|
41
|
-
'isEmpty',
|
|
42
|
-
'isOffline',
|
|
43
|
-
'isMissing',
|
|
44
|
-
'isOutOfStock',
|
|
45
|
-
'isOutOfBounds',
|
|
46
|
-
'isOffPeak',
|
|
47
|
-
'isNoticeable',
|
|
48
|
-
'hasNotification',
|
|
49
|
-
'isNoteworthy',
|
|
50
|
-
'isNone',
|
|
51
|
-
'isNegative',
|
|
52
|
-
'isNeutral',
|
|
53
|
-
'isNotification',
|
|
54
|
-
'isNote',
|
|
55
|
-
'hasNote',
|
|
56
|
-
]);
|
|
57
|
-
// Map of negative terms to suggested positive alternatives
|
|
58
|
-
const POSITIVE_ALTERNATIVES = {
|
|
59
|
-
// Prefixes
|
|
6
|
+
// Common negative prefixes for boolean variables
|
|
7
|
+
const BOOLEAN_NEGATIVE_PREFIXES = ['not', 'no', 'non', 'un', 'in', 'dis'];
|
|
8
|
+
// Map of negative boolean terms to suggested positive alternatives
|
|
9
|
+
const BOOLEAN_POSITIVE_ALTERNATIVES = {
|
|
10
|
+
// Boolean prefixes
|
|
60
11
|
'isNot': ['is'],
|
|
61
12
|
'isUn': ['is'],
|
|
62
13
|
'isDis': ['is'],
|
|
@@ -68,54 +19,13 @@ const POSITIVE_ALTERNATIVES = {
|
|
|
68
19
|
'shouldNot': ['should'],
|
|
69
20
|
'willNot': ['will'],
|
|
70
21
|
'doesNot': ['does'],
|
|
71
|
-
// Words
|
|
72
|
-
'isInvalid': ['isValid'],
|
|
73
|
-
'isDisabled': ['isEnabled'],
|
|
74
|
-
'isIncomplete': ['isComplete'],
|
|
75
|
-
'isUnpaid': ['isPaid', 'hasPaid'],
|
|
76
|
-
'isInactive': ['isActive'],
|
|
77
|
-
'isDisallowed': ['isAllowed'],
|
|
78
|
-
'isUnauthorized': ['isAuthorized'],
|
|
79
|
-
'isUnverified': ['isVerified'],
|
|
80
|
-
'isImpossible': ['isPossible'],
|
|
81
|
-
'isError': ['isSuccess', 'isValid'],
|
|
82
|
-
'isBroken': ['isWorking', 'isFunctional'],
|
|
83
|
-
'isBanned': ['isAllowed', 'isPermitted'],
|
|
84
|
-
'isRestricted': ['isAllowed', 'isAccessible'],
|
|
85
|
-
'isBlocked': ['isAllowed', 'isAccessible'],
|
|
86
|
-
'isProhibited': ['isAllowed', 'isPermitted'],
|
|
87
|
-
'prevent': ['allow', 'enable'],
|
|
88
|
-
'avoid': ['use', 'prefer'],
|
|
89
|
-
'block': ['allow', 'permit'],
|
|
90
|
-
'deny': ['allow', 'grant'],
|
|
91
|
-
'reject': ['accept', 'approve'],
|
|
92
|
-
'exclude': ['include'],
|
|
93
|
-
'fail': ['succeed', 'pass'],
|
|
94
|
-
'missing': ['present', 'available'],
|
|
95
|
-
'forbidden': ['allowed', 'permitted'],
|
|
96
|
-
'disabled': ['enabled'],
|
|
97
|
-
'incomplete': ['complete'],
|
|
98
|
-
'invalid': ['valid'],
|
|
99
|
-
'disallowed': ['allowed'],
|
|
100
|
-
'unauthorized': ['authorized'],
|
|
101
|
-
'unverified': ['verified'],
|
|
102
|
-
'inactive': ['active'],
|
|
103
|
-
'disabledFeatures': ['enabledFeatures'],
|
|
104
|
-
'inactiveUsers': ['activeUsers'],
|
|
105
|
-
'disableFeature': ['enableFeature'],
|
|
106
|
-
'disableAccount': ['enableAccount'],
|
|
107
|
-
'blockUser': ['allowUser'],
|
|
108
|
-
'preventAccess': ['allowAccess', 'enableAccess'],
|
|
109
|
-
'restrictAccess': ['allowAccess', 'grantAccess'],
|
|
110
|
-
'limitations': ['capabilities', 'features'],
|
|
111
|
-
'limitation': ['capability', 'feature'],
|
|
112
22
|
};
|
|
113
23
|
exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
114
24
|
name: 'enforce-positive-naming',
|
|
115
25
|
meta: {
|
|
116
26
|
type: 'suggestion',
|
|
117
27
|
docs: {
|
|
118
|
-
description: 'Enforce positive
|
|
28
|
+
description: 'Enforce positive naming for boolean variables and avoid negations',
|
|
119
29
|
recommended: 'error',
|
|
120
30
|
},
|
|
121
31
|
schema: [],
|
|
@@ -125,76 +35,95 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
|
125
35
|
},
|
|
126
36
|
defaultOptions: [],
|
|
127
37
|
create(context) {
|
|
38
|
+
// Get the filename from the context
|
|
39
|
+
const filename = context.getFilename();
|
|
40
|
+
// Skip checking for files that should be ignored
|
|
41
|
+
// 1. Files that are not .ts or .tsx
|
|
42
|
+
// 2. Files starting with .
|
|
43
|
+
// 3. Files containing .config
|
|
44
|
+
// 4. Files containing rc suffix
|
|
45
|
+
if ((!filename.endsWith('.ts') && !filename.endsWith('.tsx')) ||
|
|
46
|
+
filename.split('/').pop()?.startsWith('.') ||
|
|
47
|
+
filename.includes('.config') ||
|
|
48
|
+
filename.includes('rc.') ||
|
|
49
|
+
filename.endsWith('rc')) {
|
|
50
|
+
// Return empty object to skip all checks for this file
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
128
53
|
/**
|
|
129
|
-
* Check if a name has negative
|
|
54
|
+
* Check if a name has boolean negative naming
|
|
130
55
|
*/
|
|
131
|
-
function
|
|
132
|
-
// Skip checking if the name is in the whitelist
|
|
133
|
-
if (ALLOWED_NEGATIVE_TERMS.has(name)) {
|
|
134
|
-
return { isNegative: false, alternatives: [] };
|
|
135
|
-
}
|
|
56
|
+
function hasBooleanNegativeNaming(name) {
|
|
136
57
|
// Check for exact matches in our alternatives map first
|
|
137
|
-
if (
|
|
138
|
-
return { isNegative: true, alternatives:
|
|
58
|
+
if (BOOLEAN_POSITIVE_ALTERNATIVES[name]) {
|
|
59
|
+
return { isNegative: true, alternatives: BOOLEAN_POSITIVE_ALTERNATIVES[name] };
|
|
139
60
|
}
|
|
140
|
-
// Check for negative prefixes
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
61
|
+
// Check for negative prefixes in boolean-like variables
|
|
62
|
+
if (name.startsWith('is') || name.startsWith('has') || name.startsWith('can') ||
|
|
63
|
+
name.startsWith('should') || name.startsWith('will') || name.startsWith('does')) {
|
|
64
|
+
for (const prefix of BOOLEAN_NEGATIVE_PREFIXES) {
|
|
65
|
+
// Check for patterns like isNot, hasNo, canNot, etc.
|
|
66
|
+
const prefixPatterns = [
|
|
67
|
+
{ pattern: new RegExp(`^is${prefix}`, 'i'), key: `is${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
68
|
+
{ pattern: new RegExp(`^has${prefix}`, 'i'), key: `has${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
69
|
+
{ pattern: new RegExp(`^can${prefix}`, 'i'), key: `can${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
70
|
+
{ pattern: new RegExp(`^should${prefix}`, 'i'), key: `should${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
71
|
+
{ pattern: new RegExp(`^will${prefix}`, 'i'), key: `will${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
72
|
+
{ pattern: new RegExp(`^does${prefix}`, 'i'), key: `does${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
73
|
+
];
|
|
74
|
+
for (const { pattern, key } of prefixPatterns) {
|
|
75
|
+
if (pattern.test(name)) {
|
|
76
|
+
// If we have a direct match for this pattern (like isNotVerified -> isVerified)
|
|
77
|
+
const directMatch = BOOLEAN_POSITIVE_ALTERNATIVES[name];
|
|
78
|
+
if (directMatch) {
|
|
79
|
+
return { isNegative: true, alternatives: directMatch };
|
|
80
|
+
}
|
|
81
|
+
const alternatives = BOOLEAN_POSITIVE_ALTERNATIVES[key] || [];
|
|
82
|
+
if (alternatives.length > 0) {
|
|
83
|
+
// Suggest the positive version with the rest of the name
|
|
84
|
+
const restOfName = name.replace(pattern, '');
|
|
85
|
+
const suggestedAlternatives = alternatives.map(alt => `${alt}${restOfName.charAt(0).toUpperCase() + restOfName.slice(1)}`);
|
|
86
|
+
return { isNegative: true, alternatives: suggestedAlternatives };
|
|
87
|
+
}
|
|
88
|
+
return { isNegative: true, alternatives: ['a positive alternative'] };
|
|
164
89
|
}
|
|
165
|
-
return { isNegative: true, alternatives: ['a positive alternative'] };
|
|
166
90
|
}
|
|
167
91
|
}
|
|
168
92
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
93
|
+
return { isNegative: false, alternatives: [] };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Safely formats alternatives for display
|
|
97
|
+
*/
|
|
98
|
+
function formatAlternatives(alternatives) {
|
|
99
|
+
if (Array.isArray(alternatives)) {
|
|
100
|
+
return alternatives.join(', ');
|
|
178
101
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// Otherwise suggest general alternatives for the negative word
|
|
190
|
-
const alternatives = POSITIVE_ALTERNATIVES[word] || [];
|
|
191
|
-
return {
|
|
192
|
-
isNegative: true,
|
|
193
|
-
alternatives: alternatives.length ? alternatives : ['a positive alternative']
|
|
194
|
-
};
|
|
195
|
-
}
|
|
102
|
+
return String(alternatives);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Check if a node is likely to be a boolean
|
|
106
|
+
*/
|
|
107
|
+
function isBooleanLike(node) {
|
|
108
|
+
// Check if the node has a boolean type annotation
|
|
109
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation &&
|
|
110
|
+
node.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
|
|
111
|
+
return true;
|
|
196
112
|
}
|
|
197
|
-
|
|
113
|
+
// Check if the node is initialized with a boolean literal
|
|
114
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
115
|
+
node.parent.init?.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
116
|
+
typeof node.parent.init.value === 'boolean') {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
// Check if the node has a name that suggests it's a boolean
|
|
120
|
+
if (node.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
121
|
+
(node.name.startsWith('is') || node.name.startsWith('has') ||
|
|
122
|
+
node.name.startsWith('can') || node.name.startsWith('should') ||
|
|
123
|
+
node.name.startsWith('will') || node.name.startsWith('does'))) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
198
127
|
}
|
|
199
128
|
/**
|
|
200
129
|
* Check variable declarations for negative naming
|
|
@@ -202,15 +131,18 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
|
202
131
|
function checkVariableDeclaration(node) {
|
|
203
132
|
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
204
133
|
return;
|
|
134
|
+
// Only check boolean-like variables
|
|
135
|
+
if (!isBooleanLike(node.id) && !isBooleanLike(node))
|
|
136
|
+
return;
|
|
205
137
|
const variableName = node.id.name;
|
|
206
|
-
const { isNegative, alternatives } =
|
|
138
|
+
const { isNegative, alternatives } = hasBooleanNegativeNaming(variableName);
|
|
207
139
|
if (isNegative) {
|
|
208
140
|
context.report({
|
|
209
141
|
node: node.id,
|
|
210
142
|
messageId: 'avoidNegativeNaming',
|
|
211
143
|
data: {
|
|
212
144
|
name: variableName,
|
|
213
|
-
alternatives: alternatives
|
|
145
|
+
alternatives: formatAlternatives(alternatives),
|
|
214
146
|
},
|
|
215
147
|
});
|
|
216
148
|
}
|
|
@@ -239,14 +171,17 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
|
239
171
|
}
|
|
240
172
|
if (!functionName)
|
|
241
173
|
return;
|
|
242
|
-
|
|
174
|
+
// Only check boolean-returning functions
|
|
175
|
+
if (!isBooleanLike(node.id || node))
|
|
176
|
+
return;
|
|
177
|
+
const { isNegative, alternatives } = hasBooleanNegativeNaming(functionName);
|
|
243
178
|
if (isNegative) {
|
|
244
179
|
context.report({
|
|
245
180
|
node: node.id || node,
|
|
246
181
|
messageId: 'avoidNegativeNaming',
|
|
247
182
|
data: {
|
|
248
183
|
name: functionName,
|
|
249
|
-
alternatives: alternatives
|
|
184
|
+
alternatives: formatAlternatives(alternatives),
|
|
250
185
|
},
|
|
251
186
|
});
|
|
252
187
|
}
|
|
@@ -257,15 +192,18 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
|
257
192
|
function checkMethodDefinition(node) {
|
|
258
193
|
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
259
194
|
return;
|
|
195
|
+
// Only check boolean-returning methods
|
|
196
|
+
if (!isBooleanLike(node.key))
|
|
197
|
+
return;
|
|
260
198
|
const methodName = node.key.name;
|
|
261
|
-
const { isNegative, alternatives } =
|
|
199
|
+
const { isNegative, alternatives } = hasBooleanNegativeNaming(methodName);
|
|
262
200
|
if (isNegative) {
|
|
263
201
|
context.report({
|
|
264
202
|
node: node.key,
|
|
265
203
|
messageId: 'avoidNegativeNaming',
|
|
266
204
|
data: {
|
|
267
205
|
name: methodName,
|
|
268
|
-
alternatives: alternatives
|
|
206
|
+
alternatives: formatAlternatives(alternatives),
|
|
269
207
|
},
|
|
270
208
|
});
|
|
271
209
|
}
|
|
@@ -276,15 +214,18 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
|
276
214
|
function checkProperty(node) {
|
|
277
215
|
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
278
216
|
return;
|
|
217
|
+
// Only check boolean properties
|
|
218
|
+
if (!isBooleanLike(node.key))
|
|
219
|
+
return;
|
|
279
220
|
const propertyName = node.key.name;
|
|
280
|
-
const { isNegative, alternatives } =
|
|
221
|
+
const { isNegative, alternatives } = hasBooleanNegativeNaming(propertyName);
|
|
281
222
|
if (isNegative) {
|
|
282
223
|
context.report({
|
|
283
224
|
node: node.key,
|
|
284
225
|
messageId: 'avoidNegativeNaming',
|
|
285
226
|
data: {
|
|
286
227
|
name: propertyName,
|
|
287
|
-
alternatives: alternatives
|
|
228
|
+
alternatives: formatAlternatives(alternatives),
|
|
288
229
|
},
|
|
289
230
|
});
|
|
290
231
|
}
|
|
@@ -295,15 +236,19 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
|
295
236
|
function checkPropertySignature(node) {
|
|
296
237
|
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
297
238
|
return;
|
|
239
|
+
// Only check boolean properties
|
|
240
|
+
if (!isBooleanLike(node.key) &&
|
|
241
|
+
!(node.typeAnnotation?.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword))
|
|
242
|
+
return;
|
|
298
243
|
const propertyName = node.key.name;
|
|
299
|
-
const { isNegative, alternatives } =
|
|
244
|
+
const { isNegative, alternatives } = hasBooleanNegativeNaming(propertyName);
|
|
300
245
|
if (isNegative) {
|
|
301
246
|
context.report({
|
|
302
247
|
node: node.key,
|
|
303
248
|
messageId: 'avoidNegativeNaming',
|
|
304
249
|
data: {
|
|
305
250
|
name: propertyName,
|
|
306
|
-
alternatives: alternatives
|
|
251
|
+
alternatives: formatAlternatives(alternatives),
|
|
307
252
|
},
|
|
308
253
|
});
|
|
309
254
|
}
|
|
@@ -314,15 +259,18 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
|
314
259
|
function checkParameter(node) {
|
|
315
260
|
if (node.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
316
261
|
return;
|
|
262
|
+
// Only check boolean parameters
|
|
263
|
+
if (!isBooleanLike(node))
|
|
264
|
+
return;
|
|
317
265
|
const paramName = node.name;
|
|
318
|
-
const { isNegative, alternatives } =
|
|
266
|
+
const { isNegative, alternatives } = hasBooleanNegativeNaming(paramName);
|
|
319
267
|
if (isNegative) {
|
|
320
268
|
context.report({
|
|
321
269
|
node,
|
|
322
270
|
messageId: 'avoidNegativeNaming',
|
|
323
271
|
data: {
|
|
324
272
|
name: paramName,
|
|
325
|
-
alternatives: alternatives
|
|
273
|
+
alternatives: formatAlternatives(alternatives),
|
|
326
274
|
},
|
|
327
275
|
});
|
|
328
276
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
type MessageIds = 'reactNodeShouldBeLowercase' | 'componentTypeShouldBeUppercase';
|
|
2
|
+
export declare const enforceReactTypeNaming: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
3
|
+
export {};
|