@blumintinc/eslint-plugin-blumint 1.7.3 → 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 +83 -61
- package/lib/index.js +85 -1
- package/lib/rules/enforce-assert-throws.js +48 -3
- package/lib/rules/enforce-assertSafe-object-key.d.ts +2 -0
- package/lib/rules/enforce-assertSafe-object-key.js +284 -0
- package/lib/rules/enforce-centralized-mock-firestore.js +192 -14
- 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-firestore-facade.js +8 -0
- package/lib/rules/enforce-id-capitalization.d.ts +6 -0
- package/lib/rules/enforce-id-capitalization.js +78 -0
- package/lib/rules/enforce-microdiff.d.ts +3 -0
- package/lib/rules/enforce-microdiff.js +379 -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.d.ts +4 -0
- package/lib/rules/enforce-object-literal-as-const.js +88 -0
- package/lib/rules/enforce-positive-naming.d.ts +1 -0
- package/lib/rules/enforce-positive-naming.js +387 -0
- package/lib/rules/enforce-props-argument-name.d.ts +8 -0
- package/lib/rules/enforce-props-argument-name.js +182 -0
- 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-render-hits-memoization.js +155 -26
- 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-timestamp-now.d.ts +1 -0
- package/lib/rules/enforce-timestamp-now.js +223 -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/extract-global-constants.d.ts +1 -1
- package/lib/rules/extract-global-constants.js +109 -1
- package/lib/rules/global-const-style.js +3 -2
- 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.d.ts +3 -0
- package/lib/rules/no-always-true-false-conditions.js +1221 -0
- package/lib/rules/no-circular-references.d.ts +1 -0
- package/lib/rules/no-circular-references.js +523 -0
- package/lib/rules/no-firestore-jest-mock.js +27 -4
- package/lib/rules/no-hungarian.d.ts +5 -0
- package/lib/rules/no-hungarian.js +223 -0
- package/lib/rules/no-mock-firebase-admin.js +20 -7
- 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.d.ts +9 -0
- package/lib/rules/no-type-assertion-returns.js +299 -0
- package/lib/rules/no-unnecessary-destructuring.d.ts +5 -0
- package/lib/rules/no-unnecessary-destructuring.js +69 -0
- package/lib/rules/no-unnecessary-verb-suffix.d.ts +1 -0
- package/lib/rules/no-unnecessary-verb-suffix.js +203 -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-clone-deep.js +294 -22
- package/lib/rules/prefer-fragment-component.js +265 -34
- package/lib/rules/prefer-global-router-state-key.d.ts +5 -0
- package/lib/rules/prefer-global-router-state-key.js +89 -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/prefer-utility-function-over-private-static.d.ts +1 -0
- package/lib/rules/prefer-utility-function-over-private-static.js +77 -0
- package/lib/rules/react-usememo-should-be-component.d.ts +1 -0
- package/lib/rules/react-usememo-should-be-component.js +623 -0
- package/package.json +10 -7
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforceObjectLiteralAsConst = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
exports.enforceObjectLiteralAsConst = (0, createRule_1.createRule)({
|
|
6
|
+
name: 'enforce-object-literal-as-const',
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'suggestion',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Enforce that object literals returned from functions should be marked with `as const` to ensure type safety and immutability.',
|
|
11
|
+
recommended: 'error',
|
|
12
|
+
},
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
messages: {
|
|
15
|
+
enforceAsConst: 'Object literals returned from functions should be marked with `as const` to ensure type safety and immutability',
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
},
|
|
19
|
+
defaultOptions: [],
|
|
20
|
+
create(context) {
|
|
21
|
+
return {
|
|
22
|
+
ReturnStatement(node) {
|
|
23
|
+
// Skip if there's no argument in the return statement
|
|
24
|
+
if (!node.argument) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Check if the return statement is inside a function
|
|
28
|
+
const sourceCode = context.getSourceCode();
|
|
29
|
+
// Use context.getAncestors() for now, but note it's deprecated
|
|
30
|
+
// We'll need to update this when upgrading to ESLint v9
|
|
31
|
+
const ancestors = context.getAncestors();
|
|
32
|
+
const isInFunction = ancestors.some((ancestor) => ancestor.type === 'FunctionDeclaration' ||
|
|
33
|
+
ancestor.type === 'ArrowFunctionExpression' ||
|
|
34
|
+
ancestor.type === 'FunctionExpression' ||
|
|
35
|
+
ancestor.type === 'MethodDefinition');
|
|
36
|
+
if (!isInFunction) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Check if the return value is an object or array literal
|
|
40
|
+
const { argument } = node;
|
|
41
|
+
// Skip if the return value already has 'as const' assertion
|
|
42
|
+
if (argument.type === 'TSAsExpression') {
|
|
43
|
+
const tsAsExpression = argument;
|
|
44
|
+
// Check if the type annotation is 'const'
|
|
45
|
+
if (tsAsExpression.typeAnnotation.type === 'TSTypeReference' &&
|
|
46
|
+
tsAsExpression.typeAnnotation.typeName.type === 'Identifier' &&
|
|
47
|
+
tsAsExpression.typeAnnotation.typeName.name === 'const') {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// If it has another type assertion but not 'as const', we still need to check
|
|
51
|
+
// if the expression is an object/array literal
|
|
52
|
+
if (tsAsExpression.expression.type !== 'ObjectExpression' &&
|
|
53
|
+
tsAsExpression.expression.type !== 'ArrayExpression') {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (argument.type !== 'ObjectExpression' &&
|
|
58
|
+
argument.type !== 'ArrayExpression') {
|
|
59
|
+
// Skip if not an object/array literal and not a type assertion
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Skip if the return value uses spread operator
|
|
63
|
+
if ((argument.type === 'ObjectExpression' &&
|
|
64
|
+
argument.properties.some((prop) => prop.type === 'SpreadElement')) ||
|
|
65
|
+
(argument.type === 'ArrayExpression' &&
|
|
66
|
+
argument.elements.some((elem) => elem !== null && elem.type === 'SpreadElement'))) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Report the issue and provide a fix
|
|
70
|
+
context.report({
|
|
71
|
+
node,
|
|
72
|
+
messageId: 'enforceAsConst',
|
|
73
|
+
fix(fixer) {
|
|
74
|
+
const text = sourceCode.getText(argument);
|
|
75
|
+
// If it's already a type assertion but not 'as const'
|
|
76
|
+
if (argument.type === 'TSAsExpression') {
|
|
77
|
+
// Get the expression part (before the 'as')
|
|
78
|
+
const expressionText = sourceCode.getText(argument.expression);
|
|
79
|
+
return fixer.replaceText(argument, `${expressionText} as const`);
|
|
80
|
+
}
|
|
81
|
+
return fixer.replaceText(argument, `${text} as const`);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=enforce-object-literal-as-const.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const enforcePositiveNaming: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"avoidNegativeNaming", [], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforcePositiveNaming = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
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
|
|
60
|
+
'isNot': ['is'],
|
|
61
|
+
'isUn': ['is'],
|
|
62
|
+
'isDis': ['is'],
|
|
63
|
+
'isIn': ['is'],
|
|
64
|
+
'isNon': ['is'],
|
|
65
|
+
'hasNo': ['has'],
|
|
66
|
+
'hasNot': ['has'],
|
|
67
|
+
'canNot': ['can'],
|
|
68
|
+
'shouldNot': ['should'],
|
|
69
|
+
'willNot': ['will'],
|
|
70
|
+
'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
|
+
};
|
|
113
|
+
exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
114
|
+
name: 'enforce-positive-naming',
|
|
115
|
+
meta: {
|
|
116
|
+
type: 'suggestion',
|
|
117
|
+
docs: {
|
|
118
|
+
description: 'Enforce positive variable naming patterns and avoid negative naming',
|
|
119
|
+
recommended: 'error',
|
|
120
|
+
},
|
|
121
|
+
schema: [],
|
|
122
|
+
messages: {
|
|
123
|
+
avoidNegativeNaming: 'Avoid negative naming "{{name}}". Consider using a positive alternative like: {{alternatives}}',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
defaultOptions: [],
|
|
127
|
+
create(context) {
|
|
128
|
+
// Get the filename from the context
|
|
129
|
+
const filename = context.getFilename();
|
|
130
|
+
// Skip checking for files that should be ignored
|
|
131
|
+
// 1. Files that are not .ts or .tsx
|
|
132
|
+
// 2. Files starting with .
|
|
133
|
+
// 3. Files containing .config
|
|
134
|
+
// 4. Files containing rc suffix
|
|
135
|
+
if ((!filename.endsWith('.ts') && !filename.endsWith('.tsx')) ||
|
|
136
|
+
filename.split('/').pop()?.startsWith('.') ||
|
|
137
|
+
filename.includes('.config') ||
|
|
138
|
+
filename.includes('rc.') ||
|
|
139
|
+
filename.endsWith('rc')) {
|
|
140
|
+
// Return empty object to skip all checks for this file
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if a name has negative connotations
|
|
145
|
+
*/
|
|
146
|
+
function hasNegativeNaming(name) {
|
|
147
|
+
// Skip checking if the name is in the whitelist
|
|
148
|
+
if (ALLOWED_NEGATIVE_TERMS.has(name)) {
|
|
149
|
+
return { isNegative: false, alternatives: [] };
|
|
150
|
+
}
|
|
151
|
+
// Check for exact matches in our alternatives map first
|
|
152
|
+
if (POSITIVE_ALTERNATIVES[name]) {
|
|
153
|
+
return { isNegative: true, alternatives: POSITIVE_ALTERNATIVES[name] };
|
|
154
|
+
}
|
|
155
|
+
// Check for negative prefixes
|
|
156
|
+
for (const prefix of NEGATIVE_PREFIXES) {
|
|
157
|
+
// Check for patterns like isNot, hasNo, canNot, etc.
|
|
158
|
+
const prefixPatterns = [
|
|
159
|
+
{ pattern: new RegExp(`^is${prefix}`, 'i'), key: `is${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
160
|
+
{ pattern: new RegExp(`^has${prefix}`, 'i'), key: `has${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
161
|
+
{ pattern: new RegExp(`^can${prefix}`, 'i'), key: `can${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
162
|
+
{ pattern: new RegExp(`^should${prefix}`, 'i'), key: `should${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
163
|
+
{ pattern: new RegExp(`^will${prefix}`, 'i'), key: `will${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
164
|
+
{ pattern: new RegExp(`^does${prefix}`, 'i'), key: `does${prefix.charAt(0).toUpperCase() + prefix.slice(1)}` },
|
|
165
|
+
];
|
|
166
|
+
for (const { pattern, key } of prefixPatterns) {
|
|
167
|
+
if (pattern.test(name)) {
|
|
168
|
+
// If we have a direct match for this pattern (like isNotVerified -> isVerified)
|
|
169
|
+
const directMatch = POSITIVE_ALTERNATIVES[name];
|
|
170
|
+
if (directMatch) {
|
|
171
|
+
return { isNegative: true, alternatives: directMatch };
|
|
172
|
+
}
|
|
173
|
+
const alternatives = POSITIVE_ALTERNATIVES[key] || [];
|
|
174
|
+
if (alternatives.length > 0) {
|
|
175
|
+
// Suggest the positive version with the rest of the name
|
|
176
|
+
const restOfName = name.replace(pattern, '');
|
|
177
|
+
const suggestedAlternatives = alternatives.map(alt => `${alt}${restOfName.charAt(0).toUpperCase() + restOfName.slice(1)}`);
|
|
178
|
+
return { isNegative: true, alternatives: suggestedAlternatives };
|
|
179
|
+
}
|
|
180
|
+
return { isNegative: true, alternatives: ['a positive alternative'] };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Check for exact negative words
|
|
185
|
+
for (const word of NEGATIVE_WORDS) {
|
|
186
|
+
if (name.toLowerCase() === word.toLowerCase()) {
|
|
187
|
+
const alternatives = POSITIVE_ALTERNATIVES[word] || [];
|
|
188
|
+
return {
|
|
189
|
+
isNegative: true,
|
|
190
|
+
alternatives: alternatives.length ? alternatives : ['a positive alternative']
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Check for negative words in camelCase/PascalCase
|
|
195
|
+
for (const word of NEGATIVE_WORDS) {
|
|
196
|
+
// Match the word at the beginning or after a capital letter
|
|
197
|
+
const pattern = new RegExp(`(^|[A-Z])${word}($|[A-Z])`, 'i');
|
|
198
|
+
if (pattern.test(name)) {
|
|
199
|
+
// For compound names like isInvalid, check if we have a specific suggestion
|
|
200
|
+
const exactMatch = `is${word.charAt(0).toUpperCase() + word.slice(1)}`;
|
|
201
|
+
if (POSITIVE_ALTERNATIVES[exactMatch]) {
|
|
202
|
+
return { isNegative: true, alternatives: POSITIVE_ALTERNATIVES[exactMatch] };
|
|
203
|
+
}
|
|
204
|
+
// Otherwise suggest general alternatives for the negative word
|
|
205
|
+
const alternatives = POSITIVE_ALTERNATIVES[word] || [];
|
|
206
|
+
return {
|
|
207
|
+
isNegative: true,
|
|
208
|
+
alternatives: alternatives.length ? alternatives : ['a positive alternative']
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return { isNegative: false, alternatives: [] };
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Safely formats alternatives for display
|
|
216
|
+
*/
|
|
217
|
+
function formatAlternatives(alternatives) {
|
|
218
|
+
if (Array.isArray(alternatives)) {
|
|
219
|
+
return alternatives.join(', ');
|
|
220
|
+
}
|
|
221
|
+
return String(alternatives);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Check variable declarations for negative naming
|
|
225
|
+
*/
|
|
226
|
+
function checkVariableDeclaration(node) {
|
|
227
|
+
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
228
|
+
return;
|
|
229
|
+
const variableName = node.id.name;
|
|
230
|
+
const { isNegative, alternatives } = hasNegativeNaming(variableName);
|
|
231
|
+
if (isNegative) {
|
|
232
|
+
context.report({
|
|
233
|
+
node: node.id,
|
|
234
|
+
messageId: 'avoidNegativeNaming',
|
|
235
|
+
data: {
|
|
236
|
+
name: variableName,
|
|
237
|
+
alternatives: formatAlternatives(alternatives),
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Check function declarations for negative naming
|
|
244
|
+
*/
|
|
245
|
+
function checkFunctionDeclaration(node) {
|
|
246
|
+
// Skip anonymous functions
|
|
247
|
+
if (!node.id && node.parent?.type !== utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// Get function name from either the function declaration or variable declarator
|
|
251
|
+
let functionName = '';
|
|
252
|
+
if (node.id) {
|
|
253
|
+
functionName = node.id.name;
|
|
254
|
+
}
|
|
255
|
+
else if (node.parent?.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
256
|
+
node.parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
257
|
+
functionName = node.parent.id.name;
|
|
258
|
+
}
|
|
259
|
+
else if (node.parent?.type === utils_1.AST_NODE_TYPES.Property &&
|
|
260
|
+
node.parent.key.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
261
|
+
// Handle object method shorthand
|
|
262
|
+
functionName = node.parent.key.name;
|
|
263
|
+
}
|
|
264
|
+
if (!functionName)
|
|
265
|
+
return;
|
|
266
|
+
const { isNegative, alternatives } = hasNegativeNaming(functionName);
|
|
267
|
+
if (isNegative) {
|
|
268
|
+
context.report({
|
|
269
|
+
node: node.id || node,
|
|
270
|
+
messageId: 'avoidNegativeNaming',
|
|
271
|
+
data: {
|
|
272
|
+
name: functionName,
|
|
273
|
+
alternatives: formatAlternatives(alternatives),
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Check method definitions for negative naming
|
|
280
|
+
*/
|
|
281
|
+
function checkMethodDefinition(node) {
|
|
282
|
+
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
283
|
+
return;
|
|
284
|
+
const methodName = node.key.name;
|
|
285
|
+
const { isNegative, alternatives } = hasNegativeNaming(methodName);
|
|
286
|
+
if (isNegative) {
|
|
287
|
+
context.report({
|
|
288
|
+
node: node.key,
|
|
289
|
+
messageId: 'avoidNegativeNaming',
|
|
290
|
+
data: {
|
|
291
|
+
name: methodName,
|
|
292
|
+
alternatives: formatAlternatives(alternatives),
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Check property definitions for negative naming
|
|
299
|
+
*/
|
|
300
|
+
function checkProperty(node) {
|
|
301
|
+
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
302
|
+
return;
|
|
303
|
+
const propertyName = node.key.name;
|
|
304
|
+
const { isNegative, alternatives } = hasNegativeNaming(propertyName);
|
|
305
|
+
if (isNegative) {
|
|
306
|
+
context.report({
|
|
307
|
+
node: node.key,
|
|
308
|
+
messageId: 'avoidNegativeNaming',
|
|
309
|
+
data: {
|
|
310
|
+
name: propertyName,
|
|
311
|
+
alternatives: formatAlternatives(alternatives),
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Check TSPropertySignature for negative naming (in interfaces)
|
|
318
|
+
*/
|
|
319
|
+
function checkPropertySignature(node) {
|
|
320
|
+
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
321
|
+
return;
|
|
322
|
+
const propertyName = node.key.name;
|
|
323
|
+
const { isNegative, alternatives } = hasNegativeNaming(propertyName);
|
|
324
|
+
if (isNegative) {
|
|
325
|
+
context.report({
|
|
326
|
+
node: node.key,
|
|
327
|
+
messageId: 'avoidNegativeNaming',
|
|
328
|
+
data: {
|
|
329
|
+
name: propertyName,
|
|
330
|
+
alternatives: formatAlternatives(alternatives),
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Check parameter names for negative naming
|
|
337
|
+
*/
|
|
338
|
+
function checkParameter(node) {
|
|
339
|
+
if (node.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
340
|
+
return;
|
|
341
|
+
const paramName = node.name;
|
|
342
|
+
const { isNegative, alternatives } = hasNegativeNaming(paramName);
|
|
343
|
+
if (isNegative) {
|
|
344
|
+
context.report({
|
|
345
|
+
node,
|
|
346
|
+
messageId: 'avoidNegativeNaming',
|
|
347
|
+
data: {
|
|
348
|
+
name: paramName,
|
|
349
|
+
alternatives: formatAlternatives(alternatives),
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
VariableDeclarator: checkVariableDeclaration,
|
|
356
|
+
FunctionDeclaration: checkFunctionDeclaration,
|
|
357
|
+
// Only check function expressions when they're not part of a variable declaration
|
|
358
|
+
// to avoid duplicate errors
|
|
359
|
+
FunctionExpression(node) {
|
|
360
|
+
if (node.parent?.type !== utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
361
|
+
checkFunctionDeclaration(node);
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
// Only check arrow function expressions when they're not part of a variable declaration
|
|
365
|
+
// to avoid duplicate errors
|
|
366
|
+
ArrowFunctionExpression(node) {
|
|
367
|
+
if (node.parent?.type !== utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
368
|
+
checkFunctionDeclaration(node);
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
MethodDefinition: checkMethodDefinition,
|
|
372
|
+
Property: checkProperty,
|
|
373
|
+
TSPropertySignature: checkPropertySignature,
|
|
374
|
+
Identifier(node) {
|
|
375
|
+
// Check parameter names in function declarations
|
|
376
|
+
if (node.parent &&
|
|
377
|
+
(node.parent.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
378
|
+
node.parent.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
379
|
+
node.parent.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) &&
|
|
380
|
+
node.parent.params.includes(node)) {
|
|
381
|
+
checkParameter(node);
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
//# sourceMappingURL=enforce-positive-naming.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type Options = [
|
|
2
|
+
{
|
|
3
|
+
enforceDestructuring?: boolean;
|
|
4
|
+
ignoreExternalInterfaces?: boolean;
|
|
5
|
+
}
|
|
6
|
+
];
|
|
7
|
+
export declare const enforcePropsArgumentName: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"usePropsForType", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enforcePropsArgumentName = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
enforceDestructuring: false,
|
|
8
|
+
ignoreExternalInterfaces: true,
|
|
9
|
+
};
|
|
10
|
+
exports.enforcePropsArgumentName = (0, createRule_1.createRule)({
|
|
11
|
+
name: 'enforce-props-argument-name',
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'suggestion',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Enforce using "Props" suffix in type names for parameter objects',
|
|
16
|
+
recommended: 'error',
|
|
17
|
+
},
|
|
18
|
+
fixable: 'code',
|
|
19
|
+
schema: [
|
|
20
|
+
{
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
enforceDestructuring: {
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
},
|
|
26
|
+
ignoreExternalInterfaces: {
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
additionalProperties: false,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
messages: {
|
|
34
|
+
usePropsForType: 'Use "Props" suffix in type name instead of "{{ typeSuffix }}"',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
defaultOptions: [defaultOptions],
|
|
38
|
+
create(context, [options]) {
|
|
39
|
+
const finalOptions = { ...defaultOptions, ...options };
|
|
40
|
+
// Check if a node is from an external library
|
|
41
|
+
function isFromExternalLibrary(node) {
|
|
42
|
+
if (!finalOptions.ignoreExternalInterfaces) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
let current = node;
|
|
46
|
+
while (current.parent) {
|
|
47
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
48
|
+
// Check if the interface extends something from an external library
|
|
49
|
+
const interfaceDecl = current;
|
|
50
|
+
if (interfaceDecl.extends && interfaceDecl.extends.length > 0) {
|
|
51
|
+
// Check if any of the extended interfaces are from external libraries
|
|
52
|
+
// This is a simplified check - a more robust implementation would trace imports
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
current = current.parent;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
// Extract type name from a type annotation
|
|
61
|
+
function getTypeName(typeAnnotation) {
|
|
62
|
+
if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
63
|
+
const typeName = typeAnnotation.typeName;
|
|
64
|
+
if (typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
65
|
+
return typeName.name;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// Check if a type name has a non-"Props" suffix
|
|
71
|
+
function hasNonPropsSuffix(typeName) {
|
|
72
|
+
if (typeName.endsWith('Props')) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const suffixes = ['Config', 'Settings', 'Options', 'Params', 'Parameters', 'Args', 'Arguments'];
|
|
76
|
+
for (const suffix of suffixes) {
|
|
77
|
+
if (typeName.endsWith(suffix)) {
|
|
78
|
+
return suffix;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
// Fix type name
|
|
84
|
+
function fixTypeName(fixer, node, oldName, suffix) {
|
|
85
|
+
if (node.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
|
|
86
|
+
node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
87
|
+
const baseName = oldName.slice(0, -suffix.length);
|
|
88
|
+
return fixer.replaceText(node.typeName, `${baseName}Props`);
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
// Check function parameters
|
|
93
|
+
function checkFunctionParams(node) {
|
|
94
|
+
if (node.params.length !== 1) {
|
|
95
|
+
return; // Only check functions with a single parameter
|
|
96
|
+
}
|
|
97
|
+
const param = node.params[0];
|
|
98
|
+
// Skip primitive parameters
|
|
99
|
+
if (param.type === utils_1.AST_NODE_TYPES.Identifier && param.typeAnnotation && (param.typeAnnotation.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSStringKeyword ||
|
|
100
|
+
param.typeAnnotation.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSNumberKeyword ||
|
|
101
|
+
param.typeAnnotation.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Check if the parameter has a type annotation
|
|
105
|
+
if (param.type === utils_1.AST_NODE_TYPES.Identifier && param.typeAnnotation && param.typeAnnotation.typeAnnotation) {
|
|
106
|
+
const typeName = getTypeName(param.typeAnnotation.typeAnnotation);
|
|
107
|
+
if (typeName) {
|
|
108
|
+
const nonPropsSuffix = hasNonPropsSuffix(typeName);
|
|
109
|
+
if (nonPropsSuffix) {
|
|
110
|
+
context.report({
|
|
111
|
+
node: param.typeAnnotation.typeAnnotation,
|
|
112
|
+
messageId: 'usePropsForType',
|
|
113
|
+
data: { typeSuffix: nonPropsSuffix },
|
|
114
|
+
fix: (fixer) => fixTypeName(fixer, param.typeAnnotation.typeAnnotation, typeName, nonPropsSuffix),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Check class constructor parameters
|
|
121
|
+
function checkClassConstructor(node) {
|
|
122
|
+
if (node.kind !== 'constructor') {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const constructor = node.value;
|
|
126
|
+
if (constructor.params.length !== 1) {
|
|
127
|
+
return; // Only check constructors with a single parameter
|
|
128
|
+
}
|
|
129
|
+
const param = constructor.params[0];
|
|
130
|
+
// Skip primitive parameters
|
|
131
|
+
if (param.type === utils_1.AST_NODE_TYPES.Identifier && param.typeAnnotation && (param.typeAnnotation.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSStringKeyword ||
|
|
132
|
+
param.typeAnnotation.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSNumberKeyword ||
|
|
133
|
+
param.typeAnnotation.typeAnnotation.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Check if the parameter has a type annotation
|
|
137
|
+
if (param.type === utils_1.AST_NODE_TYPES.Identifier && param.typeAnnotation && param.typeAnnotation.typeAnnotation) {
|
|
138
|
+
const typeName = getTypeName(param.typeAnnotation.typeAnnotation);
|
|
139
|
+
if (typeName) {
|
|
140
|
+
const nonPropsSuffix = hasNonPropsSuffix(typeName);
|
|
141
|
+
if (nonPropsSuffix) {
|
|
142
|
+
context.report({
|
|
143
|
+
node: param.typeAnnotation.typeAnnotation,
|
|
144
|
+
messageId: 'usePropsForType',
|
|
145
|
+
data: { typeSuffix: nonPropsSuffix },
|
|
146
|
+
fix: (fixer) => fixTypeName(fixer, param.typeAnnotation.typeAnnotation, typeName, nonPropsSuffix),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Check type definitions
|
|
153
|
+
function checkTypeDefinition(node) {
|
|
154
|
+
if (!node.id || !node.id.name) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const typeName = node.id.name;
|
|
158
|
+
const nonPropsSuffix = hasNonPropsSuffix(typeName);
|
|
159
|
+
if (nonPropsSuffix && !isFromExternalLibrary(node)) {
|
|
160
|
+
const baseName = typeName.slice(0, -nonPropsSuffix.length);
|
|
161
|
+
const newTypeName = `${baseName}Props`;
|
|
162
|
+
context.report({
|
|
163
|
+
node: node.id,
|
|
164
|
+
messageId: 'usePropsForType',
|
|
165
|
+
data: { typeSuffix: nonPropsSuffix },
|
|
166
|
+
fix: (fixer) => {
|
|
167
|
+
return fixer.replaceText(node.id, newTypeName);
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
FunctionDeclaration: checkFunctionParams,
|
|
174
|
+
FunctionExpression: checkFunctionParams,
|
|
175
|
+
ArrowFunctionExpression: checkFunctionParams,
|
|
176
|
+
TSMethodSignature: checkFunctionParams,
|
|
177
|
+
MethodDefinition: checkClassConstructor,
|
|
178
|
+
TSTypeAliasDeclaration: checkTypeDefinition,
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
//# sourceMappingURL=enforce-props-argument-name.js.map
|
|
@@ -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 {};
|