@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.
Files changed (41) hide show
  1. package/README.md +84 -39
  2. package/lib/index.js +50 -2
  3. package/lib/rules/enforce-assertSafe-object-key.js +1 -1
  4. package/lib/rules/enforce-css-media-queries.d.ts +5 -0
  5. package/lib/rules/enforce-css-media-queries.js +87 -0
  6. package/lib/rules/enforce-dynamic-imports.d.ts +9 -0
  7. package/lib/rules/enforce-dynamic-imports.js +84 -0
  8. package/lib/rules/enforce-id-capitalization.d.ts +6 -0
  9. package/lib/rules/enforce-id-capitalization.js +118 -0
  10. package/lib/rules/enforce-mui-rounded-icons.d.ts +1 -0
  11. package/lib/rules/enforce-mui-rounded-icons.js +54 -0
  12. package/lib/rules/enforce-object-literal-as-const.js +37 -0
  13. package/lib/rules/enforce-positive-naming.js +116 -168
  14. package/lib/rules/enforce-react-type-naming.d.ts +3 -0
  15. package/lib/rules/enforce-react-type-naming.js +191 -0
  16. package/lib/rules/enforce-singular-type-names.d.ts +2 -0
  17. package/lib/rules/enforce-singular-type-names.js +112 -0
  18. package/lib/rules/enforce-verb-noun-naming.js +5 -2
  19. package/lib/rules/ensure-pointer-events-none.d.ts +1 -0
  20. package/lib/rules/ensure-pointer-events-none.js +211 -0
  21. package/lib/rules/key-only-outermost-element.d.ts +3 -0
  22. package/lib/rules/key-only-outermost-element.js +152 -0
  23. package/lib/rules/no-always-true-false-conditions.js +19 -0
  24. package/lib/rules/no-circular-references.d.ts +1 -0
  25. package/lib/rules/no-circular-references.js +523 -0
  26. package/lib/rules/no-hungarian.d.ts +5 -0
  27. package/lib/rules/no-hungarian.js +223 -0
  28. package/lib/rules/no-object-values-on-strings.d.ts +2 -0
  29. package/lib/rules/no-object-values-on-strings.js +294 -0
  30. package/lib/rules/no-type-assertion-returns.js +143 -118
  31. package/lib/rules/no-unnecessary-destructuring.d.ts +5 -0
  32. package/lib/rules/no-unnecessary-destructuring.js +69 -0
  33. package/lib/rules/no-unused-props.js +109 -11
  34. package/lib/rules/no-unused-usestate.d.ts +8 -0
  35. package/lib/rules/no-unused-usestate.js +84 -0
  36. package/lib/rules/omit-index-html.d.ts +7 -0
  37. package/lib/rules/omit-index-html.js +91 -0
  38. package/lib/rules/prefer-usememo-over-useeffect-usestate.d.ts +5 -0
  39. package/lib/rules/prefer-usememo-over-useeffect-usestate.js +129 -0
  40. package/lib/rules/react-usememo-should-be-component.js +369 -2
  41. 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 and words to detect
7
- const NEGATIVE_PREFIXES = ['not', 'no', 'non', 'un', 'in', 'dis'];
8
- const NEGATIVE_WORDS = [
9
- 'invalid',
10
- 'disabled',
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 variable naming patterns and avoid negative naming',
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 connotations
54
+ * Check if a name has boolean negative naming
130
55
  */
131
- function hasNegativeNaming(name) {
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 (POSITIVE_ALTERNATIVES[name]) {
138
- return { isNegative: true, alternatives: POSITIVE_ALTERNATIVES[name] };
58
+ if (BOOLEAN_POSITIVE_ALTERNATIVES[name]) {
59
+ return { isNegative: true, alternatives: BOOLEAN_POSITIVE_ALTERNATIVES[name] };
139
60
  }
140
- // Check for negative prefixes
141
- for (const prefix of NEGATIVE_PREFIXES) {
142
- // Check for patterns like isNot, hasNo, canNot, etc.
143
- const prefixPatterns = [
144
- { pattern: new RegExp(`^is${prefix}`, 'i'), key: `is${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
145
- { pattern: new RegExp(`^has${prefix}`, 'i'), key: `has${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
146
- { pattern: new RegExp(`^can${prefix}`, 'i'), key: `can${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
147
- { pattern: new RegExp(`^should${prefix}`, 'i'), key: `should${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
148
- { pattern: new RegExp(`^will${prefix}`, 'i'), key: `will${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
149
- { pattern: new RegExp(`^does${prefix}`, 'i'), key: `does${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
150
- ];
151
- for (const { pattern, key } of prefixPatterns) {
152
- if (pattern.test(name)) {
153
- // If we have a direct match for this pattern (like isNotVerified -> isVerified)
154
- const directMatch = POSITIVE_ALTERNATIVES[name];
155
- if (directMatch) {
156
- return { isNegative: true, alternatives: directMatch };
157
- }
158
- const alternatives = POSITIVE_ALTERNATIVES[key] || [];
159
- if (alternatives.length > 0) {
160
- // Suggest the positive version with the rest of the name
161
- const restOfName = name.replace(pattern, '');
162
- const suggestedAlternatives = alternatives.map(alt => `${alt}${restOfName.charAt(0).toUpperCase() + restOfName.slice(1)}`);
163
- return { isNegative: true, alternatives: suggestedAlternatives };
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
- // Check for exact negative words
170
- for (const word of NEGATIVE_WORDS) {
171
- if (name.toLowerCase() === word.toLowerCase()) {
172
- const alternatives = POSITIVE_ALTERNATIVES[word] || [];
173
- return {
174
- isNegative: true,
175
- alternatives: alternatives.length ? alternatives : ['a positive alternative']
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
- // Check for negative words in camelCase/PascalCase
180
- for (const word of NEGATIVE_WORDS) {
181
- // Match the word at the beginning or after a capital letter
182
- const pattern = new RegExp(`(^|[A-Z])${word}($|[A-Z])`, 'i');
183
- if (pattern.test(name)) {
184
- // For compound names like isInvalid, check if we have a specific suggestion
185
- const exactMatch = `is${word.charAt(0).toUpperCase() + word.slice(1)}`;
186
- if (POSITIVE_ALTERNATIVES[exactMatch]) {
187
- return { isNegative: true, alternatives: POSITIVE_ALTERNATIVES[exactMatch] };
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
- return { isNegative: false, alternatives: [] };
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 } = hasNegativeNaming(variableName);
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.join(', '),
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
- const { isNegative, alternatives } = hasNegativeNaming(functionName);
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.join(', '),
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 } = hasNegativeNaming(methodName);
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.join(', '),
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 } = hasNegativeNaming(propertyName);
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.join(', '),
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 } = hasNegativeNaming(propertyName);
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.join(', '),
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 } = hasNegativeNaming(paramName);
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.join(', '),
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 {};