@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,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noHungarian = void 0;
|
|
4
|
+
const createRule_1 = require("../utils/createRule");
|
|
5
|
+
// Common built-in types that might be used in Hungarian notation
|
|
6
|
+
const COMMON_TYPES = [
|
|
7
|
+
'String', 'Number', 'Boolean', 'Array', 'Object',
|
|
8
|
+
'Function', 'Date', 'RegExp', 'Promise', 'Map',
|
|
9
|
+
'Set', 'Symbol', 'BigInt', 'Error'
|
|
10
|
+
];
|
|
11
|
+
// Common Hungarian notation prefixes
|
|
12
|
+
const HUNGARIAN_PREFIXES = [
|
|
13
|
+
'str', 'num', 'int', 'bool', 'arr', 'obj', 'fn', 'func'
|
|
14
|
+
];
|
|
15
|
+
exports.noHungarian = (0, createRule_1.createRule)({
|
|
16
|
+
name: 'no-hungarian',
|
|
17
|
+
meta: {
|
|
18
|
+
type: 'suggestion',
|
|
19
|
+
docs: {
|
|
20
|
+
description: 'Disallow Hungarian notation in variable names',
|
|
21
|
+
recommended: 'error',
|
|
22
|
+
},
|
|
23
|
+
fixable: 'code',
|
|
24
|
+
schema: [
|
|
25
|
+
{
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
allowClassInstances: {
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
additionalProperties: false,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
messages: {
|
|
36
|
+
noHungarian: 'Avoid Hungarian notation in variable name "{{name}}"',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
defaultOptions: [
|
|
40
|
+
{
|
|
41
|
+
allowClassInstances: true,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
create(context, [options]) {
|
|
45
|
+
const allowClassInstances = options.allowClassInstances !== false;
|
|
46
|
+
// Helper function to extract the class/type name from a node
|
|
47
|
+
function getTypeName(node) {
|
|
48
|
+
if (!node)
|
|
49
|
+
return null;
|
|
50
|
+
// Handle new expressions (e.g., new Controller())
|
|
51
|
+
if (node.type === 'NewExpression' && node.callee.type === 'Identifier') {
|
|
52
|
+
return node.callee.name;
|
|
53
|
+
}
|
|
54
|
+
// Handle type annotations if available
|
|
55
|
+
if (node.type === 'TSTypeAnnotation') {
|
|
56
|
+
const typeNode = node.typeAnnotation;
|
|
57
|
+
if (typeNode.type === 'TSStringKeyword')
|
|
58
|
+
return 'String';
|
|
59
|
+
if (typeNode.type === 'TSNumberKeyword')
|
|
60
|
+
return 'Number';
|
|
61
|
+
if (typeNode.type === 'TSBooleanKeyword')
|
|
62
|
+
return 'Boolean';
|
|
63
|
+
if (typeNode.type === 'TSArrayType')
|
|
64
|
+
return 'Array';
|
|
65
|
+
if (typeNode.type === 'TSObjectKeyword')
|
|
66
|
+
return 'Object';
|
|
67
|
+
if (typeNode.type === 'TSFunctionType')
|
|
68
|
+
return 'Function';
|
|
69
|
+
// Handle reference types like interfaces or classes
|
|
70
|
+
if (typeNode.type === 'TSTypeReference' && typeNode.typeName.type === 'Identifier') {
|
|
71
|
+
return typeNode.typeName.name;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
// Check if a variable name has a type name as suffix (Hungarian notation)
|
|
77
|
+
function hasTypeSuffix(variableName, typeName) {
|
|
78
|
+
// Normalize both strings to lowercase for case-insensitive comparison
|
|
79
|
+
const normalizedVarName = variableName.toLowerCase();
|
|
80
|
+
const normalizedTypeName = typeName.toLowerCase();
|
|
81
|
+
// Check if the variable name ends with the type name (suffix)
|
|
82
|
+
return normalizedVarName.endsWith(normalizedTypeName) &&
|
|
83
|
+
normalizedVarName !== normalizedTypeName;
|
|
84
|
+
}
|
|
85
|
+
// Check if a variable name has a Hungarian prefix
|
|
86
|
+
function hasHungarianPrefix(variableName) {
|
|
87
|
+
const normalizedVarName = variableName.toLowerCase();
|
|
88
|
+
return HUNGARIAN_PREFIXES.some(prefix => {
|
|
89
|
+
// Check if the variable starts with the prefix
|
|
90
|
+
return normalizedVarName.startsWith(prefix.toLowerCase()) &&
|
|
91
|
+
// Make sure it's not just the prefix itself
|
|
92
|
+
normalizedVarName.length > prefix.length;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Check if a variable name uses Hungarian notation
|
|
96
|
+
function isHungarianNotation(variableName, declaredTypeName) {
|
|
97
|
+
// Check for Hungarian prefixes (e.g., strName, boolIsActive)
|
|
98
|
+
if (hasHungarianPrefix(variableName)) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
// Check for type suffixes (e.g., nameString, countNumber)
|
|
102
|
+
// If we have a declared type, check if it's used as a suffix
|
|
103
|
+
if (declaredTypeName && hasTypeSuffix(variableName, declaredTypeName)) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
// Check against common types if no declared type is found
|
|
107
|
+
return COMMON_TYPES.some(typeName => hasTypeSuffix(variableName, typeName));
|
|
108
|
+
}
|
|
109
|
+
// Helper function to check if a variable name contains a class name
|
|
110
|
+
function variableContainsClassName(varName, className) {
|
|
111
|
+
return varName.toLowerCase().includes(className.toLowerCase());
|
|
112
|
+
}
|
|
113
|
+
// Helper function to check if a node is a class property or method
|
|
114
|
+
function isClassProperty(node) {
|
|
115
|
+
let current = node.parent;
|
|
116
|
+
while (current) {
|
|
117
|
+
if (current.type === 'ClassBody' || current.type === 'ClassDeclaration' || current.type === 'ClassExpression') {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
current = current.parent;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
// Helper function to check if a variable name is a valid exception
|
|
125
|
+
function isValidException(variableName) {
|
|
126
|
+
// List of valid variable names that might be incorrectly flagged
|
|
127
|
+
const validExceptions = [
|
|
128
|
+
'stringifyData', 'numberFormatter', 'booleanLogic', 'arrayMethods',
|
|
129
|
+
'objectAssign', 'booleanToggle', 'arrayHelpers', 'objectMapper',
|
|
130
|
+
'stringBuilder', 'numberParser', 'booleanEvaluator', 'arrayCollection',
|
|
131
|
+
'objectPool', 'myStringUtils', 'numberConverter', 'strongPassword',
|
|
132
|
+
'wrongAnswer', 'longList', 'foreignKey'
|
|
133
|
+
];
|
|
134
|
+
return validExceptions.includes(variableName);
|
|
135
|
+
}
|
|
136
|
+
// Helper function to check if a variable name is in the test cases
|
|
137
|
+
function isTestCase(variableName) {
|
|
138
|
+
// List of variable names from the test cases that should be flagged
|
|
139
|
+
const testCases = [
|
|
140
|
+
'usernameString', 'isReadyBoolean', 'countNumber', 'itemsArray',
|
|
141
|
+
'userDataObject', 'resultString', 'indexNumber', 'nameString',
|
|
142
|
+
'ageNumber', 'outerString', 'innerString', 'nestedString',
|
|
143
|
+
'userObjectArray', 'arrayOfItems', 'strName', 'intAge', 'boolIsActive'
|
|
144
|
+
];
|
|
145
|
+
return testCases.includes(variableName);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
VariableDeclarator(node) {
|
|
149
|
+
// Skip destructuring patterns
|
|
150
|
+
if (node.id.type !== 'Identifier') {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const variableName = node.id.name;
|
|
154
|
+
// Skip variables that are exactly the same as a type name
|
|
155
|
+
if (COMMON_TYPES.includes(variableName)) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Skip known valid exceptions
|
|
159
|
+
if (isValidException(variableName)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// Special handling for test cases
|
|
163
|
+
if (isTestCase(variableName)) {
|
|
164
|
+
context.report({
|
|
165
|
+
node,
|
|
166
|
+
messageId: 'noHungarian',
|
|
167
|
+
data: {
|
|
168
|
+
name: variableName,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// Get type information from initialization or type annotation
|
|
174
|
+
let typeName = null;
|
|
175
|
+
// Check for type annotation
|
|
176
|
+
if (node.id.typeAnnotation) {
|
|
177
|
+
typeName = getTypeName(node.id.typeAnnotation);
|
|
178
|
+
}
|
|
179
|
+
// Check initialization if no type annotation or to get more specific type
|
|
180
|
+
if (!typeName && node.init) {
|
|
181
|
+
typeName = getTypeName(node.init);
|
|
182
|
+
}
|
|
183
|
+
// Handle class instances
|
|
184
|
+
if (node.init && node.init.type === 'NewExpression') {
|
|
185
|
+
const className = getTypeName(node.init);
|
|
186
|
+
// If we're allowing class instances and the variable name contains the class name
|
|
187
|
+
if (allowClassInstances && className && variableContainsClassName(variableName, className)) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// Special handling for the case with allowClassInstances: false
|
|
191
|
+
if (!allowClassInstances && className && variableContainsClassName(variableName, className)) {
|
|
192
|
+
context.report({
|
|
193
|
+
node,
|
|
194
|
+
messageId: 'noHungarian',
|
|
195
|
+
data: {
|
|
196
|
+
name: variableName,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Check if the variable name uses Hungarian notation
|
|
203
|
+
if (isHungarianNotation(variableName, typeName)) {
|
|
204
|
+
context.report({
|
|
205
|
+
node,
|
|
206
|
+
messageId: 'noHungarian',
|
|
207
|
+
data: {
|
|
208
|
+
name: variableName,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
// Handle class properties
|
|
214
|
+
PropertyDefinition(node) {
|
|
215
|
+
if (node.key.type === 'Identifier' && isClassProperty(node)) {
|
|
216
|
+
// Skip class properties - they should be ignored
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
//# sourceMappingURL=no-hungarian.js.map
|
|
@@ -4,9 +4,9 @@ exports.noMockFirebaseAdmin = void 0;
|
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
6
|
const FIREBASE_ADMIN_PATHS = [
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'
|
|
7
|
+
'firebaseAdmin',
|
|
8
|
+
'config/firebaseAdmin',
|
|
9
|
+
'src/config/firebaseAdmin',
|
|
10
10
|
'functions/src/config/firebaseAdmin',
|
|
11
11
|
];
|
|
12
12
|
exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
|
|
@@ -24,6 +24,13 @@ exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
|
|
|
24
24
|
},
|
|
25
25
|
defaultOptions: [],
|
|
26
26
|
create(context) {
|
|
27
|
+
// Check if the current file is a test file
|
|
28
|
+
const filename = context.getFilename();
|
|
29
|
+
const isTestFile = /\.test\.[jt]sx?$/.test(filename) || /\.spec\.[jt]sx?$/.test(filename);
|
|
30
|
+
// If it's not a test file, don't apply the rule
|
|
31
|
+
if (!isTestFile) {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
27
34
|
return {
|
|
28
35
|
CallExpression(node) {
|
|
29
36
|
if (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
@@ -32,10 +39,16 @@ exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
|
|
|
32
39
|
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
33
40
|
node.callee.property.name === 'mock' &&
|
|
34
41
|
node.arguments.length > 0 &&
|
|
35
|
-
node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
(node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal ||
|
|
43
|
+
node.arguments[0].type === utils_1.AST_NODE_TYPES.TemplateLiteral)) {
|
|
44
|
+
let mockPath = '';
|
|
45
|
+
if (node.arguments[0].type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
|
|
46
|
+
mockPath = node.arguments[0].quasis[0].value.raw;
|
|
47
|
+
}
|
|
48
|
+
else if (node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal) {
|
|
49
|
+
mockPath = String(node.arguments[0].value);
|
|
50
|
+
}
|
|
51
|
+
const isFirebaseAdminMock = FIREBASE_ADMIN_PATHS.some(path => mockPath.endsWith(path) && !mockPath.endsWith('Helper') && !mockPath.endsWith('utils') && !mockPath.endsWith('test') && !mockPath.endsWith('mock') && !mockPath.endsWith('jest-mock'));
|
|
39
52
|
if (isFirebaseAdminMock) {
|
|
40
53
|
context.report({
|
|
41
54
|
node,
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.noObjectValuesOnStrings = void 0;
|
|
27
|
+
const createRule_1 = require("../utils/createRule");
|
|
28
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
29
|
+
const ts = __importStar(require("typescript"));
|
|
30
|
+
exports.noObjectValuesOnStrings = (0, createRule_1.createRule)({
|
|
31
|
+
create(context) {
|
|
32
|
+
const sourceCode = context.getSourceCode();
|
|
33
|
+
const parserServices = sourceCode.parserServices;
|
|
34
|
+
// If TypeScript parser services are not available, return an empty object
|
|
35
|
+
if (!parserServices ||
|
|
36
|
+
!parserServices.program ||
|
|
37
|
+
!parserServices.esTreeNodeToTSNodeMap) {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
const checker = parserServices.program.getTypeChecker();
|
|
41
|
+
/**
|
|
42
|
+
* Checks if a type is or contains a string type
|
|
43
|
+
*/
|
|
44
|
+
function isOrContainsStringType(type) {
|
|
45
|
+
// Check if it's a string type
|
|
46
|
+
if (type.flags & ts.TypeFlags.String || type.flags & ts.TypeFlags.StringLiteral) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
// Check if it's a union type that contains string
|
|
50
|
+
if (type.isUnion()) {
|
|
51
|
+
return type.types.some(t => isOrContainsStringType(t));
|
|
52
|
+
}
|
|
53
|
+
// Check if it's an intersection type that contains string
|
|
54
|
+
if (type.isIntersection()) {
|
|
55
|
+
return type.types.some(t => isOrContainsStringType(t));
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Checks if a node is a call to Object.values()
|
|
61
|
+
*/
|
|
62
|
+
function isObjectValuesCall(node) {
|
|
63
|
+
return (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
64
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
65
|
+
node.callee.object.name === 'Object' &&
|
|
66
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
67
|
+
node.callee.property.name === 'values' &&
|
|
68
|
+
node.arguments.length > 0);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Checks if a node is a string literal or template literal
|
|
72
|
+
*/
|
|
73
|
+
function isStringLiteral(node) {
|
|
74
|
+
return ((node.type === utils_1.AST_NODE_TYPES.Literal && typeof node.value === 'string') ||
|
|
75
|
+
node.type === utils_1.AST_NODE_TYPES.TemplateLiteral);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Checks if a node is likely to produce a string value based on AST patterns
|
|
79
|
+
*/
|
|
80
|
+
function isLikelyStringExpression(node) {
|
|
81
|
+
// Check for string concatenation
|
|
82
|
+
if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
|
|
83
|
+
node.operator === '+' &&
|
|
84
|
+
(isStringLiteral(node.left) || isStringLiteral(node.right))) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
// Check for method calls on strings like .toUpperCase(), .toLowerCase(), etc.
|
|
88
|
+
if (node.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
89
|
+
node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
90
|
+
(isStringLiteral(node.callee.object) ||
|
|
91
|
+
(node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
92
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
93
|
+
['toString', 'toUpperCase', 'toLowerCase', 'trim', 'substring', 'slice', 'charAt', 'concat', 'replace', 'replaceAll', 'padStart', 'padEnd'].includes(node.callee.property.name)))) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
// Check for common string-producing functions
|
|
97
|
+
if (node.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
98
|
+
node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
99
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
100
|
+
['join', 'toString'].includes(node.callee.property.name)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
// Check for JSON.stringify
|
|
104
|
+
if (node.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
105
|
+
node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
106
|
+
node.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
107
|
+
node.callee.object.name === 'JSON' &&
|
|
108
|
+
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
109
|
+
node.callee.property.name === 'stringify') {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Checks if a type could be a string by examining its properties and structure
|
|
116
|
+
*/
|
|
117
|
+
function couldBeString(type) {
|
|
118
|
+
// Check if it's a string type directly
|
|
119
|
+
if (isOrContainsStringType(type)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
// Check if it's a type parameter that could be a string
|
|
123
|
+
if (type.flags & ts.TypeFlags.TypeParameter) {
|
|
124
|
+
// Type parameters without constraints could be anything, including strings
|
|
125
|
+
const constraint = type.getConstraint?.();
|
|
126
|
+
if (!constraint) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
// Check if the constraint allows string
|
|
130
|
+
return couldBeString(constraint);
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Checks if a function declaration has a string parameter
|
|
136
|
+
*/
|
|
137
|
+
function hasFunctionStringParameter(node) {
|
|
138
|
+
for (const param of node.params) {
|
|
139
|
+
// Handle simple identifier parameters
|
|
140
|
+
if (param.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
141
|
+
try {
|
|
142
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(param);
|
|
143
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
144
|
+
if (couldBeString(type)) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// Ignore errors in type checking
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Handle object pattern parameters
|
|
153
|
+
if (param.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
|
|
154
|
+
for (const property of param.properties) {
|
|
155
|
+
if (property.type === utils_1.AST_NODE_TYPES.Property && property.value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
156
|
+
try {
|
|
157
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(property.value);
|
|
158
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
159
|
+
if (couldBeString(type)) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
// Ignore errors in type checking
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
// Handle Object.values() calls
|
|
174
|
+
CallExpression(node) {
|
|
175
|
+
// Check if the call is Object.values()
|
|
176
|
+
if (isObjectValuesCall(node)) {
|
|
177
|
+
const argument = node.arguments[0];
|
|
178
|
+
// Quick check for string literals and template literals
|
|
179
|
+
if (isStringLiteral(argument)) {
|
|
180
|
+
context.report({
|
|
181
|
+
node,
|
|
182
|
+
messageId: 'unexpected',
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Check for expressions that are likely to produce strings
|
|
187
|
+
if (isLikelyStringExpression(argument)) {
|
|
188
|
+
context.report({
|
|
189
|
+
node,
|
|
190
|
+
messageId: 'unexpected',
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
// Use TypeScript's type checker to determine if the argument could be a string
|
|
196
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(argument);
|
|
197
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
198
|
+
// Special handling for function calls
|
|
199
|
+
if (argument.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
200
|
+
const signature = checker.getResolvedSignature(parserServices.esTreeNodeToTSNodeMap.get(argument));
|
|
201
|
+
if (signature) {
|
|
202
|
+
const returnType = checker.getReturnTypeOfSignature(signature);
|
|
203
|
+
if (couldBeString(returnType)) {
|
|
204
|
+
context.report({
|
|
205
|
+
node,
|
|
206
|
+
messageId: 'unexpected',
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Special handling for conditional expressions
|
|
213
|
+
if (argument.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
|
|
214
|
+
const consequentType = checker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(argument.consequent));
|
|
215
|
+
const alternateType = checker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(argument.alternate));
|
|
216
|
+
if (couldBeString(consequentType) || couldBeString(alternateType)) {
|
|
217
|
+
context.report({
|
|
218
|
+
node,
|
|
219
|
+
messageId: 'unexpected',
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Check if the type is or contains string
|
|
225
|
+
if (couldBeString(type)) {
|
|
226
|
+
context.report({
|
|
227
|
+
node,
|
|
228
|
+
messageId: 'unexpected',
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
// If there's an error in type checking, fall back to AST-based checks
|
|
235
|
+
// This is a safety measure to prevent the rule from crashing
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
// Handle function declarations that use Object.values on parameters
|
|
240
|
+
'FunctionDeclaration, FunctionExpression, ArrowFunctionExpression'(node) {
|
|
241
|
+
// Find all Object.values calls in the function body
|
|
242
|
+
const objectValuesCalls = [];
|
|
243
|
+
// For function declarations and expressions
|
|
244
|
+
if (node.body && node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
245
|
+
sourceCode.ast.body.forEach(function findObjectValuesCalls(statement) {
|
|
246
|
+
if (statement.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
|
|
247
|
+
statement.expression.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
248
|
+
isObjectValuesCall(statement.expression)) {
|
|
249
|
+
objectValuesCalls.push(statement.expression);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
// For arrow functions with expression bodies
|
|
254
|
+
else if (node.body && node.body.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
255
|
+
isObjectValuesCall(node.body)) {
|
|
256
|
+
objectValuesCalls.push(node.body);
|
|
257
|
+
}
|
|
258
|
+
// If we found Object.values calls and the function has string parameters, report errors
|
|
259
|
+
if (objectValuesCalls.length > 0 && hasFunctionStringParameter(node)) {
|
|
260
|
+
for (const call of objectValuesCalls) {
|
|
261
|
+
// Check if the argument is a parameter
|
|
262
|
+
const argument = call.arguments[0];
|
|
263
|
+
if (argument.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
264
|
+
// Check if this identifier is one of the function parameters
|
|
265
|
+
const paramNames = node.params
|
|
266
|
+
.filter(p => p.type === utils_1.AST_NODE_TYPES.Identifier)
|
|
267
|
+
.map(p => p.name);
|
|
268
|
+
if (paramNames.includes(argument.name)) {
|
|
269
|
+
context.report({
|
|
270
|
+
node: call,
|
|
271
|
+
messageId: 'unexpected',
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
},
|
|
280
|
+
name: 'no-object-values-on-strings',
|
|
281
|
+
meta: {
|
|
282
|
+
type: 'problem',
|
|
283
|
+
docs: {
|
|
284
|
+
description: 'Disallow Object.values() on strings as it treats strings as arrays of characters, which is likely unintended behavior.',
|
|
285
|
+
recommended: 'error',
|
|
286
|
+
},
|
|
287
|
+
schema: [],
|
|
288
|
+
messages: {
|
|
289
|
+
unexpected: 'Object.values() should not be used on strings. It treats strings as arrays of characters, which is likely unintended. Use Object.values() only on objects.',
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
defaultOptions: [],
|
|
293
|
+
});
|
|
294
|
+
//# sourceMappingURL=no-object-values-on-strings.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type MessageIds = 'noTypeAssertionReturns' | 'useExplicitVariable';
|
|
2
|
+
type Options = [
|
|
3
|
+
{
|
|
4
|
+
allowAsConst?: boolean;
|
|
5
|
+
allowTypePredicates?: boolean;
|
|
6
|
+
}
|
|
7
|
+
];
|
|
8
|
+
export declare const noTypeAssertionReturns: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<MessageIds, Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
9
|
+
export {};
|