eslint-plugin-absolute 0.2.0 → 0.2.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/.absolutejs/eslint.cache.json +49 -0
- package/.absolutejs/prettier.cache.json +49 -0
- package/.absolutejs/tsconfig.tsbuildinfo +1 -0
- package/.claude/settings.local.json +8 -3
- package/.codex +0 -0
- package/dist/index.js +1500 -1421
- package/eslint.config.mjs +107 -0
- package/package.json +22 -20
- package/src/index.ts +15 -15
- package/src/rules/explicit-object-types.ts +42 -40
- package/src/rules/inline-style-limit.ts +56 -54
- package/src/rules/localize-react-props.ts +261 -266
- package/src/rules/max-depth-extended.ts +55 -66
- package/src/rules/max-jsx-nesting.ts +28 -36
- package/src/rules/min-var-length.ts +238 -208
- package/src/rules/no-button-navigation.ts +114 -156
- package/src/rules/no-explicit-return-types.ts +32 -36
- package/src/rules/no-inline-prop-types.ts +30 -30
- package/src/rules/no-multi-style-objects.ts +35 -42
- package/src/rules/no-nested-jsx-return.ts +100 -105
- package/src/rules/no-or-none-component.ts +17 -19
- package/src/rules/no-transition-cssproperties.ts +76 -70
- package/src/rules/no-unnecessary-div.ts +26 -34
- package/src/rules/no-unnecessary-key.ts +41 -75
- package/src/rules/no-useless-function.ts +18 -20
- package/src/rules/seperate-style-files.ts +19 -21
- package/src/rules/sort-exports.ts +382 -256
- package/src/rules/sort-keys-fixable.ts +486 -322
- package/src/rules/spring-naming-convention.ts +104 -89
- package/tsconfig.json +3 -1
package/dist/index.js
CHANGED
|
@@ -2,24 +2,9 @@
|
|
|
2
2
|
// src/rules/no-nested-jsx-return.ts
|
|
3
3
|
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
4
4
|
var noNestedJSXReturn = {
|
|
5
|
-
meta: {
|
|
6
|
-
type: "problem",
|
|
7
|
-
docs: {
|
|
8
|
-
description: "Disallow nested functions that return non-component, non-singular JSX to enforce one component per file"
|
|
9
|
-
},
|
|
10
|
-
schema: [],
|
|
11
|
-
messages: {
|
|
12
|
-
nestedFunctionJSX: "Nested function returning non-component, non-singular JSX detected. Extract it into its own component.",
|
|
13
|
-
nestedArrowJSX: "Nested arrow function returning non-component, non-singular JSX detected. Extract it into its own component.",
|
|
14
|
-
nestedArrowFragment: "Nested arrow function returning a non-singular JSX fragment detected. Extract it into its own component."
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
defaultOptions: [],
|
|
18
5
|
create(context) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
function getLeftmostJSXIdentifier(name) {
|
|
6
|
+
const isJSX = (node) => node !== null && node !== undefined && (node.type === AST_NODE_TYPES.JSXElement || node.type === AST_NODE_TYPES.JSXFragment);
|
|
7
|
+
const getLeftmostJSXIdentifier = (name) => {
|
|
23
8
|
let current = name;
|
|
24
9
|
while (current.type === AST_NODE_TYPES.JSXMemberExpression) {
|
|
25
10
|
current = current.object;
|
|
@@ -28,8 +13,8 @@ var noNestedJSXReturn = {
|
|
|
28
13
|
return current;
|
|
29
14
|
}
|
|
30
15
|
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
16
|
+
};
|
|
17
|
+
const isJSXComponentElement = (node) => {
|
|
33
18
|
if (!node || node.type !== AST_NODE_TYPES.JSXElement) {
|
|
34
19
|
return false;
|
|
35
20
|
}
|
|
@@ -43,286 +28,334 @@ var noNestedJSXReturn = {
|
|
|
43
28
|
return false;
|
|
44
29
|
}
|
|
45
30
|
return /^[A-Z]/.test(leftmost.name);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return false;
|
|
50
|
-
const children = node.children.filter((child) => {
|
|
31
|
+
};
|
|
32
|
+
const hasNoMeaningfulChildren = (children) => {
|
|
33
|
+
const filtered = children.filter((child) => {
|
|
51
34
|
if (child.type === AST_NODE_TYPES.JSXText) {
|
|
52
35
|
return child.value.trim() !== "";
|
|
53
36
|
}
|
|
54
37
|
return true;
|
|
55
38
|
});
|
|
39
|
+
return filtered.length === 0;
|
|
40
|
+
};
|
|
41
|
+
const isSingularJSXReturn = (node) => {
|
|
42
|
+
if (!isJSX(node))
|
|
43
|
+
return false;
|
|
44
|
+
const children = node.children.filter((child2) => {
|
|
45
|
+
if (child2.type === AST_NODE_TYPES.JSXText) {
|
|
46
|
+
return child2.value.trim() !== "";
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
56
50
|
if (children.length === 0) {
|
|
57
51
|
return true;
|
|
58
52
|
}
|
|
59
|
-
if (children.length
|
|
60
|
-
|
|
61
|
-
if (!child) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
if (child.type === AST_NODE_TYPES.JSXElement || child.type === AST_NODE_TYPES.JSXFragment) {
|
|
65
|
-
const innerChildren = child.children.filter((innerChild) => {
|
|
66
|
-
if (innerChild.type === AST_NODE_TYPES.JSXText) {
|
|
67
|
-
return innerChild.value.trim() !== "";
|
|
68
|
-
}
|
|
69
|
-
return true;
|
|
70
|
-
});
|
|
71
|
-
return innerChildren.length === 0;
|
|
72
|
-
}
|
|
73
|
-
return true;
|
|
53
|
+
if (children.length !== 1) {
|
|
54
|
+
return false;
|
|
74
55
|
}
|
|
75
|
-
|
|
76
|
-
|
|
56
|
+
const [child] = children;
|
|
57
|
+
if (!child) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (child.type === AST_NODE_TYPES.JSXElement || child.type === AST_NODE_TYPES.JSXFragment) {
|
|
61
|
+
return hasNoMeaningfulChildren(child.children);
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
};
|
|
77
65
|
const functionStack = [];
|
|
78
|
-
|
|
66
|
+
const pushFunction = (node) => {
|
|
79
67
|
functionStack.push(node);
|
|
80
|
-
}
|
|
81
|
-
|
|
68
|
+
};
|
|
69
|
+
const popFunction = () => {
|
|
82
70
|
functionStack.pop();
|
|
83
|
-
}
|
|
71
|
+
};
|
|
84
72
|
return {
|
|
85
|
-
"
|
|
86
|
-
pushFunction(node);
|
|
87
|
-
},
|
|
88
|
-
"FunctionDeclaration:exit"(_node) {
|
|
89
|
-
popFunction();
|
|
90
|
-
},
|
|
91
|
-
"FunctionExpression:exit"(_node) {
|
|
92
|
-
popFunction();
|
|
93
|
-
},
|
|
94
|
-
"ArrowFunctionExpression:exit"(_node) {
|
|
95
|
-
popFunction();
|
|
96
|
-
},
|
|
97
|
-
ReturnStatement(node) {
|
|
73
|
+
"ArrowFunctionExpression > JSXElement"(node) {
|
|
98
74
|
if (functionStack.length <= 1) {
|
|
99
75
|
return;
|
|
100
76
|
}
|
|
101
|
-
|
|
102
|
-
if (!isJSX(argument)) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (!isJSXComponentElement(argument) && !isSingularJSXReturn(argument)) {
|
|
77
|
+
if (!isJSXComponentElement(node) && !isSingularJSXReturn(node)) {
|
|
106
78
|
context.report({
|
|
107
|
-
|
|
108
|
-
|
|
79
|
+
messageId: "nestedArrowJSX",
|
|
80
|
+
node
|
|
109
81
|
});
|
|
110
82
|
}
|
|
111
83
|
},
|
|
112
|
-
"ArrowFunctionExpression >
|
|
84
|
+
"ArrowFunctionExpression > JSXFragment"(node) {
|
|
113
85
|
if (functionStack.length <= 1) {
|
|
114
86
|
return;
|
|
115
87
|
}
|
|
116
|
-
if (!
|
|
88
|
+
if (!isSingularJSXReturn(node)) {
|
|
117
89
|
context.report({
|
|
118
|
-
|
|
119
|
-
|
|
90
|
+
messageId: "nestedArrowFragment",
|
|
91
|
+
node
|
|
120
92
|
});
|
|
121
93
|
}
|
|
122
94
|
},
|
|
123
|
-
"ArrowFunctionExpression
|
|
95
|
+
"ArrowFunctionExpression:exit"() {
|
|
96
|
+
popFunction();
|
|
97
|
+
},
|
|
98
|
+
"FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(node) {
|
|
99
|
+
pushFunction(node);
|
|
100
|
+
},
|
|
101
|
+
"FunctionDeclaration:exit"() {
|
|
102
|
+
popFunction();
|
|
103
|
+
},
|
|
104
|
+
"FunctionExpression:exit"() {
|
|
105
|
+
popFunction();
|
|
106
|
+
},
|
|
107
|
+
ReturnStatement(node) {
|
|
124
108
|
if (functionStack.length <= 1) {
|
|
125
109
|
return;
|
|
126
110
|
}
|
|
127
|
-
|
|
111
|
+
const { argument } = node;
|
|
112
|
+
if (!isJSX(argument)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!isJSXComponentElement(argument) && !isSingularJSXReturn(argument)) {
|
|
128
116
|
context.report({
|
|
129
|
-
|
|
130
|
-
|
|
117
|
+
messageId: "nestedFunctionJSX",
|
|
118
|
+
node
|
|
131
119
|
});
|
|
132
120
|
}
|
|
133
121
|
}
|
|
134
122
|
};
|
|
123
|
+
},
|
|
124
|
+
defaultOptions: [],
|
|
125
|
+
meta: {
|
|
126
|
+
docs: {
|
|
127
|
+
description: "Disallow nested functions that return non-component, non-singular JSX to enforce one component per file"
|
|
128
|
+
},
|
|
129
|
+
messages: {
|
|
130
|
+
nestedArrowFragment: "Nested arrow function returning a non-singular JSX fragment detected. Extract it into its own component.",
|
|
131
|
+
nestedArrowJSX: "Nested arrow function returning non-component, non-singular JSX detected. Extract it into its own component.",
|
|
132
|
+
nestedFunctionJSX: "Nested function returning non-component, non-singular JSX detected. Extract it into its own component."
|
|
133
|
+
},
|
|
134
|
+
schema: [],
|
|
135
|
+
type: "problem"
|
|
135
136
|
}
|
|
136
137
|
};
|
|
137
138
|
|
|
138
139
|
// src/rules/explicit-object-types.ts
|
|
139
140
|
var explicitObjectTypes = {
|
|
140
|
-
meta: {
|
|
141
|
-
type: "problem",
|
|
142
|
-
docs: {
|
|
143
|
-
description: "Require explicit type annotations for object literals and arrays of object literals"
|
|
144
|
-
},
|
|
145
|
-
schema: [],
|
|
146
|
-
messages: {
|
|
147
|
-
objectLiteralNeedsType: "Object literal must have an explicit type annotation.",
|
|
148
|
-
arrayOfObjectLiteralsNeedsType: "Array of object literals must have an explicit type annotation."
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
defaultOptions: [],
|
|
152
141
|
create(context) {
|
|
153
|
-
|
|
154
|
-
return !!node && node.type === "ObjectExpression";
|
|
155
|
-
}
|
|
142
|
+
const isObjectLiteral = (node) => node !== null && node !== undefined && node.type === "ObjectExpression";
|
|
156
143
|
return {
|
|
157
144
|
VariableDeclarator(node) {
|
|
158
145
|
if (!node.init)
|
|
159
146
|
return;
|
|
160
147
|
if (node.id.type === "Identifier" && node.id.typeAnnotation)
|
|
161
148
|
return;
|
|
162
|
-
if (isObjectLiteral(node.init)) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
149
|
+
if (isObjectLiteral(node.init) && node.id.type === "Identifier") {
|
|
150
|
+
context.report({
|
|
151
|
+
messageId: "objectLiteralNeedsType",
|
|
152
|
+
node: node.id
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (node.init.type !== "ArrayExpression") {
|
|
169
157
|
return;
|
|
170
158
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
159
|
+
const hasObjectLiteral = node.init.elements.some((element) => {
|
|
160
|
+
if (!element || element.type === "SpreadElement")
|
|
161
|
+
return false;
|
|
162
|
+
return isObjectLiteral(element);
|
|
163
|
+
});
|
|
164
|
+
if (hasObjectLiteral && node.id.type === "Identifier") {
|
|
165
|
+
context.report({
|
|
166
|
+
messageId: "arrayOfObjectLiteralsNeedsType",
|
|
167
|
+
node: node.id
|
|
176
168
|
});
|
|
177
|
-
if (hasObjectLiteral && node.id.type === "Identifier") {
|
|
178
|
-
context.report({
|
|
179
|
-
node: node.id,
|
|
180
|
-
messageId: "arrayOfObjectLiteralsNeedsType"
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
169
|
}
|
|
184
170
|
}
|
|
185
171
|
};
|
|
172
|
+
},
|
|
173
|
+
defaultOptions: [],
|
|
174
|
+
meta: {
|
|
175
|
+
docs: {
|
|
176
|
+
description: "Require explicit type annotations for object literals and arrays of object literals"
|
|
177
|
+
},
|
|
178
|
+
messages: {
|
|
179
|
+
arrayOfObjectLiteralsNeedsType: "Array of object literals must have an explicit type annotation.",
|
|
180
|
+
objectLiteralNeedsType: "Object literal must have an explicit type annotation."
|
|
181
|
+
},
|
|
182
|
+
schema: [],
|
|
183
|
+
type: "problem"
|
|
186
184
|
}
|
|
187
185
|
};
|
|
188
186
|
|
|
189
187
|
// src/rules/sort-keys-fixable.ts
|
|
188
|
+
var SORT_BEFORE = -1;
|
|
189
|
+
var hasDuplicateNames = (names) => {
|
|
190
|
+
const seen = new Set;
|
|
191
|
+
const nonNullNames = names.flatMap((name) => name === null ? [] : [name]);
|
|
192
|
+
for (const name of nonNullNames) {
|
|
193
|
+
if (seen.has(name)) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
seen.add(name);
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
};
|
|
200
|
+
var isSafeStaticTemplate = (node) => node.expressions.length === 0;
|
|
201
|
+
var isSafeArrayElement = (node) => {
|
|
202
|
+
if (!node || node.type === "SpreadElement") {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return isSafeToReorderExpression(node);
|
|
206
|
+
};
|
|
207
|
+
var isSafeObjectProperty = (property) => {
|
|
208
|
+
if (property.type !== "Property" || property.computed || property.kind !== "init") {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
if (property.key.type !== "Identifier" && property.key.type !== "Literal") {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
if (property.method) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return isSafeToReorderExpression(property.value);
|
|
218
|
+
};
|
|
219
|
+
var isSafeToReorderExpression = (node) => {
|
|
220
|
+
if (!node || node.type === "PrivateIdentifier") {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
switch (node.type) {
|
|
224
|
+
case "Identifier":
|
|
225
|
+
case "Literal":
|
|
226
|
+
case "ThisExpression":
|
|
227
|
+
case "FunctionExpression":
|
|
228
|
+
case "ArrowFunctionExpression":
|
|
229
|
+
case "ClassExpression":
|
|
230
|
+
return true;
|
|
231
|
+
case "TemplateLiteral":
|
|
232
|
+
return isSafeStaticTemplate(node);
|
|
233
|
+
case "UnaryExpression":
|
|
234
|
+
return isSafeToReorderExpression(node.argument);
|
|
235
|
+
case "ArrayExpression":
|
|
236
|
+
return node.elements.every(isSafeArrayElement);
|
|
237
|
+
case "ObjectExpression":
|
|
238
|
+
return node.properties.every(isSafeObjectProperty);
|
|
239
|
+
default:
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
var isSafeJSXAttributeValue = (value) => {
|
|
244
|
+
if (value === null) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
if (value.type === "Literal") {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
if (value.type !== "JSXExpressionContainer") {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
if (value.expression.type === "JSXEmptyExpression") {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
return isSafeToReorderExpression(value.expression);
|
|
257
|
+
};
|
|
190
258
|
var sortKeysFixable = {
|
|
191
|
-
meta: {
|
|
192
|
-
type: "suggestion",
|
|
193
|
-
docs: {
|
|
194
|
-
description: "enforce sorted keys in object literals with auto-fix (limited to simple cases, preserving comments)"
|
|
195
|
-
},
|
|
196
|
-
fixable: "code",
|
|
197
|
-
schema: [
|
|
198
|
-
{
|
|
199
|
-
type: "object",
|
|
200
|
-
properties: {
|
|
201
|
-
order: {
|
|
202
|
-
type: "string",
|
|
203
|
-
enum: ["asc", "desc"]
|
|
204
|
-
},
|
|
205
|
-
caseSensitive: {
|
|
206
|
-
type: "boolean"
|
|
207
|
-
},
|
|
208
|
-
natural: {
|
|
209
|
-
type: "boolean"
|
|
210
|
-
},
|
|
211
|
-
minKeys: {
|
|
212
|
-
type: "integer",
|
|
213
|
-
minimum: 2
|
|
214
|
-
},
|
|
215
|
-
variablesBeforeFunctions: {
|
|
216
|
-
type: "boolean"
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
additionalProperties: false
|
|
220
|
-
}
|
|
221
|
-
],
|
|
222
|
-
messages: {
|
|
223
|
-
unsorted: "Object keys are not sorted."
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
defaultOptions: [{}],
|
|
227
259
|
create(context) {
|
|
228
|
-
const sourceCode = context
|
|
229
|
-
const option = context.options
|
|
260
|
+
const { sourceCode } = context;
|
|
261
|
+
const [option] = context.options;
|
|
230
262
|
const order = option && option.order ? option.order : "asc";
|
|
231
263
|
const caseSensitive = option && typeof option.caseSensitive === "boolean" ? option.caseSensitive : false;
|
|
232
264
|
const natural = option && typeof option.natural === "boolean" ? option.natural : false;
|
|
233
265
|
const minKeys = option && typeof option.minKeys === "number" ? option.minKeys : 2;
|
|
234
266
|
const variablesBeforeFunctions = option && typeof option.variablesBeforeFunctions === "boolean" ? option.variablesBeforeFunctions : false;
|
|
235
|
-
|
|
236
|
-
let
|
|
237
|
-
let
|
|
267
|
+
const compareKeys = (keyLeft, keyRight) => {
|
|
268
|
+
let left = keyLeft;
|
|
269
|
+
let right = keyRight;
|
|
238
270
|
if (!caseSensitive) {
|
|
239
|
-
|
|
240
|
-
|
|
271
|
+
left = left.toLowerCase();
|
|
272
|
+
right = right.toLowerCase();
|
|
241
273
|
}
|
|
242
274
|
if (natural) {
|
|
243
|
-
return
|
|
275
|
+
return left.localeCompare(right, undefined, {
|
|
244
276
|
numeric: true
|
|
245
277
|
});
|
|
246
278
|
}
|
|
247
|
-
return
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const value = prop
|
|
251
|
-
return
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const key = prop
|
|
279
|
+
return left.localeCompare(right);
|
|
280
|
+
};
|
|
281
|
+
const isFunctionProperty = (prop) => {
|
|
282
|
+
const { value } = prop;
|
|
283
|
+
return Boolean(value) && (value.type === "FunctionExpression" || value.type === "ArrowFunctionExpression" || prop.method === true);
|
|
284
|
+
};
|
|
285
|
+
const getPropertyKeyName = (prop) => {
|
|
286
|
+
const { key } = prop;
|
|
255
287
|
if (key.type === "Identifier") {
|
|
256
288
|
return key.name;
|
|
257
289
|
}
|
|
258
290
|
if (key.type === "Literal") {
|
|
259
|
-
const value = key
|
|
291
|
+
const { value } = key;
|
|
260
292
|
if (typeof value === "string") {
|
|
261
293
|
return value;
|
|
262
294
|
}
|
|
263
295
|
return String(value);
|
|
264
296
|
}
|
|
265
297
|
return "";
|
|
266
|
-
}
|
|
267
|
-
|
|
298
|
+
};
|
|
299
|
+
const getLeadingComments = (prop, prevProp) => {
|
|
268
300
|
const comments = sourceCode.getCommentsBefore(prop);
|
|
269
301
|
if (!prevProp || comments.length === 0) {
|
|
270
302
|
return comments;
|
|
271
303
|
}
|
|
272
|
-
return comments.filter((
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const after = sourceCode.getCommentsAfter(prop).filter((
|
|
276
|
-
if (nextProp) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
}
|
|
304
|
+
return comments.filter((comment) => comment.loc.start.line !== prevProp.loc.end.line);
|
|
305
|
+
};
|
|
306
|
+
const getTrailingComments = (prop, nextProp) => {
|
|
307
|
+
const after = sourceCode.getCommentsAfter(prop).filter((comment) => comment.loc.start.line === prop.loc.end.line);
|
|
308
|
+
if (!nextProp) {
|
|
309
|
+
return after;
|
|
310
|
+
}
|
|
311
|
+
const beforeNext = sourceCode.getCommentsBefore(nextProp);
|
|
312
|
+
const trailingOfPrev = beforeNext.filter((comment) => comment.loc.start.line === prop.loc.end.line);
|
|
313
|
+
const newComments = trailingOfPrev.filter((comment) => !after.some((existing) => existing.range[0] === comment.range[0]));
|
|
314
|
+
after.push(...newComments);
|
|
285
315
|
return after;
|
|
286
|
-
}
|
|
287
|
-
|
|
316
|
+
};
|
|
317
|
+
const getChunkStart = (idx, fixableProps, rangeStart, fullStart) => {
|
|
318
|
+
if (idx === 0) {
|
|
319
|
+
return rangeStart;
|
|
320
|
+
}
|
|
321
|
+
const prevProp = fixableProps[idx - 1];
|
|
322
|
+
const currentProp = fixableProps[idx];
|
|
323
|
+
const prevTrailing = getTrailingComments(prevProp, currentProp);
|
|
324
|
+
const prevEnd = prevTrailing.length > 0 ? prevTrailing[prevTrailing.length - 1].range[1] : prevProp.range[1];
|
|
325
|
+
const allTokens = sourceCode.getTokensBetween(prevProp, currentProp, {
|
|
326
|
+
includeComments: false
|
|
327
|
+
});
|
|
328
|
+
const tokenAfterPrev = allTokens.find((tok) => tok.range[0] >= prevEnd) ?? null;
|
|
329
|
+
if (tokenAfterPrev && tokenAfterPrev.value === "," && tokenAfterPrev.range[1] <= fullStart) {
|
|
330
|
+
return tokenAfterPrev.range[1];
|
|
331
|
+
}
|
|
332
|
+
return prevEnd;
|
|
333
|
+
};
|
|
334
|
+
const buildSortedText = (fixableProps, rangeStart) => {
|
|
288
335
|
const chunks = [];
|
|
289
|
-
for (let
|
|
290
|
-
const prop = fixableProps[
|
|
291
|
-
const prevProp =
|
|
292
|
-
const nextProp =
|
|
336
|
+
for (let idx = 0;idx < fixableProps.length; idx++) {
|
|
337
|
+
const prop = fixableProps[idx];
|
|
338
|
+
const prevProp = idx > 0 ? fixableProps[idx - 1] : null;
|
|
339
|
+
const nextProp = idx < fixableProps.length - 1 ? fixableProps[idx + 1] : null;
|
|
293
340
|
const leading = getLeadingComments(prop, prevProp);
|
|
294
341
|
const trailing = getTrailingComments(prop, nextProp);
|
|
295
342
|
const fullStart = leading.length > 0 ? leading[0].range[0] : prop.range[0];
|
|
296
343
|
const fullEnd = trailing.length > 0 ? trailing[trailing.length - 1].range[1] : prop.range[1];
|
|
297
|
-
|
|
298
|
-
if (i === 0) {
|
|
299
|
-
chunkStart = rangeStart;
|
|
300
|
-
} else {
|
|
301
|
-
const prevTrailing = getTrailingComments(prevProp, prop);
|
|
302
|
-
const prevEnd = prevTrailing.length > 0 ? prevTrailing[prevTrailing.length - 1].range[1] : prevProp.range[1];
|
|
303
|
-
const tokenAfterPrev = sourceCode.getTokenAfter({
|
|
304
|
-
range: [prevEnd, prevEnd]
|
|
305
|
-
}, { includeComments: false });
|
|
306
|
-
if (tokenAfterPrev && tokenAfterPrev.value === "," && tokenAfterPrev.range[1] <= fullStart) {
|
|
307
|
-
chunkStart = tokenAfterPrev.range[1];
|
|
308
|
-
} else {
|
|
309
|
-
chunkStart = prevEnd;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
344
|
+
const chunkStart = getChunkStart(idx, fixableProps, rangeStart, fullStart);
|
|
312
345
|
const text = sourceCode.text.slice(chunkStart, fullEnd);
|
|
313
346
|
chunks.push({ prop, text });
|
|
314
347
|
}
|
|
315
|
-
const sorted = chunks.slice().sort((
|
|
348
|
+
const sorted = chunks.slice().sort((left, right) => {
|
|
316
349
|
if (variablesBeforeFunctions) {
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
if (
|
|
320
|
-
return
|
|
350
|
+
const leftIsFunc = isFunctionProperty(left.prop);
|
|
351
|
+
const rightIsFunc = isFunctionProperty(right.prop);
|
|
352
|
+
if (leftIsFunc !== rightIsFunc) {
|
|
353
|
+
return leftIsFunc ? 1 : SORT_BEFORE;
|
|
321
354
|
}
|
|
322
355
|
}
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
let res = compareKeys(
|
|
356
|
+
const leftKey = getPropertyKeyName(left.prop);
|
|
357
|
+
const rightKey = getPropertyKeyName(right.prop);
|
|
358
|
+
let res = compareKeys(leftKey, rightKey);
|
|
326
359
|
if (order === "desc") {
|
|
327
360
|
res = -res;
|
|
328
361
|
}
|
|
@@ -336,12 +369,12 @@ var sortKeysFixable = {
|
|
|
336
369
|
const col = fixableProps[0].loc.start.column;
|
|
337
370
|
const indent = sourceCode.text.slice(fixableProps[0].range[0] - col, fixableProps[0].range[0]);
|
|
338
371
|
separator = `,
|
|
339
|
-
|
|
372
|
+
${indent}`;
|
|
340
373
|
} else {
|
|
341
374
|
separator = ", ";
|
|
342
375
|
}
|
|
343
|
-
return sorted.map((chunk,
|
|
344
|
-
if (
|
|
376
|
+
return sorted.map((chunk, idx) => {
|
|
377
|
+
if (idx === 0) {
|
|
345
378
|
const originalFirstChunk = chunks[0];
|
|
346
379
|
const originalLeadingWs = originalFirstChunk.text.match(/^(\s*)/)?.[1] ?? "";
|
|
347
380
|
const stripped2 = chunk.text.replace(/^\s*/, "");
|
|
@@ -350,8 +383,9 @@ var sortKeysFixable = {
|
|
|
350
383
|
const stripped = chunk.text.replace(/^\s*/, "");
|
|
351
384
|
return separator + stripped;
|
|
352
385
|
}).join("");
|
|
353
|
-
}
|
|
354
|
-
|
|
386
|
+
};
|
|
387
|
+
const getFixableProps = (node) => node.properties.filter((prop) => prop.type === "Property" && !prop.computed && (prop.key.type === "Identifier" || prop.key.type === "Literal"));
|
|
388
|
+
const checkObjectExpression = (node) => {
|
|
355
389
|
if (node.properties.length < minKeys) {
|
|
356
390
|
return;
|
|
357
391
|
}
|
|
@@ -359,380 +393,392 @@ var sortKeysFixable = {
|
|
|
359
393
|
const keys = node.properties.map((prop) => {
|
|
360
394
|
let keyName = null;
|
|
361
395
|
let isFunc = false;
|
|
362
|
-
if (prop.type
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
396
|
+
if (prop.type !== "Property") {
|
|
397
|
+
autoFixable = false;
|
|
398
|
+
return {
|
|
399
|
+
isFunction: isFunc,
|
|
400
|
+
keyName,
|
|
401
|
+
node: prop
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (prop.computed) {
|
|
405
|
+
autoFixable = false;
|
|
406
|
+
}
|
|
407
|
+
if (prop.key.type === "Identifier") {
|
|
408
|
+
keyName = prop.key.name;
|
|
409
|
+
} else if (prop.key.type === "Literal") {
|
|
410
|
+
const { value } = prop.key;
|
|
411
|
+
keyName = typeof value === "string" ? value : String(value);
|
|
377
412
|
} else {
|
|
378
413
|
autoFixable = false;
|
|
379
414
|
}
|
|
415
|
+
if (isFunctionProperty(prop)) {
|
|
416
|
+
isFunc = true;
|
|
417
|
+
}
|
|
380
418
|
return {
|
|
419
|
+
isFunction: isFunc,
|
|
381
420
|
keyName,
|
|
382
|
-
node: prop
|
|
383
|
-
isFunction: isFunc
|
|
421
|
+
node: prop
|
|
384
422
|
};
|
|
385
423
|
});
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (prop.computed) {
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
if (prop.key.type !== "Identifier" && prop.key.type !== "Literal") {
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
props.push(prop);
|
|
399
|
-
}
|
|
400
|
-
return props;
|
|
401
|
-
};
|
|
424
|
+
if (hasDuplicateNames(keys.map((key) => key.keyName))) {
|
|
425
|
+
autoFixable = false;
|
|
426
|
+
}
|
|
427
|
+
if (autoFixable && keys.some((key) => key.node.type === "Property" && !isSafeToReorderExpression(key.node.value))) {
|
|
428
|
+
autoFixable = false;
|
|
429
|
+
}
|
|
402
430
|
let fixProvided = false;
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
431
|
+
const createReportWithFix = (curr, shouldFix) => {
|
|
432
|
+
context.report({
|
|
433
|
+
fix: shouldFix ? (fixer) => {
|
|
434
|
+
const fixableProps = getFixableProps(node);
|
|
435
|
+
if (fixableProps.length < minKeys) {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
const [firstProp] = fixableProps;
|
|
439
|
+
const lastProp = fixableProps[fixableProps.length - 1];
|
|
440
|
+
if (!firstProp || !lastProp) {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
const firstLeading = getLeadingComments(firstProp, null);
|
|
444
|
+
const [firstLeadingComment] = firstLeading;
|
|
445
|
+
const rangeStart = firstLeadingComment ? firstLeadingComment.range[0] : firstProp.range[0];
|
|
446
|
+
const lastTrailing = getTrailingComments(lastProp, null);
|
|
447
|
+
const rangeEnd = lastTrailing.length > 0 ? lastTrailing[lastTrailing.length - 1].range[1] : lastProp.range[1];
|
|
448
|
+
const sortedText = buildSortedText(fixableProps, rangeStart);
|
|
449
|
+
return fixer.replaceTextRange([rangeStart, rangeEnd], sortedText);
|
|
450
|
+
} : null,
|
|
451
|
+
messageId: "unsorted",
|
|
452
|
+
node: curr.node.type === "Property" ? curr.node.key : curr.node
|
|
453
|
+
});
|
|
454
|
+
fixProvided = true;
|
|
455
|
+
};
|
|
456
|
+
keys.forEach((curr, idx) => {
|
|
457
|
+
if (idx === 0) {
|
|
458
|
+
return;
|
|
408
459
|
}
|
|
409
|
-
|
|
410
|
-
|
|
460
|
+
const prev = keys[idx - 1];
|
|
461
|
+
if (!prev || !curr || prev.keyName === null || curr.keyName === null) {
|
|
462
|
+
return;
|
|
411
463
|
}
|
|
412
464
|
const shouldFix = !fixProvided && autoFixable;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
messageId: "unsorted",
|
|
417
|
-
fix: shouldFix ? (fixer) => {
|
|
418
|
-
const fixableProps = getFixableProps();
|
|
419
|
-
if (fixableProps.length < minKeys) {
|
|
420
|
-
return null;
|
|
421
|
-
}
|
|
422
|
-
const firstProp = fixableProps[0];
|
|
423
|
-
const lastProp = fixableProps[fixableProps.length - 1];
|
|
424
|
-
if (!firstProp || !lastProp) {
|
|
425
|
-
return null;
|
|
426
|
-
}
|
|
427
|
-
const firstLeading = getLeadingComments(firstProp, null);
|
|
428
|
-
const rangeStart = firstLeading.length > 0 ? firstLeading[0].range[0] : firstProp.range[0];
|
|
429
|
-
const lastTrailing = getTrailingComments(lastProp, null);
|
|
430
|
-
const rangeEnd = lastTrailing.length > 0 ? lastTrailing[lastTrailing.length - 1].range[1] : lastProp.range[1];
|
|
431
|
-
const sortedText = buildSortedText(fixableProps, rangeStart);
|
|
432
|
-
return fixer.replaceTextRange([rangeStart, rangeEnd], sortedText);
|
|
433
|
-
} : null
|
|
434
|
-
});
|
|
435
|
-
fixProvided = true;
|
|
436
|
-
};
|
|
437
|
-
if (variablesBeforeFunctions) {
|
|
438
|
-
if (prev.isFunction && !curr.isFunction) {
|
|
439
|
-
reportWithFix();
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
if (prev.isFunction === curr.isFunction && compareKeys(prev.keyName, curr.keyName) > 0) {
|
|
443
|
-
reportWithFix();
|
|
444
|
-
}
|
|
445
|
-
} else {
|
|
446
|
-
if (compareKeys(prev.keyName, curr.keyName) > 0) {
|
|
447
|
-
reportWithFix();
|
|
448
|
-
}
|
|
465
|
+
if (variablesBeforeFunctions && prev.isFunction && !curr.isFunction) {
|
|
466
|
+
createReportWithFix(curr, shouldFix);
|
|
467
|
+
return;
|
|
449
468
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if (expr && expr.type === "ObjectExpression") {
|
|
457
|
-
checkObjectExpression(expr);
|
|
469
|
+
if (variablesBeforeFunctions && prev.isFunction === curr.isFunction && compareKeys(prev.keyName, curr.keyName) > 0) {
|
|
470
|
+
createReportWithFix(curr, shouldFix);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (!variablesBeforeFunctions && compareKeys(prev.keyName, curr.keyName) > 0) {
|
|
474
|
+
createReportWithFix(curr, shouldFix);
|
|
458
475
|
}
|
|
476
|
+
});
|
|
477
|
+
};
|
|
478
|
+
const checkJSXAttributeObject = (attr) => {
|
|
479
|
+
const { value } = attr;
|
|
480
|
+
if (value && value.type === "JSXExpressionContainer" && value.expression && value.expression.type === "ObjectExpression") {
|
|
481
|
+
checkObjectExpression(value.expression);
|
|
459
482
|
}
|
|
460
|
-
}
|
|
461
|
-
|
|
483
|
+
};
|
|
484
|
+
const getAttrName = (attr) => {
|
|
485
|
+
if (attr.type !== "JSXAttribute" || attr.name.type !== "JSXIdentifier") {
|
|
486
|
+
return "";
|
|
487
|
+
}
|
|
488
|
+
return attr.name.name;
|
|
489
|
+
};
|
|
490
|
+
const compareAttrNames = (nameLeft, nameRight) => {
|
|
491
|
+
let res = compareKeys(nameLeft, nameRight);
|
|
492
|
+
if (order === "desc") {
|
|
493
|
+
res = -res;
|
|
494
|
+
}
|
|
495
|
+
return res;
|
|
496
|
+
};
|
|
497
|
+
const isOutOfOrder = (names) => names.some((currName, idx) => {
|
|
498
|
+
if (idx === 0 || !currName) {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
const prevName = names[idx - 1];
|
|
502
|
+
return prevName !== undefined && compareAttrNames(prevName, currName) > 0;
|
|
503
|
+
});
|
|
504
|
+
const checkJSXOpeningElement = (node) => {
|
|
462
505
|
const attrs = node.attributes;
|
|
463
506
|
if (attrs.length < minKeys) {
|
|
464
507
|
return;
|
|
465
508
|
}
|
|
466
|
-
if (attrs.some((
|
|
509
|
+
if (attrs.some((attr) => attr.type !== "JSXAttribute")) {
|
|
467
510
|
return;
|
|
468
511
|
}
|
|
469
|
-
if (attrs.some((
|
|
512
|
+
if (attrs.some((attr) => attr.type === "JSXAttribute" && attr.name.type !== "JSXIdentifier")) {
|
|
470
513
|
return;
|
|
471
514
|
}
|
|
472
|
-
const names = attrs.map((
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
if (a.name.type !== "JSXIdentifier") {
|
|
477
|
-
return "";
|
|
478
|
-
}
|
|
479
|
-
return a.name.name;
|
|
480
|
-
});
|
|
481
|
-
const cmp = (a, b) => {
|
|
482
|
-
let res = compareKeys(a, b);
|
|
483
|
-
if (order === "desc") {
|
|
484
|
-
res = -res;
|
|
485
|
-
}
|
|
486
|
-
return res;
|
|
487
|
-
};
|
|
488
|
-
let outOfOrder = false;
|
|
489
|
-
for (let i = 1;i < names.length; i++) {
|
|
490
|
-
const prevName = names[i - 1];
|
|
491
|
-
const currName = names[i];
|
|
492
|
-
if (!prevName || !currName) {
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
if (cmp(prevName, currName) > 0) {
|
|
496
|
-
outOfOrder = true;
|
|
497
|
-
break;
|
|
498
|
-
}
|
|
515
|
+
const names = attrs.map((attr) => getAttrName(attr));
|
|
516
|
+
if (!isOutOfOrder(names)) {
|
|
517
|
+
return;
|
|
499
518
|
}
|
|
500
|
-
if (
|
|
519
|
+
if (hasDuplicateNames(names)) {
|
|
520
|
+
context.report({
|
|
521
|
+
messageId: "unsorted",
|
|
522
|
+
node: attrs[0].type === "JSXAttribute" ? attrs[0].name : attrs[0]
|
|
523
|
+
});
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (attrs.some((attr) => attr.type === "JSXAttribute" && !isSafeJSXAttributeValue(attr.value))) {
|
|
527
|
+
context.report({
|
|
528
|
+
messageId: "unsorted",
|
|
529
|
+
node: attrs[0].type === "JSXAttribute" ? attrs[0].name : attrs[0]
|
|
530
|
+
});
|
|
501
531
|
return;
|
|
502
532
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (!prevAttr || !currAttr) {
|
|
507
|
-
continue;
|
|
533
|
+
const braceConflict = attrs.find((currAttr, idx) => {
|
|
534
|
+
if (idx === 0) {
|
|
535
|
+
return false;
|
|
508
536
|
}
|
|
509
|
-
const
|
|
510
|
-
if (
|
|
511
|
-
|
|
512
|
-
node: currAttr.type === "JSXAttribute" ? currAttr.name : currAttr,
|
|
513
|
-
messageId: "unsorted"
|
|
514
|
-
});
|
|
515
|
-
return;
|
|
537
|
+
const prevAttr = attrs[idx - 1];
|
|
538
|
+
if (!prevAttr) {
|
|
539
|
+
return false;
|
|
516
540
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const aName = a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" ? a.name.name : "";
|
|
520
|
-
const bName = b.type === "JSXAttribute" && b.name.type === "JSXIdentifier" ? b.name.name : "";
|
|
521
|
-
return cmp(aName, bName);
|
|
541
|
+
const between = sourceCode.text.slice(prevAttr.range[1], currAttr.range[0]);
|
|
542
|
+
return between.includes("{");
|
|
522
543
|
});
|
|
523
|
-
|
|
544
|
+
if (braceConflict) {
|
|
545
|
+
context.report({
|
|
546
|
+
messageId: "unsorted",
|
|
547
|
+
node: braceConflict.type === "JSXAttribute" ? braceConflict.name : braceConflict
|
|
548
|
+
});
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const sortedAttrs = attrs.slice().sort((left, right) => compareAttrNames(getAttrName(left), getAttrName(right)));
|
|
552
|
+
const [firstAttr] = attrs;
|
|
524
553
|
const lastAttr = attrs[attrs.length - 1];
|
|
525
554
|
if (!firstAttr || !lastAttr) {
|
|
526
555
|
return;
|
|
527
556
|
}
|
|
528
|
-
const replacement = sortedAttrs.map((
|
|
557
|
+
const replacement = sortedAttrs.map((attr) => sourceCode.getText(attr)).join(" ");
|
|
529
558
|
context.report({
|
|
530
|
-
node: firstAttr.type === "JSXAttribute" ? firstAttr.name : firstAttr,
|
|
531
|
-
messageId: "unsorted",
|
|
532
559
|
fix(fixer) {
|
|
533
560
|
return fixer.replaceTextRange([firstAttr.range[0], lastAttr.range[1]], replacement);
|
|
534
|
-
}
|
|
561
|
+
},
|
|
562
|
+
messageId: "unsorted",
|
|
563
|
+
node: firstAttr.type === "JSXAttribute" ? firstAttr.name : firstAttr
|
|
535
564
|
});
|
|
536
|
-
}
|
|
565
|
+
};
|
|
537
566
|
return {
|
|
538
|
-
ObjectExpression: checkObjectExpression,
|
|
539
567
|
JSXAttribute(node) {
|
|
540
568
|
checkJSXAttributeObject(node);
|
|
541
569
|
},
|
|
542
|
-
JSXOpeningElement: checkJSXOpeningElement
|
|
570
|
+
JSXOpeningElement: checkJSXOpeningElement,
|
|
571
|
+
ObjectExpression: checkObjectExpression
|
|
543
572
|
};
|
|
573
|
+
},
|
|
574
|
+
defaultOptions: [{}],
|
|
575
|
+
meta: {
|
|
576
|
+
docs: {
|
|
577
|
+
description: "enforce sorted keys in object literals with auto-fix (limited to simple cases, preserving comments)"
|
|
578
|
+
},
|
|
579
|
+
fixable: "code",
|
|
580
|
+
messages: {
|
|
581
|
+
unsorted: "Object keys are not sorted."
|
|
582
|
+
},
|
|
583
|
+
schema: [
|
|
584
|
+
{
|
|
585
|
+
additionalProperties: false,
|
|
586
|
+
properties: {
|
|
587
|
+
caseSensitive: {
|
|
588
|
+
type: "boolean"
|
|
589
|
+
},
|
|
590
|
+
minKeys: {
|
|
591
|
+
minimum: 2,
|
|
592
|
+
type: "integer"
|
|
593
|
+
},
|
|
594
|
+
natural: {
|
|
595
|
+
type: "boolean"
|
|
596
|
+
},
|
|
597
|
+
order: {
|
|
598
|
+
enum: ["asc", "desc"],
|
|
599
|
+
type: "string"
|
|
600
|
+
},
|
|
601
|
+
variablesBeforeFunctions: {
|
|
602
|
+
type: "boolean"
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
type: "object"
|
|
606
|
+
}
|
|
607
|
+
],
|
|
608
|
+
type: "suggestion"
|
|
544
609
|
}
|
|
545
610
|
};
|
|
546
611
|
|
|
547
612
|
// src/rules/no-transition-cssproperties.ts
|
|
613
|
+
var getKeyName = (prop) => {
|
|
614
|
+
if (prop.key.type === "Identifier") {
|
|
615
|
+
return prop.key.name;
|
|
616
|
+
}
|
|
617
|
+
if (prop.key.type !== "Literal") {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
return typeof prop.key.value === "string" ? prop.key.value : String(prop.key.value);
|
|
621
|
+
};
|
|
622
|
+
var checkPropForTransition = (context, prop) => {
|
|
623
|
+
if (prop.computed) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const keyName = getKeyName(prop);
|
|
627
|
+
if (keyName === "transition") {
|
|
628
|
+
context.report({
|
|
629
|
+
messageId: "forbiddenTransition",
|
|
630
|
+
node: prop
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
};
|
|
548
634
|
var noTransitionCSSProperties = {
|
|
549
|
-
meta: {
|
|
550
|
-
type: "problem",
|
|
551
|
-
docs: {
|
|
552
|
-
description: "Objects typed as CSSProperties must not include a 'transition' property as it conflicts with react-spring."
|
|
553
|
-
},
|
|
554
|
-
schema: [],
|
|
555
|
-
messages: {
|
|
556
|
-
forbiddenTransition: "Objects typed as CSSProperties must not include a 'transition' property as it conflicts with react-spring."
|
|
557
|
-
}
|
|
558
|
-
},
|
|
559
|
-
defaultOptions: [],
|
|
560
635
|
create(context) {
|
|
561
|
-
const sourceCode = context
|
|
636
|
+
const { sourceCode } = context;
|
|
637
|
+
const isCSSPropertiesType = (typeAnnotation) => {
|
|
638
|
+
if (typeAnnotation.type !== "TSTypeReference") {
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
const { typeName } = typeAnnotation;
|
|
642
|
+
if (typeName.type === "Identifier" && typeName.name === "CSSProperties") {
|
|
643
|
+
return true;
|
|
644
|
+
}
|
|
645
|
+
return typeName.type === "TSQualifiedName" && typeName.right && typeName.right.type === "Identifier" && typeName.right.name === "CSSProperties";
|
|
646
|
+
};
|
|
562
647
|
return {
|
|
563
648
|
VariableDeclarator(node) {
|
|
564
649
|
if (!node.id || node.id.type !== "Identifier" || !node.id.typeAnnotation) {
|
|
565
650
|
return;
|
|
566
651
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
if (typeAnnotation && typeAnnotation.type === "TSTypeReference") {
|
|
570
|
-
const typeName = typeAnnotation.typeName;
|
|
571
|
-
if (typeName.type === "Identifier" && typeName.name === "CSSProperties") {
|
|
572
|
-
isStyleType = true;
|
|
573
|
-
} else if (typeName.type === "TSQualifiedName" && typeName.right && typeName.right.type === "Identifier" && typeName.right.name === "CSSProperties") {
|
|
574
|
-
isStyleType = true;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
652
|
+
const { typeAnnotation } = node.id.typeAnnotation;
|
|
653
|
+
let isStyleType = isCSSPropertiesType(typeAnnotation);
|
|
577
654
|
if (!isStyleType) {
|
|
578
655
|
const annotationText = sourceCode.getText(node.id.typeAnnotation);
|
|
579
|
-
|
|
580
|
-
isStyleType = true;
|
|
581
|
-
}
|
|
656
|
+
isStyleType = annotationText.includes("CSSProperties");
|
|
582
657
|
}
|
|
583
658
|
if (!isStyleType) {
|
|
584
659
|
return;
|
|
585
660
|
}
|
|
586
|
-
const init = node
|
|
661
|
+
const { init } = node;
|
|
587
662
|
if (!init || init.type !== "ObjectExpression") {
|
|
588
663
|
return;
|
|
589
664
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (prop.computed) {
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
let keyName = null;
|
|
598
|
-
if (prop.key.type === "Identifier") {
|
|
599
|
-
keyName = prop.key.name;
|
|
600
|
-
} else if (prop.key.type === "Literal") {
|
|
601
|
-
if (typeof prop.key.value === "string") {
|
|
602
|
-
keyName = prop.key.value;
|
|
603
|
-
} else {
|
|
604
|
-
keyName = String(prop.key.value);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
if (keyName === "transition") {
|
|
608
|
-
context.report({
|
|
609
|
-
node: prop,
|
|
610
|
-
messageId: "forbiddenTransition"
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
}
|
|
665
|
+
const properties = init.properties.filter((prop) => prop.type === "Property");
|
|
666
|
+
properties.forEach((prop) => {
|
|
667
|
+
checkPropForTransition(context, prop);
|
|
668
|
+
});
|
|
614
669
|
}
|
|
615
670
|
};
|
|
671
|
+
},
|
|
672
|
+
defaultOptions: [],
|
|
673
|
+
meta: {
|
|
674
|
+
docs: {
|
|
675
|
+
description: "Objects typed as CSSProperties must not include a 'transition' property as it conflicts with react-spring."
|
|
676
|
+
},
|
|
677
|
+
messages: {
|
|
678
|
+
forbiddenTransition: "Objects typed as CSSProperties must not include a 'transition' property as it conflicts with react-spring."
|
|
679
|
+
},
|
|
680
|
+
schema: [],
|
|
681
|
+
type: "problem"
|
|
616
682
|
}
|
|
617
683
|
};
|
|
618
684
|
|
|
619
685
|
// src/rules/no-explicit-return-types.ts
|
|
620
686
|
var noExplicitReturnTypes = {
|
|
621
|
-
meta: {
|
|
622
|
-
type: "suggestion",
|
|
623
|
-
docs: {
|
|
624
|
-
description: "Disallow explicit return type annotations on functions, except when using type predicates for type guards or inline object literal returns (e.g., style objects)."
|
|
625
|
-
},
|
|
626
|
-
schema: [],
|
|
627
|
-
messages: {
|
|
628
|
-
noExplicitReturnType: "Explicit return types are disallowed; rely on TypeScript's inference instead."
|
|
629
|
-
}
|
|
630
|
-
},
|
|
631
|
-
defaultOptions: [],
|
|
632
687
|
create(context) {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (stmt.type === "ReturnStatement") {
|
|
638
|
-
returnCount++;
|
|
639
|
-
const arg = stmt.argument;
|
|
640
|
-
if (arg && arg.type === "ObjectExpression") {
|
|
641
|
-
returnedObject = arg;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
688
|
+
const hasSingleObjectReturn = (body) => {
|
|
689
|
+
const returnStatements = body.body.filter((stmt) => stmt.type === "ReturnStatement");
|
|
690
|
+
if (returnStatements.length !== 1) {
|
|
691
|
+
return false;
|
|
644
692
|
}
|
|
645
|
-
|
|
646
|
-
|
|
693
|
+
const [returnStmt] = returnStatements;
|
|
694
|
+
return returnStmt?.argument?.type === "ObjectExpression";
|
|
695
|
+
};
|
|
647
696
|
return {
|
|
648
697
|
"FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(node) {
|
|
649
|
-
const returnType = node
|
|
698
|
+
const { returnType } = node;
|
|
650
699
|
if (!returnType) {
|
|
651
700
|
return;
|
|
652
701
|
}
|
|
653
|
-
const typeAnnotation = returnType
|
|
702
|
+
const { typeAnnotation } = returnType;
|
|
654
703
|
if (typeAnnotation && typeAnnotation.type === "TSTypePredicate") {
|
|
655
704
|
return;
|
|
656
705
|
}
|
|
657
706
|
if (node.type === "ArrowFunctionExpression" && node.expression === true && node.body.type === "ObjectExpression") {
|
|
658
707
|
return;
|
|
659
708
|
}
|
|
660
|
-
if (node.body && node.body.type === "BlockStatement") {
|
|
661
|
-
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
709
|
+
if (node.body && node.body.type === "BlockStatement" && hasSingleObjectReturn(node.body)) {
|
|
710
|
+
return;
|
|
664
711
|
}
|
|
665
712
|
context.report({
|
|
666
|
-
|
|
667
|
-
|
|
713
|
+
messageId: "noExplicitReturnType",
|
|
714
|
+
node: returnType
|
|
668
715
|
});
|
|
669
716
|
}
|
|
670
717
|
};
|
|
718
|
+
},
|
|
719
|
+
defaultOptions: [],
|
|
720
|
+
meta: {
|
|
721
|
+
docs: {
|
|
722
|
+
description: "Disallow explicit return type annotations on functions, except when using type predicates for type guards or inline object literal returns (e.g., style objects)."
|
|
723
|
+
},
|
|
724
|
+
messages: {
|
|
725
|
+
noExplicitReturnType: "Explicit return types are disallowed; rely on TypeScript's inference instead."
|
|
726
|
+
},
|
|
727
|
+
schema: [],
|
|
728
|
+
type: "suggestion"
|
|
671
729
|
}
|
|
672
730
|
};
|
|
673
731
|
|
|
674
732
|
// src/rules/max-jsx-nesting.ts
|
|
733
|
+
var isJSXAncestor = (node) => node.type === "JSXElement" || node.type === "JSXFragment";
|
|
675
734
|
var maxJSXNesting = {
|
|
676
|
-
meta: {
|
|
677
|
-
type: "suggestion",
|
|
678
|
-
docs: {
|
|
679
|
-
description: "Warn when JSX elements are nested too deeply, suggesting refactoring into a separate component."
|
|
680
|
-
},
|
|
681
|
-
schema: [
|
|
682
|
-
{
|
|
683
|
-
type: "number",
|
|
684
|
-
minimum: 1
|
|
685
|
-
}
|
|
686
|
-
],
|
|
687
|
-
messages: {
|
|
688
|
-
tooDeeplyNested: "JSX element is nested too deeply ({{level}} levels, allowed is {{maxAllowed}} levels). Consider refactoring into a separate component."
|
|
689
|
-
}
|
|
690
|
-
},
|
|
691
|
-
defaultOptions: [1],
|
|
692
735
|
create(context) {
|
|
693
|
-
const option = context.options
|
|
736
|
+
const [option] = context.options;
|
|
694
737
|
const maxAllowed = typeof option === "number" ? option : 1;
|
|
695
|
-
|
|
738
|
+
const getJSXNestingLevel = (node) => {
|
|
696
739
|
let level = 1;
|
|
697
740
|
let current = node.parent;
|
|
698
741
|
while (current) {
|
|
699
|
-
|
|
700
|
-
level++;
|
|
701
|
-
}
|
|
742
|
+
level += isJSXAncestor(current) ? 1 : 0;
|
|
702
743
|
current = current.parent;
|
|
703
744
|
}
|
|
704
745
|
return level;
|
|
705
|
-
}
|
|
746
|
+
};
|
|
706
747
|
return {
|
|
707
748
|
JSXElement(node) {
|
|
708
749
|
const level = getJSXNestingLevel(node);
|
|
709
750
|
if (level > maxAllowed) {
|
|
710
751
|
context.report({
|
|
711
|
-
|
|
752
|
+
data: { level, maxAllowed },
|
|
712
753
|
messageId: "tooDeeplyNested",
|
|
713
|
-
|
|
754
|
+
node
|
|
714
755
|
});
|
|
715
756
|
}
|
|
716
757
|
}
|
|
717
758
|
};
|
|
759
|
+
},
|
|
760
|
+
defaultOptions: [1],
|
|
761
|
+
meta: {
|
|
762
|
+
docs: {
|
|
763
|
+
description: "Warn when JSX elements are nested too deeply, suggesting refactoring into a separate component."
|
|
764
|
+
},
|
|
765
|
+
messages: {
|
|
766
|
+
tooDeeplyNested: "JSX element is nested too deeply ({{level}} levels, allowed is {{maxAllowed}} levels). Consider refactoring into a separate component."
|
|
767
|
+
},
|
|
768
|
+
schema: [
|
|
769
|
+
{
|
|
770
|
+
minimum: 1,
|
|
771
|
+
type: "number"
|
|
772
|
+
}
|
|
773
|
+
],
|
|
774
|
+
type: "suggestion"
|
|
718
775
|
}
|
|
719
776
|
};
|
|
720
777
|
|
|
721
778
|
// src/rules/seperate-style-files.ts
|
|
722
779
|
var seperateStyleFiles = {
|
|
723
|
-
meta: {
|
|
724
|
-
type: "suggestion",
|
|
725
|
-
docs: {
|
|
726
|
-
description: "Warn when a component file (.jsx or .tsx) contains a style object typed as CSSProperties. " + "Style objects should be moved to their own file under the style folder."
|
|
727
|
-
},
|
|
728
|
-
schema: [],
|
|
729
|
-
messages: {
|
|
730
|
-
moveToFile: 'Style object "{{name}}" is typed as {{typeName}}. Move it to its own file under the style folder.'
|
|
731
|
-
}
|
|
732
|
-
},
|
|
733
|
-
defaultOptions: [],
|
|
734
780
|
create(context) {
|
|
735
|
-
const filename = context
|
|
781
|
+
const { filename } = context;
|
|
736
782
|
if (!filename.endsWith(".tsx") && !filename.endsWith(".jsx")) {
|
|
737
783
|
return {};
|
|
738
784
|
}
|
|
@@ -755,39 +801,46 @@ var seperateStyleFiles = {
|
|
|
755
801
|
if (typeNameNode.type === "Identifier") {
|
|
756
802
|
typeName = typeNameNode.name;
|
|
757
803
|
} else if (typeNameNode.type === "TSQualifiedName") {
|
|
758
|
-
const right = typeNameNode
|
|
804
|
+
const { right } = typeNameNode;
|
|
759
805
|
typeName = right.name;
|
|
760
806
|
}
|
|
761
807
|
if (typeName === "CSSProperties") {
|
|
762
808
|
context.report({
|
|
763
|
-
node,
|
|
764
|
-
messageId: "moveToFile",
|
|
765
809
|
data: {
|
|
766
810
|
name: identifier.name,
|
|
767
811
|
typeName
|
|
768
|
-
}
|
|
812
|
+
},
|
|
813
|
+
messageId: "moveToFile",
|
|
814
|
+
node
|
|
769
815
|
});
|
|
770
816
|
}
|
|
771
817
|
}
|
|
772
818
|
};
|
|
819
|
+
},
|
|
820
|
+
defaultOptions: [],
|
|
821
|
+
meta: {
|
|
822
|
+
docs: {
|
|
823
|
+
description: "Warn when a component file (.jsx or .tsx) contains a style object typed as CSSProperties. " + "Style objects should be moved to their own file under the style folder."
|
|
824
|
+
},
|
|
825
|
+
messages: {
|
|
826
|
+
moveToFile: 'Style object "{{name}}" is typed as {{typeName}}. Move it to its own file under the style folder.'
|
|
827
|
+
},
|
|
828
|
+
schema: [],
|
|
829
|
+
type: "suggestion"
|
|
773
830
|
}
|
|
774
831
|
};
|
|
775
832
|
|
|
776
833
|
// src/rules/no-unnecessary-key.ts
|
|
834
|
+
var isMapCallExpression = (node) => {
|
|
835
|
+
if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
const { property } = node.callee;
|
|
839
|
+
return property.type === "Identifier" && property.name === "map" || property.type === "Literal" && property.value === "map";
|
|
840
|
+
};
|
|
777
841
|
var noUnnecessaryKey = {
|
|
778
|
-
meta: {
|
|
779
|
-
type: "problem",
|
|
780
|
-
docs: {
|
|
781
|
-
description: "enforce that the key prop is only used on components rendered as part of a mapping"
|
|
782
|
-
},
|
|
783
|
-
schema: [],
|
|
784
|
-
messages: {
|
|
785
|
-
unnecessaryKey: "The key prop should only be used on elements that are directly rendered as part of an array mapping."
|
|
786
|
-
}
|
|
787
|
-
},
|
|
788
|
-
defaultOptions: [],
|
|
789
842
|
create(context) {
|
|
790
|
-
|
|
843
|
+
const getAncestors = (node) => {
|
|
791
844
|
const ancestors = [];
|
|
792
845
|
let current = node.parent;
|
|
793
846
|
while (current) {
|
|
@@ -795,40 +848,15 @@ var noUnnecessaryKey = {
|
|
|
795
848
|
current = current.parent;
|
|
796
849
|
}
|
|
797
850
|
return ancestors;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const property = node.callee.property;
|
|
803
|
-
if (property.type === "Identifier" && property.name === "map") {
|
|
804
|
-
return true;
|
|
805
|
-
}
|
|
806
|
-
if (property.type === "Literal" && property.value === "map") {
|
|
807
|
-
return true;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
return false;
|
|
812
|
-
}
|
|
813
|
-
function isReturnedFromFunction(ancestors) {
|
|
814
|
-
for (const node of ancestors) {
|
|
815
|
-
if (node.type === "ReturnStatement") {
|
|
816
|
-
return true;
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
return false;
|
|
820
|
-
}
|
|
821
|
-
function checkJSXOpeningElement(node) {
|
|
851
|
+
};
|
|
852
|
+
const isInsideMapCall = (ancestors) => ancestors.some(isMapCallExpression);
|
|
853
|
+
const isReturnedFromFunction = (ancestors) => ancestors.some((ancestor) => ancestor.type === "ReturnStatement");
|
|
854
|
+
const checkJSXOpeningElement = (node) => {
|
|
822
855
|
const keyAttribute = node.attributes.find((attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === "key");
|
|
823
856
|
if (!keyAttribute) {
|
|
824
857
|
return;
|
|
825
858
|
}
|
|
826
|
-
|
|
827
|
-
if (typeof context.getAncestors === "function") {
|
|
828
|
-
ancestors = context.getAncestors();
|
|
829
|
-
} else {
|
|
830
|
-
ancestors = getAncestors(node);
|
|
831
|
-
}
|
|
859
|
+
const ancestors = getAncestors(node);
|
|
832
860
|
if (isInsideMapCall(ancestors)) {
|
|
833
861
|
return;
|
|
834
862
|
}
|
|
@@ -836,304 +864,389 @@ var noUnnecessaryKey = {
|
|
|
836
864
|
return;
|
|
837
865
|
}
|
|
838
866
|
context.report({
|
|
839
|
-
|
|
840
|
-
|
|
867
|
+
messageId: "unnecessaryKey",
|
|
868
|
+
node: keyAttribute
|
|
841
869
|
});
|
|
842
|
-
}
|
|
870
|
+
};
|
|
843
871
|
return {
|
|
844
872
|
JSXOpeningElement: checkJSXOpeningElement
|
|
845
873
|
};
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
// src/rules/sort-exports.ts
|
|
850
|
-
var sortExports = {
|
|
874
|
+
},
|
|
875
|
+
defaultOptions: [],
|
|
851
876
|
meta: {
|
|
852
|
-
type: "suggestion",
|
|
853
877
|
docs: {
|
|
854
|
-
description: "
|
|
878
|
+
description: "enforce that the key prop is only used on components rendered as part of a mapping"
|
|
855
879
|
},
|
|
856
|
-
fixable: "code",
|
|
857
|
-
schema: [
|
|
858
|
-
{
|
|
859
|
-
type: "object",
|
|
860
|
-
properties: {
|
|
861
|
-
order: {
|
|
862
|
-
type: "string",
|
|
863
|
-
enum: ["asc", "desc"]
|
|
864
|
-
},
|
|
865
|
-
caseSensitive: {
|
|
866
|
-
type: "boolean"
|
|
867
|
-
},
|
|
868
|
-
natural: {
|
|
869
|
-
type: "boolean"
|
|
870
|
-
},
|
|
871
|
-
minKeys: {
|
|
872
|
-
type: "integer",
|
|
873
|
-
minimum: 2
|
|
874
|
-
},
|
|
875
|
-
variablesBeforeFunctions: {
|
|
876
|
-
type: "boolean"
|
|
877
|
-
}
|
|
878
|
-
},
|
|
879
|
-
additionalProperties: false
|
|
880
|
-
}
|
|
881
|
-
],
|
|
882
880
|
messages: {
|
|
883
|
-
|
|
884
|
-
|
|
881
|
+
unnecessaryKey: "The key prop should only be used on elements that are directly rendered as part of an array mapping."
|
|
882
|
+
},
|
|
883
|
+
schema: [],
|
|
884
|
+
type: "problem"
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
// src/rules/sort-exports.ts
|
|
889
|
+
var SORT_BEFORE2 = Number.parseInt("-1", 10);
|
|
890
|
+
var hasStringTypeProperty = (value) => {
|
|
891
|
+
const maybeType = Reflect.get(value, "type");
|
|
892
|
+
return typeof maybeType === "string";
|
|
893
|
+
};
|
|
894
|
+
var isNodeLike = (value) => value !== null && value !== undefined && typeof value === "object" && ("type" in value) && hasStringTypeProperty(value);
|
|
895
|
+
var shouldSkipNodeEntry = (key, value) => key === "parent" || value === null || value === undefined;
|
|
896
|
+
var visitNodeArray = (values, visit) => values.filter(isNodeLike).forEach(visit);
|
|
897
|
+
var visitNodeEntryValue = (value, visit) => {
|
|
898
|
+
if (Array.isArray(value)) {
|
|
899
|
+
visitNodeArray(value, visit);
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (isNodeLike(value)) {
|
|
903
|
+
visit(value);
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
var visitNodeEntries = (current, visit) => Object.entries(current).filter(([key, value]) => !shouldSkipNodeEntry(key, value)).forEach(([, value]) => {
|
|
907
|
+
visitNodeEntryValue(value, visit);
|
|
908
|
+
});
|
|
909
|
+
var getVariableDeclaratorName = (declaration) => {
|
|
910
|
+
if (declaration.declarations.length !== 1) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
const [firstDeclarator] = declaration.declarations;
|
|
914
|
+
if (firstDeclarator && firstDeclarator.id.type === "Identifier") {
|
|
915
|
+
return firstDeclarator.id.name;
|
|
916
|
+
}
|
|
917
|
+
return null;
|
|
918
|
+
};
|
|
919
|
+
var getDeclarationName = (declaration) => {
|
|
920
|
+
if (!declaration) {
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
if (declaration.type === "VariableDeclaration") {
|
|
924
|
+
return getVariableDeclaratorName(declaration);
|
|
925
|
+
}
|
|
926
|
+
if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && declaration.id && declaration.id.type === "Identifier") {
|
|
927
|
+
return declaration.id.name;
|
|
928
|
+
}
|
|
929
|
+
return null;
|
|
930
|
+
};
|
|
931
|
+
var getSpecifierName = (node) => {
|
|
932
|
+
if (node.specifiers.length !== 1) {
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
const [spec] = node.specifiers;
|
|
936
|
+
if (!spec) {
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
if (spec.exported.type === "Identifier") {
|
|
940
|
+
return spec.exported.name;
|
|
941
|
+
}
|
|
942
|
+
if (spec.exported.type === "Literal" && typeof spec.exported.value === "string") {
|
|
943
|
+
return spec.exported.value;
|
|
944
|
+
}
|
|
945
|
+
return null;
|
|
946
|
+
};
|
|
947
|
+
var getExportName = (node) => getDeclarationName(node.declaration) ?? getSpecifierName(node);
|
|
948
|
+
var isFixableExport = (exportNode) => {
|
|
949
|
+
const { declaration } = exportNode;
|
|
950
|
+
if (!declaration) {
|
|
951
|
+
return exportNode.specifiers.length === 1;
|
|
952
|
+
}
|
|
953
|
+
if (declaration.type === "VariableDeclaration" && declaration.declarations.length === 1) {
|
|
954
|
+
const [firstDecl] = declaration.declarations;
|
|
955
|
+
return firstDecl !== undefined && firstDecl.id.type === "Identifier";
|
|
956
|
+
}
|
|
957
|
+
return (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && declaration.id !== null && declaration.id.type === "Identifier";
|
|
958
|
+
};
|
|
959
|
+
var visitImmediateReferences = (node, onReference) => {
|
|
960
|
+
if (!node) {
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
const visit = (current) => {
|
|
964
|
+
if (!current) {
|
|
965
|
+
return;
|
|
885
966
|
}
|
|
886
|
-
|
|
887
|
-
|
|
967
|
+
switch (current.type) {
|
|
968
|
+
case "Identifier":
|
|
969
|
+
onReference(current.name);
|
|
970
|
+
return;
|
|
971
|
+
case "FunctionDeclaration":
|
|
972
|
+
case "FunctionExpression":
|
|
973
|
+
case "ArrowFunctionExpression":
|
|
974
|
+
return;
|
|
975
|
+
case "MemberExpression":
|
|
976
|
+
visit(current.object);
|
|
977
|
+
if (current.computed) {
|
|
978
|
+
visit(current.property);
|
|
979
|
+
}
|
|
980
|
+
return;
|
|
981
|
+
case "Property":
|
|
982
|
+
if (current.computed) {
|
|
983
|
+
visit(current.key);
|
|
984
|
+
}
|
|
985
|
+
visit(current.value);
|
|
986
|
+
return;
|
|
987
|
+
case "PropertyDefinition":
|
|
988
|
+
if (current.computed) {
|
|
989
|
+
visit(current.key);
|
|
990
|
+
}
|
|
991
|
+
if (current.static) {
|
|
992
|
+
visit(current.value);
|
|
993
|
+
}
|
|
994
|
+
return;
|
|
995
|
+
case "MethodDefinition":
|
|
996
|
+
if (current.computed) {
|
|
997
|
+
visit(current.key);
|
|
998
|
+
}
|
|
999
|
+
return;
|
|
1000
|
+
case "StaticBlock":
|
|
1001
|
+
for (const statement of current.body) {
|
|
1002
|
+
visit(statement);
|
|
1003
|
+
}
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
visitNodeEntries(current, visit);
|
|
1007
|
+
};
|
|
1008
|
+
visit(node);
|
|
1009
|
+
};
|
|
1010
|
+
var getImmediateDependencyNames = (node) => {
|
|
1011
|
+
const names = new Set;
|
|
1012
|
+
const { declaration } = node;
|
|
1013
|
+
const addName = names.add.bind(names);
|
|
1014
|
+
const addDeclaratorDependencies = (declarator) => visitImmediateReferences(declarator.init, addName);
|
|
1015
|
+
const addClassElementDependencies = (element) => visitImmediateReferences(element, addName);
|
|
1016
|
+
if (!declaration) {
|
|
1017
|
+
return names;
|
|
1018
|
+
}
|
|
1019
|
+
if (declaration.type === "VariableDeclaration") {
|
|
1020
|
+
declaration.declarations.forEach(addDeclaratorDependencies);
|
|
1021
|
+
return names;
|
|
1022
|
+
}
|
|
1023
|
+
if (declaration.type === "ClassDeclaration") {
|
|
1024
|
+
visitImmediateReferences(declaration.superClass, addName);
|
|
1025
|
+
declaration.body.body.forEach(addClassElementDependencies);
|
|
1026
|
+
}
|
|
1027
|
+
return names;
|
|
1028
|
+
};
|
|
1029
|
+
var sortExports = {
|
|
888
1030
|
create(context) {
|
|
889
|
-
const sourceCode = context
|
|
890
|
-
const option = context.options
|
|
1031
|
+
const { sourceCode } = context;
|
|
1032
|
+
const [option] = context.options;
|
|
891
1033
|
const order = option && option.order ? option.order : "asc";
|
|
892
1034
|
const caseSensitive = option && typeof option.caseSensitive === "boolean" ? option.caseSensitive : false;
|
|
893
1035
|
const natural = option && typeof option.natural === "boolean" ? option.natural : false;
|
|
894
1036
|
const minKeys = option && typeof option.minKeys === "number" ? option.minKeys : 2;
|
|
895
1037
|
const variablesBeforeFunctions = option && typeof option.variablesBeforeFunctions === "boolean" ? option.variablesBeforeFunctions : false;
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
let strA = a;
|
|
901
|
-
let strB = b;
|
|
1038
|
+
const generateExportText = (node) => sourceCode.getText(node).trim().replace(/\s*;?\s*$/, ";");
|
|
1039
|
+
const compareStrings = (strLeft, strRight) => {
|
|
1040
|
+
let left = strLeft;
|
|
1041
|
+
let right = strRight;
|
|
902
1042
|
if (!caseSensitive) {
|
|
903
|
-
|
|
904
|
-
|
|
1043
|
+
left = left.toLowerCase();
|
|
1044
|
+
right = right.toLowerCase();
|
|
905
1045
|
}
|
|
906
|
-
const cmp = natural ?
|
|
1046
|
+
const cmp = natural ? left.localeCompare(right, undefined, { numeric: true }) : left.localeCompare(right);
|
|
907
1047
|
return order === "asc" ? cmp : -cmp;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
const declaration = node
|
|
911
|
-
if (declaration) {
|
|
912
|
-
if (declaration.type === "VariableDeclaration") {
|
|
913
|
-
if (declaration.declarations.length === 1) {
|
|
914
|
-
const firstDeclarator = declaration.declarations[0];
|
|
915
|
-
if (firstDeclarator && firstDeclarator.id.type === "Identifier") {
|
|
916
|
-
return firstDeclarator.id.name;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
} else if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
|
|
920
|
-
const id = declaration.id;
|
|
921
|
-
if (id && id.type === "Identifier") {
|
|
922
|
-
return id.name;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
} else if (node.specifiers.length === 1) {
|
|
926
|
-
const spec = node.specifiers[0];
|
|
927
|
-
if (!spec) {
|
|
928
|
-
return null;
|
|
929
|
-
}
|
|
930
|
-
if (spec.exported.type === "Identifier") {
|
|
931
|
-
return spec.exported.name;
|
|
932
|
-
}
|
|
933
|
-
if (spec.exported.type === "Literal" && typeof spec.exported.value === "string") {
|
|
934
|
-
return spec.exported.value;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
return null;
|
|
938
|
-
}
|
|
939
|
-
function isFunctionExport(node) {
|
|
940
|
-
const declaration = node.declaration;
|
|
1048
|
+
};
|
|
1049
|
+
const isFunctionExport = (node) => {
|
|
1050
|
+
const { declaration } = node;
|
|
941
1051
|
if (!declaration) {
|
|
942
1052
|
return false;
|
|
943
1053
|
}
|
|
944
|
-
if (declaration.type === "VariableDeclaration") {
|
|
945
|
-
if (declaration.declarations.length === 1) {
|
|
946
|
-
const firstDeclarator = declaration.declarations[0];
|
|
947
|
-
if (!firstDeclarator) {
|
|
948
|
-
return false;
|
|
949
|
-
}
|
|
950
|
-
const init = firstDeclarator.init;
|
|
951
|
-
if (!init) {
|
|
952
|
-
return false;
|
|
953
|
-
}
|
|
954
|
-
return init.type === "FunctionExpression" || init.type === "ArrowFunctionExpression";
|
|
955
|
-
}
|
|
956
|
-
return false;
|
|
957
|
-
}
|
|
958
1054
|
if (declaration.type === "FunctionDeclaration") {
|
|
959
1055
|
return true;
|
|
960
1056
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
function sortComparator(a, b) {
|
|
964
|
-
const kindA = a.node.exportKind ?? "value";
|
|
965
|
-
const kindB = b.node.exportKind ?? "value";
|
|
966
|
-
if (kindA !== kindB) {
|
|
967
|
-
return kindA === "type" ? -1 : 1;
|
|
1057
|
+
if (declaration.type !== "VariableDeclaration") {
|
|
1058
|
+
return false;
|
|
968
1059
|
}
|
|
969
|
-
if (
|
|
970
|
-
|
|
971
|
-
return a.isFunction ? 1 : -1;
|
|
972
|
-
}
|
|
1060
|
+
if (declaration.declarations.length !== 1) {
|
|
1061
|
+
return false;
|
|
973
1062
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const text = sourceCode.getText(node);
|
|
978
|
-
for (const name of laterNames) {
|
|
979
|
-
if (text.includes(name)) {
|
|
980
|
-
return true;
|
|
981
|
-
}
|
|
1063
|
+
const [firstDeclarator] = declaration.declarations;
|
|
1064
|
+
if (!firstDeclarator) {
|
|
1065
|
+
return false;
|
|
982
1066
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
if (block.length < minKeys) {
|
|
987
|
-
return;
|
|
1067
|
+
const { init } = firstDeclarator;
|
|
1068
|
+
if (!init) {
|
|
1069
|
+
return false;
|
|
988
1070
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
name,
|
|
997
|
-
node,
|
|
998
|
-
isFunction: isFunctionExport(node),
|
|
999
|
-
text: sourceCode.getText(node)
|
|
1000
|
-
});
|
|
1071
|
+
return init.type === "FunctionExpression" || init.type === "ArrowFunctionExpression";
|
|
1072
|
+
};
|
|
1073
|
+
const sortComparator = (left, right) => {
|
|
1074
|
+
const kindA = left.node.exportKind ?? "value";
|
|
1075
|
+
const kindB = right.node.exportKind ?? "value";
|
|
1076
|
+
if (kindA !== kindB) {
|
|
1077
|
+
return kindA === "type" ? SORT_BEFORE2 : 1;
|
|
1001
1078
|
}
|
|
1002
|
-
if (
|
|
1003
|
-
return;
|
|
1079
|
+
if (variablesBeforeFunctions && left.isFunction !== right.isFunction) {
|
|
1080
|
+
return left.isFunction ? 1 : SORT_BEFORE2;
|
|
1004
1081
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1082
|
+
return compareStrings(left.name, right.name);
|
|
1083
|
+
};
|
|
1084
|
+
const buildItems = (block) => block.map((node) => {
|
|
1085
|
+
const name = getExportName(node);
|
|
1086
|
+
if (!name) {
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
const item = {
|
|
1090
|
+
isFunction: isFunctionExport(node),
|
|
1091
|
+
name,
|
|
1092
|
+
node,
|
|
1093
|
+
text: sourceCode.getText(node)
|
|
1094
|
+
};
|
|
1095
|
+
return item;
|
|
1096
|
+
}).filter((item) => item !== null);
|
|
1097
|
+
const findFirstUnsorted = (items) => {
|
|
1007
1098
|
let messageId = "alphabetical";
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1099
|
+
const unsorted = items.some((current, idx) => {
|
|
1100
|
+
if (idx === 0) {
|
|
1101
|
+
return false;
|
|
1102
|
+
}
|
|
1103
|
+
const prev = items[idx - 1];
|
|
1104
|
+
if (!prev) {
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
if (sortComparator(prev, current) <= 0) {
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
1110
|
+
if (variablesBeforeFunctions && prev.isFunction && !current.isFunction) {
|
|
1111
|
+
messageId = "variablesBeforeFunctions";
|
|
1112
|
+
}
|
|
1113
|
+
return true;
|
|
1114
|
+
});
|
|
1115
|
+
return unsorted ? messageId : null;
|
|
1116
|
+
};
|
|
1117
|
+
const checkForwardDependencies = (items) => {
|
|
1118
|
+
const exportNames = items.map((item) => item.name);
|
|
1119
|
+
return items.some((item, idx) => {
|
|
1120
|
+
const laterNames = new Set(exportNames.slice(idx + 1));
|
|
1121
|
+
if (laterNames.size === 0) {
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
const dependencies = getImmediateDependencyNames(item.node);
|
|
1125
|
+
for (const dependency of dependencies) {
|
|
1126
|
+
if (laterNames.has(dependency)) {
|
|
1127
|
+
return true;
|
|
1018
1128
|
}
|
|
1019
|
-
break;
|
|
1020
1129
|
}
|
|
1130
|
+
return false;
|
|
1131
|
+
});
|
|
1132
|
+
};
|
|
1133
|
+
const processExportBlock = (block) => {
|
|
1134
|
+
if (block.length < minKeys) {
|
|
1135
|
+
return;
|
|
1021
1136
|
}
|
|
1022
|
-
|
|
1137
|
+
const items = buildItems(block);
|
|
1138
|
+
if (items.length < minKeys) {
|
|
1023
1139
|
return;
|
|
1024
1140
|
}
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
const laterNames = new Set(exportNames.slice(i + 1));
|
|
1032
|
-
const nodeToCheck = item.node.declaration ?? item.node;
|
|
1033
|
-
if (hasForwardDependency(nodeToCheck, laterNames)) {
|
|
1034
|
-
return;
|
|
1035
|
-
}
|
|
1141
|
+
const messageId = findFirstUnsorted(items);
|
|
1142
|
+
if (!messageId) {
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (checkForwardDependencies(items)) {
|
|
1146
|
+
return;
|
|
1036
1147
|
}
|
|
1148
|
+
const sortedItems = items.slice().sort(sortComparator);
|
|
1037
1149
|
const expectedOrder = sortedItems.map((item) => item.name).join(", ");
|
|
1038
|
-
const firstNode = block
|
|
1150
|
+
const [firstNode] = block;
|
|
1039
1151
|
const lastNode = block[block.length - 1];
|
|
1040
1152
|
if (!firstNode || !lastNode) {
|
|
1041
1153
|
return;
|
|
1042
1154
|
}
|
|
1043
1155
|
context.report({
|
|
1044
|
-
node: firstNode,
|
|
1045
|
-
messageId,
|
|
1046
1156
|
data: {
|
|
1047
1157
|
expectedOrder
|
|
1048
1158
|
},
|
|
1049
1159
|
fix(fixer) {
|
|
1050
|
-
const fixableNodes =
|
|
1051
|
-
for (const n of block) {
|
|
1052
|
-
const declaration = n.declaration;
|
|
1053
|
-
if (declaration) {
|
|
1054
|
-
if (declaration.type === "VariableDeclaration" && declaration.declarations.length === 1) {
|
|
1055
|
-
const firstDecl = declaration.declarations[0];
|
|
1056
|
-
if (firstDecl && firstDecl.id.type === "Identifier") {
|
|
1057
|
-
fixableNodes.push(n);
|
|
1058
|
-
continue;
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && declaration.id && declaration.id.type === "Identifier") {
|
|
1062
|
-
fixableNodes.push(n);
|
|
1063
|
-
continue;
|
|
1064
|
-
}
|
|
1065
|
-
continue;
|
|
1066
|
-
}
|
|
1067
|
-
if (n.specifiers.length === 1) {
|
|
1068
|
-
fixableNodes.push(n);
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1160
|
+
const fixableNodes = block.filter(isFixableExport);
|
|
1071
1161
|
if (fixableNodes.length < minKeys) {
|
|
1072
1162
|
return null;
|
|
1073
1163
|
}
|
|
1074
1164
|
const sortedText = sortedItems.map((item) => generateExportText(item.node)).join(`
|
|
1075
1165
|
`);
|
|
1076
|
-
const rangeStart = firstNode.range
|
|
1077
|
-
const rangeEnd = lastNode.range
|
|
1166
|
+
const [rangeStart] = firstNode.range;
|
|
1167
|
+
const [, rangeEnd] = lastNode.range;
|
|
1078
1168
|
const fullText = sourceCode.getText();
|
|
1079
1169
|
const originalText = fullText.slice(rangeStart, rangeEnd);
|
|
1080
1170
|
if (originalText === sortedText) {
|
|
1081
1171
|
return null;
|
|
1082
1172
|
}
|
|
1083
1173
|
return fixer.replaceTextRange([rangeStart, rangeEnd], sortedText);
|
|
1084
|
-
}
|
|
1174
|
+
},
|
|
1175
|
+
messageId,
|
|
1176
|
+
node: firstNode
|
|
1085
1177
|
});
|
|
1086
|
-
}
|
|
1178
|
+
};
|
|
1087
1179
|
return {
|
|
1088
1180
|
"Program:exit"(node) {
|
|
1089
|
-
const body = node
|
|
1181
|
+
const { body } = node;
|
|
1090
1182
|
const block = [];
|
|
1091
|
-
|
|
1092
|
-
const stmt = body[i];
|
|
1093
|
-
if (!stmt) {
|
|
1094
|
-
continue;
|
|
1095
|
-
}
|
|
1183
|
+
body.forEach((stmt) => {
|
|
1096
1184
|
if (stmt.type === "ExportNamedDeclaration" && !stmt.source && getExportName(stmt) !== null) {
|
|
1097
1185
|
block.push(stmt);
|
|
1098
|
-
|
|
1099
|
-
if (block.length > 0) {
|
|
1100
|
-
processExportBlock(block);
|
|
1101
|
-
block.length = 0;
|
|
1102
|
-
}
|
|
1186
|
+
return;
|
|
1103
1187
|
}
|
|
1104
|
-
|
|
1188
|
+
if (block.length > 0) {
|
|
1189
|
+
processExportBlock(block);
|
|
1190
|
+
block.length = 0;
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1105
1193
|
if (block.length > 0) {
|
|
1106
1194
|
processExportBlock(block);
|
|
1107
1195
|
}
|
|
1108
1196
|
}
|
|
1109
1197
|
};
|
|
1198
|
+
},
|
|
1199
|
+
defaultOptions: [{}],
|
|
1200
|
+
meta: {
|
|
1201
|
+
docs: {
|
|
1202
|
+
description: "Enforce that top-level export declarations are sorted by exported name and, optionally, that variable exports come before function exports"
|
|
1203
|
+
},
|
|
1204
|
+
fixable: "code",
|
|
1205
|
+
messages: {
|
|
1206
|
+
alphabetical: "Export declarations are not sorted alphabetically. Expected order: {{expectedOrder}}.",
|
|
1207
|
+
variablesBeforeFunctions: "Non-function exports should come before function exports."
|
|
1208
|
+
},
|
|
1209
|
+
schema: [
|
|
1210
|
+
{
|
|
1211
|
+
additionalProperties: false,
|
|
1212
|
+
properties: {
|
|
1213
|
+
caseSensitive: {
|
|
1214
|
+
type: "boolean"
|
|
1215
|
+
},
|
|
1216
|
+
minKeys: {
|
|
1217
|
+
minimum: 2,
|
|
1218
|
+
type: "integer"
|
|
1219
|
+
},
|
|
1220
|
+
natural: {
|
|
1221
|
+
type: "boolean"
|
|
1222
|
+
},
|
|
1223
|
+
order: {
|
|
1224
|
+
enum: ["asc", "desc"],
|
|
1225
|
+
type: "string"
|
|
1226
|
+
},
|
|
1227
|
+
variablesBeforeFunctions: {
|
|
1228
|
+
type: "boolean"
|
|
1229
|
+
}
|
|
1230
|
+
},
|
|
1231
|
+
type: "object"
|
|
1232
|
+
}
|
|
1233
|
+
],
|
|
1234
|
+
type: "suggestion"
|
|
1110
1235
|
}
|
|
1111
1236
|
};
|
|
1112
1237
|
|
|
1113
1238
|
// src/rules/localize-react-props.ts
|
|
1114
1239
|
import { AST_NODE_TYPES as AST_NODE_TYPES2 } from "@typescript-eslint/utils";
|
|
1115
1240
|
var localizeReactProps = {
|
|
1116
|
-
meta: {
|
|
1117
|
-
type: "suggestion",
|
|
1118
|
-
docs: {
|
|
1119
|
-
description: "Disallow variables that are only passed to a single custom child component. For useState, only report if both the state and its setter are exclusively passed to a single custom child. For general variables, only report if a given child receives exactly one such candidate \u2013 if two or more are passed to the same component type, they\u2019re assumed to be settings that belong on the parent."
|
|
1120
|
-
},
|
|
1121
|
-
schema: [],
|
|
1122
|
-
messages: {
|
|
1123
|
-
stateAndSetterToChild: "State variable '{{stateVarName}}' and its setter '{{setterVarName}}' are only passed to a single custom child component. Consider moving the state into that component.",
|
|
1124
|
-
variableToChild: "Variable '{{varName}}' is only passed to a single custom child component. Consider moving it to that component."
|
|
1125
|
-
}
|
|
1126
|
-
},
|
|
1127
|
-
defaultOptions: [],
|
|
1128
1241
|
create(context) {
|
|
1129
1242
|
const candidateVariables = [];
|
|
1130
|
-
|
|
1243
|
+
const getSingleSetElement = (set) => {
|
|
1131
1244
|
for (const value of set) {
|
|
1132
1245
|
return value;
|
|
1133
1246
|
}
|
|
1134
1247
|
return null;
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1248
|
+
};
|
|
1249
|
+
const getRightmostJSXIdentifier = (name) => {
|
|
1137
1250
|
let current = name;
|
|
1138
1251
|
while (current.type === AST_NODE_TYPES2.JSXMemberExpression) {
|
|
1139
1252
|
current = current.property;
|
|
@@ -1142,8 +1255,8 @@ var localizeReactProps = {
|
|
|
1142
1255
|
return current;
|
|
1143
1256
|
}
|
|
1144
1257
|
return null;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1258
|
+
};
|
|
1259
|
+
const getLeftmostJSXIdentifier = (name) => {
|
|
1147
1260
|
let current = name;
|
|
1148
1261
|
while (current.type === AST_NODE_TYPES2.JSXMemberExpression) {
|
|
1149
1262
|
current = current.object;
|
|
@@ -1152,8 +1265,8 @@ var localizeReactProps = {
|
|
|
1152
1265
|
return current;
|
|
1153
1266
|
}
|
|
1154
1267
|
return null;
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1268
|
+
};
|
|
1269
|
+
const getJSXElementName = (jsxElement) => {
|
|
1157
1270
|
if (!jsxElement || !jsxElement.openingElement || !jsxElement.openingElement.name) {
|
|
1158
1271
|
return "";
|
|
1159
1272
|
}
|
|
@@ -1166,14 +1279,10 @@ var localizeReactProps = {
|
|
|
1166
1279
|
return rightmost.name;
|
|
1167
1280
|
}
|
|
1168
1281
|
return "";
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
function isHookCall(node) {
|
|
1174
|
-
return node !== null && node.type === AST_NODE_TYPES2.CallExpression && node.callee !== null && node.callee.type === AST_NODE_TYPES2.Identifier && /^use[A-Z]/.test(node.callee.name) && node.callee.name !== "useState";
|
|
1175
|
-
}
|
|
1176
|
-
function getJSXAncestor(node) {
|
|
1282
|
+
};
|
|
1283
|
+
const isUseStateCall = (node) => node !== null && node.type === AST_NODE_TYPES2.CallExpression && node.callee !== null && (node.callee.type === AST_NODE_TYPES2.Identifier && node.callee.name === "useState" || node.callee.type === AST_NODE_TYPES2.MemberExpression && node.callee.property !== null && node.callee.property.type === AST_NODE_TYPES2.Identifier && node.callee.property.name === "useState");
|
|
1284
|
+
const isHookCall = (node) => node !== null && node.type === AST_NODE_TYPES2.CallExpression && node.callee !== null && node.callee.type === AST_NODE_TYPES2.Identifier && /^use[A-Z]/.test(node.callee.name) && node.callee.name !== "useState";
|
|
1285
|
+
const getJSXAncestor = (node) => {
|
|
1177
1286
|
let current = node.parent;
|
|
1178
1287
|
while (current) {
|
|
1179
1288
|
if (current.type === AST_NODE_TYPES2.JSXElement) {
|
|
@@ -1182,33 +1291,31 @@ var localizeReactProps = {
|
|
|
1182
1291
|
current = current.parent;
|
|
1183
1292
|
}
|
|
1184
1293
|
return null;
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1294
|
+
};
|
|
1295
|
+
const getTagNameFromOpening = (openingElement) => {
|
|
1296
|
+
const nameNode = openingElement.name;
|
|
1297
|
+
if (nameNode.type === AST_NODE_TYPES2.JSXIdentifier) {
|
|
1298
|
+
return nameNode.name;
|
|
1299
|
+
}
|
|
1300
|
+
const rightmost = getRightmostJSXIdentifier(nameNode);
|
|
1301
|
+
return rightmost ? rightmost.name : null;
|
|
1302
|
+
};
|
|
1303
|
+
const isProviderOrContext = (tagName) => tagName.endsWith("Provider") || tagName.endsWith("Context");
|
|
1304
|
+
const isValueAttributeOnProvider = (node) => node.type === AST_NODE_TYPES2.JSXAttribute && node.name && node.name.type === AST_NODE_TYPES2.JSXIdentifier && node.name.name === "value" && node.parent && node.parent.type === AST_NODE_TYPES2.JSXOpeningElement && (() => {
|
|
1305
|
+
const tagName = getTagNameFromOpening(node.parent);
|
|
1306
|
+
return tagName !== null && isProviderOrContext(tagName);
|
|
1307
|
+
})();
|
|
1308
|
+
const isContextProviderValueProp = (node) => {
|
|
1187
1309
|
let current = node.parent;
|
|
1188
1310
|
while (current) {
|
|
1189
|
-
if (current
|
|
1190
|
-
|
|
1191
|
-
const nameNode = current.parent.name;
|
|
1192
|
-
if (nameNode.type === AST_NODE_TYPES2.JSXIdentifier) {
|
|
1193
|
-
const tagName = nameNode.name;
|
|
1194
|
-
if (tagName.endsWith("Provider") || tagName.endsWith("Context")) {
|
|
1195
|
-
return true;
|
|
1196
|
-
}
|
|
1197
|
-
} else {
|
|
1198
|
-
const rightmost = getRightmostJSXIdentifier(nameNode);
|
|
1199
|
-
if (rightmost) {
|
|
1200
|
-
if (rightmost.name.endsWith("Provider") || rightmost.name.endsWith("Context")) {
|
|
1201
|
-
return true;
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1311
|
+
if (isValueAttributeOnProvider(current)) {
|
|
1312
|
+
return true;
|
|
1206
1313
|
}
|
|
1207
1314
|
current = current.parent;
|
|
1208
1315
|
}
|
|
1209
1316
|
return false;
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1317
|
+
};
|
|
1318
|
+
const isCustomJSXElement = (jsxElement) => {
|
|
1212
1319
|
if (!jsxElement || !jsxElement.openingElement || !jsxElement.openingElement.name) {
|
|
1213
1320
|
return false;
|
|
1214
1321
|
}
|
|
@@ -1217,12 +1324,9 @@ var localizeReactProps = {
|
|
|
1217
1324
|
return /^[A-Z]/.test(nameNode.name);
|
|
1218
1325
|
}
|
|
1219
1326
|
const leftmost = getLeftmostJSXIdentifier(nameNode);
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
return false;
|
|
1224
|
-
}
|
|
1225
|
-
function getComponentFunction(node) {
|
|
1327
|
+
return leftmost !== null && /^[A-Z]/.test(leftmost.name);
|
|
1328
|
+
};
|
|
1329
|
+
const getComponentFunction = (node) => {
|
|
1226
1330
|
let current = node;
|
|
1227
1331
|
while (current) {
|
|
1228
1332
|
if (current.type === AST_NODE_TYPES2.FunctionDeclaration || current.type === AST_NODE_TYPES2.FunctionExpression || current.type === AST_NODE_TYPES2.ArrowFunctionExpression) {
|
|
@@ -1231,145 +1335,125 @@ var localizeReactProps = {
|
|
|
1231
1335
|
current = current.parent;
|
|
1232
1336
|
}
|
|
1233
1337
|
return null;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
let scope = context.sourceCode.getScope(
|
|
1338
|
+
};
|
|
1339
|
+
const findVariableForIdentifier = (identifier) => {
|
|
1340
|
+
let scope = context.sourceCode.getScope(identifier);
|
|
1237
1341
|
while (scope) {
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
return variable;
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1342
|
+
const found = scope.variables.find((variable) => variable.defs.some((def) => def.name === identifier));
|
|
1343
|
+
if (found) {
|
|
1344
|
+
return found;
|
|
1244
1345
|
}
|
|
1245
1346
|
scope = scope.upper ?? null;
|
|
1246
1347
|
}
|
|
1247
1348
|
return null;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1349
|
+
};
|
|
1350
|
+
const classifyReference = (reference, declarationId, jsxUsageSet) => {
|
|
1351
|
+
const { identifier } = reference;
|
|
1352
|
+
if (identifier === declarationId || isContextProviderValueProp(identifier)) {
|
|
1353
|
+
return false;
|
|
1354
|
+
}
|
|
1355
|
+
const jsxAncestor = getJSXAncestor(identifier);
|
|
1356
|
+
if (jsxAncestor && isCustomJSXElement(jsxAncestor)) {
|
|
1357
|
+
jsxUsageSet.add(jsxAncestor);
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
return true;
|
|
1361
|
+
};
|
|
1362
|
+
const analyzeVariableUsage = (declarationId) => {
|
|
1250
1363
|
const variable = findVariableForIdentifier(declarationId);
|
|
1251
1364
|
if (!variable) {
|
|
1252
1365
|
return {
|
|
1253
|
-
|
|
1254
|
-
|
|
1366
|
+
hasOutsideUsage: false,
|
|
1367
|
+
jsxUsageSet: new Set
|
|
1255
1368
|
};
|
|
1256
1369
|
}
|
|
1257
1370
|
const jsxUsageSet = new Set;
|
|
1258
|
-
|
|
1259
|
-
for (const reference of variable.references) {
|
|
1260
|
-
const identifier = reference.identifier;
|
|
1261
|
-
if (identifier === declarationId) {
|
|
1262
|
-
continue;
|
|
1263
|
-
}
|
|
1264
|
-
if (isContextProviderValueProp(identifier)) {
|
|
1265
|
-
continue;
|
|
1266
|
-
}
|
|
1267
|
-
const jsxAncestor = getJSXAncestor(identifier);
|
|
1268
|
-
if (jsxAncestor && isCustomJSXElement(jsxAncestor)) {
|
|
1269
|
-
jsxUsageSet.add(jsxAncestor);
|
|
1270
|
-
} else {
|
|
1271
|
-
hasOutsideUsage = true;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1371
|
+
const hasOutsideUsage = variable.references.some((ref) => classifyReference(ref, declarationId, jsxUsageSet));
|
|
1274
1372
|
return {
|
|
1275
|
-
|
|
1276
|
-
|
|
1373
|
+
hasOutsideUsage,
|
|
1374
|
+
jsxUsageSet
|
|
1277
1375
|
};
|
|
1278
|
-
}
|
|
1376
|
+
};
|
|
1279
1377
|
const componentHookVars = new WeakMap;
|
|
1280
|
-
|
|
1378
|
+
const getHookSet = (componentFunction) => {
|
|
1281
1379
|
let hookSet = componentHookVars.get(componentFunction);
|
|
1282
1380
|
if (!hookSet) {
|
|
1283
1381
|
hookSet = new Set;
|
|
1284
1382
|
componentHookVars.set(componentFunction, hookSet);
|
|
1285
1383
|
}
|
|
1286
1384
|
return hookSet;
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1385
|
+
};
|
|
1386
|
+
const isRangeContained = (refRange, nodeRange) => refRange[0] >= nodeRange[0] && refRange[1] <= nodeRange[1];
|
|
1387
|
+
const variableHasReferenceInRange = (variable, nodeRange) => variable.references.some((reference) => reference.identifier.range !== undefined && isRangeContained(reference.identifier.range, nodeRange));
|
|
1388
|
+
const hasHookDependency = (node, hookSet) => {
|
|
1289
1389
|
if (!node.range) {
|
|
1290
1390
|
return false;
|
|
1291
1391
|
}
|
|
1292
1392
|
const nodeRange = node.range;
|
|
1293
|
-
const nodeStart = nodeRange[0];
|
|
1294
|
-
const nodeEnd = nodeRange[1];
|
|
1295
1393
|
let scope = context.sourceCode.getScope(node);
|
|
1296
1394
|
while (scope) {
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
}
|
|
1301
|
-
for (const reference of variable.references) {
|
|
1302
|
-
const identifier = reference.identifier;
|
|
1303
|
-
if (!identifier.range) {
|
|
1304
|
-
continue;
|
|
1305
|
-
}
|
|
1306
|
-
const refRange = identifier.range;
|
|
1307
|
-
const refStart = refRange[0];
|
|
1308
|
-
const refEnd = refRange[1];
|
|
1309
|
-
if (refStart >= nodeStart && refEnd <= nodeEnd) {
|
|
1310
|
-
return true;
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1395
|
+
const hookVars = scope.variables.filter((variable) => hookSet.has(variable.name));
|
|
1396
|
+
if (hookVars.some((variable) => variableHasReferenceInRange(variable, nodeRange))) {
|
|
1397
|
+
return true;
|
|
1313
1398
|
}
|
|
1314
1399
|
scope = scope.upper ?? null;
|
|
1315
1400
|
}
|
|
1316
1401
|
return false;
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1402
|
+
};
|
|
1403
|
+
const processUseStateDeclarator = (node) => {
|
|
1404
|
+
if (!node.init || !isUseStateCall(node.init) || node.id.type !== AST_NODE_TYPES2.ArrayPattern || node.id.elements.length < 2) {
|
|
1405
|
+
return false;
|
|
1406
|
+
}
|
|
1407
|
+
const [stateElem, setterElem] = node.id.elements;
|
|
1408
|
+
if (!stateElem || stateElem.type !== AST_NODE_TYPES2.Identifier || !setterElem || setterElem.type !== AST_NODE_TYPES2.Identifier) {
|
|
1409
|
+
return false;
|
|
1410
|
+
}
|
|
1411
|
+
const stateVarName = stateElem.name;
|
|
1412
|
+
const setterVarName = setterElem.name;
|
|
1413
|
+
const stateUsage = analyzeVariableUsage(stateElem);
|
|
1414
|
+
const setterUsage = analyzeVariableUsage(setterElem);
|
|
1415
|
+
const stateExclusivelySingleJSX = !stateUsage.hasOutsideUsage && stateUsage.jsxUsageSet.size === 1;
|
|
1416
|
+
const setterExclusivelySingleJSX = !setterUsage.hasOutsideUsage && setterUsage.jsxUsageSet.size === 1;
|
|
1417
|
+
if (!stateExclusivelySingleJSX || !setterExclusivelySingleJSX) {
|
|
1418
|
+
return true;
|
|
1419
|
+
}
|
|
1420
|
+
const stateTarget = getSingleSetElement(stateUsage.jsxUsageSet);
|
|
1421
|
+
const setterTarget = getSingleSetElement(setterUsage.jsxUsageSet);
|
|
1422
|
+
if (stateTarget && stateTarget === setterTarget) {
|
|
1423
|
+
context.report({
|
|
1424
|
+
data: { setterVarName, stateVarName },
|
|
1425
|
+
messageId: "stateAndSetterToChild",
|
|
1426
|
+
node
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
return true;
|
|
1430
|
+
};
|
|
1431
|
+
const processGeneralVariable = (node, componentFunction) => {
|
|
1432
|
+
if (!node.id || node.id.type !== AST_NODE_TYPES2.Identifier) {
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
const varName = node.id.name;
|
|
1436
|
+
if (node.init) {
|
|
1437
|
+
const hookSet = getHookSet(componentFunction);
|
|
1438
|
+
if (hasHookDependency(node.init, hookSet)) {
|
|
1322
1439
|
return;
|
|
1323
|
-
if (node.init && node.id && node.id.type === AST_NODE_TYPES2.Identifier && node.init.type === AST_NODE_TYPES2.CallExpression && isHookCall(node.init)) {
|
|
1324
|
-
const hookSet = getHookSet(componentFunction);
|
|
1325
|
-
hookSet.add(node.id.name);
|
|
1326
|
-
}
|
|
1327
|
-
if (node.init && isUseStateCall(node.init) && node.id.type === AST_NODE_TYPES2.ArrayPattern && node.id.elements.length >= 2) {
|
|
1328
|
-
const stateElem = node.id.elements[0];
|
|
1329
|
-
const setterElem = node.id.elements[1];
|
|
1330
|
-
if (!stateElem || stateElem.type !== AST_NODE_TYPES2.Identifier || !setterElem || setterElem.type !== AST_NODE_TYPES2.Identifier) {
|
|
1331
|
-
return;
|
|
1332
|
-
}
|
|
1333
|
-
const stateVarName = stateElem.name;
|
|
1334
|
-
const setterVarName = setterElem.name;
|
|
1335
|
-
const stateUsage = analyzeVariableUsage(stateElem);
|
|
1336
|
-
const setterUsage = analyzeVariableUsage(setterElem);
|
|
1337
|
-
const stateExclusivelySingleJSX = !stateUsage.hasOutsideUsage && stateUsage.jsxUsageSet.size === 1;
|
|
1338
|
-
const setterExclusivelySingleJSX = !setterUsage.hasOutsideUsage && setterUsage.jsxUsageSet.size === 1;
|
|
1339
|
-
if (stateExclusivelySingleJSX && setterExclusivelySingleJSX) {
|
|
1340
|
-
const stateTarget = getSingleSetElement(stateUsage.jsxUsageSet);
|
|
1341
|
-
const setterTarget = getSingleSetElement(setterUsage.jsxUsageSet);
|
|
1342
|
-
if (stateTarget && stateTarget === setterTarget) {
|
|
1343
|
-
context.report({
|
|
1344
|
-
node,
|
|
1345
|
-
messageId: "stateAndSetterToChild",
|
|
1346
|
-
data: { stateVarName, setterVarName }
|
|
1347
|
-
});
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
} else if (node.id && node.id.type === AST_NODE_TYPES2.Identifier) {
|
|
1351
|
-
const varName = node.id.name;
|
|
1352
|
-
if (node.init) {
|
|
1353
|
-
const hookSet = getHookSet(componentFunction);
|
|
1354
|
-
if (hasHookDependency(node.init, hookSet)) {
|
|
1355
|
-
return;
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
const usage = analyzeVariableUsage(node.id);
|
|
1359
|
-
if (!usage.hasOutsideUsage && usage.jsxUsageSet.size === 1) {
|
|
1360
|
-
const target = getSingleSetElement(usage.jsxUsageSet);
|
|
1361
|
-
const componentName = getJSXElementName(target);
|
|
1362
|
-
candidateVariables.push({
|
|
1363
|
-
node,
|
|
1364
|
-
varName,
|
|
1365
|
-
componentName
|
|
1366
|
-
});
|
|
1367
|
-
}
|
|
1368
1440
|
}
|
|
1369
|
-
}
|
|
1441
|
+
}
|
|
1442
|
+
const usage = analyzeVariableUsage(node.id);
|
|
1443
|
+
if (!usage.hasOutsideUsage && usage.jsxUsageSet.size === 1) {
|
|
1444
|
+
const target = getSingleSetElement(usage.jsxUsageSet);
|
|
1445
|
+
const componentName = getJSXElementName(target);
|
|
1446
|
+
candidateVariables.push({
|
|
1447
|
+
componentName,
|
|
1448
|
+
node,
|
|
1449
|
+
varName
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
return {
|
|
1370
1454
|
"Program:exit"() {
|
|
1371
1455
|
const groups = new Map;
|
|
1372
|
-
|
|
1456
|
+
candidateVariables.forEach((candidate) => {
|
|
1373
1457
|
const key = candidate.componentName;
|
|
1374
1458
|
const existing = groups.get(key);
|
|
1375
1459
|
if (existing) {
|
|
@@ -1377,87 +1461,102 @@ var localizeReactProps = {
|
|
|
1377
1461
|
} else {
|
|
1378
1462
|
groups.set(key, [candidate]);
|
|
1379
1463
|
}
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
if (candidates.length
|
|
1383
|
-
|
|
1384
|
-
if (!candidate) {
|
|
1385
|
-
continue;
|
|
1386
|
-
}
|
|
1387
|
-
context.report({
|
|
1388
|
-
node: candidate.node,
|
|
1389
|
-
messageId: "variableToChild",
|
|
1390
|
-
data: { varName: candidate.varName }
|
|
1391
|
-
});
|
|
1464
|
+
});
|
|
1465
|
+
groups.forEach((candidates) => {
|
|
1466
|
+
if (candidates.length !== 1) {
|
|
1467
|
+
return;
|
|
1392
1468
|
}
|
|
1469
|
+
const [candidate] = candidates;
|
|
1470
|
+
if (!candidate) {
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
context.report({
|
|
1474
|
+
data: { varName: candidate.varName },
|
|
1475
|
+
messageId: "variableToChild",
|
|
1476
|
+
node: candidate.node
|
|
1477
|
+
});
|
|
1478
|
+
});
|
|
1479
|
+
},
|
|
1480
|
+
VariableDeclarator(node) {
|
|
1481
|
+
const componentFunction = getComponentFunction(node);
|
|
1482
|
+
if (!componentFunction || !componentFunction.body)
|
|
1483
|
+
return;
|
|
1484
|
+
if (node.init && node.id && node.id.type === AST_NODE_TYPES2.Identifier && node.init.type === AST_NODE_TYPES2.CallExpression && isHookCall(node.init)) {
|
|
1485
|
+
const hookSet = getHookSet(componentFunction);
|
|
1486
|
+
hookSet.add(node.id.name);
|
|
1487
|
+
}
|
|
1488
|
+
const wasUseState = processUseStateDeclarator(node);
|
|
1489
|
+
if (!wasUseState) {
|
|
1490
|
+
processGeneralVariable(node, componentFunction);
|
|
1393
1491
|
}
|
|
1394
1492
|
}
|
|
1395
1493
|
};
|
|
1494
|
+
},
|
|
1495
|
+
defaultOptions: [],
|
|
1496
|
+
meta: {
|
|
1497
|
+
docs: {
|
|
1498
|
+
description: "Disallow variables that are only passed to a single custom child component. For useState, only report if both the state and its setter are exclusively passed to a single custom child. For general variables, only report if a given child receives exactly one such candidate \u2013 if two or more are passed to the same component type, they're assumed to be settings that belong on the parent."
|
|
1499
|
+
},
|
|
1500
|
+
messages: {
|
|
1501
|
+
stateAndSetterToChild: "State variable '{{stateVarName}}' and its setter '{{setterVarName}}' are only passed to a single custom child component. Consider moving the state into that component.",
|
|
1502
|
+
variableToChild: "Variable '{{varName}}' is only passed to a single custom child component. Consider moving it to that component."
|
|
1503
|
+
},
|
|
1504
|
+
schema: [],
|
|
1505
|
+
type: "suggestion"
|
|
1396
1506
|
}
|
|
1397
1507
|
};
|
|
1398
1508
|
|
|
1399
1509
|
// src/rules/no-or-none-component.ts
|
|
1400
1510
|
var noOrNoneComponent = {
|
|
1401
|
-
meta: {
|
|
1402
|
-
type: "suggestion",
|
|
1403
|
-
docs: {
|
|
1404
|
-
description: "Prefer using logical && operator over ternary with null/undefined for conditional JSX rendering."
|
|
1405
|
-
},
|
|
1406
|
-
schema: [],
|
|
1407
|
-
messages: {
|
|
1408
|
-
useLogicalAnd: "Prefer using the logical '&&' operator instead of a ternary with null/undefined for conditional rendering."
|
|
1409
|
-
}
|
|
1410
|
-
},
|
|
1411
|
-
defaultOptions: [],
|
|
1412
1511
|
create(context) {
|
|
1413
1512
|
return {
|
|
1414
1513
|
ConditionalExpression(node) {
|
|
1415
|
-
const alternate = node
|
|
1514
|
+
const { alternate } = node;
|
|
1416
1515
|
const isNullAlternate = alternate && alternate.type === "Literal" && alternate.value === null;
|
|
1417
1516
|
const isUndefinedAlternate = alternate && alternate.type === "Identifier" && alternate.name === "undefined";
|
|
1418
1517
|
if (!isNullAlternate && !isUndefinedAlternate) {
|
|
1419
1518
|
return;
|
|
1420
1519
|
}
|
|
1421
|
-
const parent = node
|
|
1520
|
+
const { parent } = node;
|
|
1422
1521
|
if (!parent || parent.type !== "JSXExpressionContainer") {
|
|
1423
1522
|
return;
|
|
1424
1523
|
}
|
|
1425
1524
|
const containerParent = parent.parent;
|
|
1426
1525
|
if (containerParent && containerParent.type !== "JSXAttribute") {
|
|
1427
1526
|
context.report({
|
|
1428
|
-
|
|
1429
|
-
|
|
1527
|
+
messageId: "useLogicalAnd",
|
|
1528
|
+
node
|
|
1430
1529
|
});
|
|
1431
1530
|
}
|
|
1432
1531
|
}
|
|
1433
1532
|
};
|
|
1533
|
+
},
|
|
1534
|
+
defaultOptions: [],
|
|
1535
|
+
meta: {
|
|
1536
|
+
docs: {
|
|
1537
|
+
description: "Prefer using logical && operator over ternary with null/undefined for conditional JSX rendering."
|
|
1538
|
+
},
|
|
1539
|
+
messages: {
|
|
1540
|
+
useLogicalAnd: "Prefer using the logical '&&' operator instead of a ternary with null/undefined for conditional rendering."
|
|
1541
|
+
},
|
|
1542
|
+
schema: [],
|
|
1543
|
+
type: "suggestion"
|
|
1434
1544
|
}
|
|
1435
1545
|
};
|
|
1436
1546
|
|
|
1437
1547
|
// src/rules/no-button-navigation.ts
|
|
1438
1548
|
var noButtonNavigation = {
|
|
1439
|
-
meta: {
|
|
1440
|
-
type: "suggestion",
|
|
1441
|
-
docs: {
|
|
1442
|
-
description: "Enforce using anchor tags for navigation instead of buttons whose onClick handlers change the path. Allow only query/hash updates via window.location.search or history.replaceState(window.location.pathname + \u2026)."
|
|
1443
|
-
},
|
|
1444
|
-
schema: [],
|
|
1445
|
-
messages: {
|
|
1446
|
-
noButtonNavigation: "Use an anchor tag for navigation instead of a button whose onClick handler changes the path. Detected: {{reason}}. Only query/hash updates (reading window.location.search, .pathname, or .hash) are allowed."
|
|
1447
|
-
}
|
|
1448
|
-
},
|
|
1449
|
-
defaultOptions: [],
|
|
1450
1549
|
create(context) {
|
|
1451
1550
|
const handlerStack = [];
|
|
1452
|
-
|
|
1551
|
+
const getCurrentHandler = () => {
|
|
1453
1552
|
const state = handlerStack[handlerStack.length - 1];
|
|
1454
1553
|
if (!state) {
|
|
1455
1554
|
return null;
|
|
1456
1555
|
}
|
|
1457
1556
|
return state;
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
const parent = node
|
|
1557
|
+
};
|
|
1558
|
+
const isOnClickButtonHandler = (node) => {
|
|
1559
|
+
const { parent } = node;
|
|
1461
1560
|
if (!parent || parent.type !== "JSXExpressionContainer") {
|
|
1462
1561
|
return null;
|
|
1463
1562
|
}
|
|
@@ -1473,37 +1572,50 @@ var noButtonNavigation = {
|
|
|
1473
1572
|
if (!openingElementCandidate || openingElementCandidate.type !== "JSXOpeningElement") {
|
|
1474
1573
|
return null;
|
|
1475
1574
|
}
|
|
1476
|
-
const
|
|
1477
|
-
const tagNameNode = openingElement.name;
|
|
1575
|
+
const tagNameNode = openingElementCandidate.name;
|
|
1478
1576
|
if (tagNameNode.type !== "JSXIdentifier" || tagNameNode.name !== "button") {
|
|
1479
1577
|
return null;
|
|
1480
1578
|
}
|
|
1481
1579
|
return attr;
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
const object = member
|
|
1580
|
+
};
|
|
1581
|
+
const isWindowLocationMember = (member) => {
|
|
1582
|
+
const { object } = member;
|
|
1485
1583
|
if (object.type !== "MemberExpression") {
|
|
1486
1584
|
return false;
|
|
1487
1585
|
}
|
|
1488
1586
|
const outerObject = object.object;
|
|
1489
1587
|
const outerProperty = object.property;
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
}
|
|
1495
|
-
function isWindowHistoryMember(member) {
|
|
1496
|
-
const object = member.object;
|
|
1588
|
+
return outerObject.type === "Identifier" && outerObject.name === "window" && outerProperty.type === "Identifier" && outerProperty.name === "location";
|
|
1589
|
+
};
|
|
1590
|
+
const isWindowHistoryMember = (member) => {
|
|
1591
|
+
const { object } = member;
|
|
1497
1592
|
if (object.type !== "MemberExpression") {
|
|
1498
1593
|
return false;
|
|
1499
1594
|
}
|
|
1500
1595
|
const outerObject = object.object;
|
|
1501
1596
|
const outerProperty = object.property;
|
|
1502
|
-
|
|
1503
|
-
|
|
1597
|
+
return outerObject.type === "Identifier" && outerObject.name === "window" && outerProperty.type === "Identifier" && outerProperty.name === "history";
|
|
1598
|
+
};
|
|
1599
|
+
const reportHandlerExit = (state) => {
|
|
1600
|
+
const { reason, sawReplaceCall, sawAllowedLocationRead } = state;
|
|
1601
|
+
if (reason) {
|
|
1602
|
+
context.report({
|
|
1603
|
+
data: { reason },
|
|
1604
|
+
messageId: "noButtonNavigation",
|
|
1605
|
+
node: state.attribute
|
|
1606
|
+
});
|
|
1607
|
+
return;
|
|
1504
1608
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1609
|
+
if (sawReplaceCall && !sawAllowedLocationRead) {
|
|
1610
|
+
context.report({
|
|
1611
|
+
data: {
|
|
1612
|
+
reason: "history.replaceState/pushState without reading window.location"
|
|
1613
|
+
},
|
|
1614
|
+
messageId: "noButtonNavigation",
|
|
1615
|
+
node: state.attribute
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1507
1619
|
return {
|
|
1508
1620
|
ArrowFunctionExpression(node) {
|
|
1509
1621
|
const attr = isOnClickButtonHandler(node);
|
|
@@ -1513,8 +1625,8 @@ var noButtonNavigation = {
|
|
|
1513
1625
|
handlerStack.push({
|
|
1514
1626
|
attribute: attr,
|
|
1515
1627
|
reason: null,
|
|
1516
|
-
|
|
1517
|
-
|
|
1628
|
+
sawAllowedLocationRead: false,
|
|
1629
|
+
sawReplaceCall: false
|
|
1518
1630
|
});
|
|
1519
1631
|
},
|
|
1520
1632
|
"ArrowFunctionExpression:exit"(node) {
|
|
@@ -1526,25 +1638,40 @@ var noButtonNavigation = {
|
|
|
1526
1638
|
if (!state) {
|
|
1527
1639
|
return;
|
|
1528
1640
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
node: state.attribute,
|
|
1535
|
-
messageId: "noButtonNavigation",
|
|
1536
|
-
data: { reason }
|
|
1537
|
-
});
|
|
1641
|
+
reportHandlerExit(state);
|
|
1642
|
+
},
|
|
1643
|
+
AssignmentExpression(node) {
|
|
1644
|
+
const state = getCurrentHandler();
|
|
1645
|
+
if (!state) {
|
|
1538
1646
|
return;
|
|
1539
1647
|
}
|
|
1540
|
-
if (
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1648
|
+
if (node.left.type !== "MemberExpression") {
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
const { left } = node;
|
|
1652
|
+
if (left.object.type === "Identifier" && left.object.name === "window" && left.property.type === "Identifier" && left.property.name === "location" && !state.reason) {
|
|
1653
|
+
state.reason = "assignment to window.location";
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
if (isWindowLocationMember(left) && !state.reason) {
|
|
1657
|
+
state.reason = "assignment to window.location sub-property";
|
|
1658
|
+
}
|
|
1659
|
+
},
|
|
1660
|
+
CallExpression(node) {
|
|
1661
|
+
const state = getCurrentHandler();
|
|
1662
|
+
if (!state) {
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
const { callee } = node;
|
|
1666
|
+
if (callee.type !== "MemberExpression") {
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
if (isWindowLocationMember(callee) && callee.property.type === "Identifier" && callee.property.name === "replace" && !state.reason) {
|
|
1670
|
+
state.reason = "window.location.replace";
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
if (isWindowHistoryMember(callee) && callee.property.type === "Identifier" && (callee.property.name === "pushState" || callee.property.name === "replaceState")) {
|
|
1674
|
+
state.sawReplaceCall = true;
|
|
1548
1675
|
}
|
|
1549
1676
|
},
|
|
1550
1677
|
FunctionExpression(node) {
|
|
@@ -1555,8 +1682,8 @@ var noButtonNavigation = {
|
|
|
1555
1682
|
handlerStack.push({
|
|
1556
1683
|
attribute: attr,
|
|
1557
1684
|
reason: null,
|
|
1558
|
-
|
|
1559
|
-
|
|
1685
|
+
sawAllowedLocationRead: false,
|
|
1686
|
+
sawReplaceCall: false
|
|
1560
1687
|
});
|
|
1561
1688
|
},
|
|
1562
1689
|
"FunctionExpression:exit"(node) {
|
|
@@ -1568,159 +1695,99 @@ var noButtonNavigation = {
|
|
|
1568
1695
|
if (!state) {
|
|
1569
1696
|
return;
|
|
1570
1697
|
}
|
|
1571
|
-
|
|
1572
|
-
const sawReplaceCall = state.sawReplaceCall;
|
|
1573
|
-
const sawAllowedLocationRead = state.sawAllowedLocationRead;
|
|
1574
|
-
if (reason) {
|
|
1575
|
-
context.report({
|
|
1576
|
-
node: state.attribute,
|
|
1577
|
-
messageId: "noButtonNavigation",
|
|
1578
|
-
data: { reason }
|
|
1579
|
-
});
|
|
1580
|
-
return;
|
|
1581
|
-
}
|
|
1582
|
-
if (sawReplaceCall && !sawAllowedLocationRead) {
|
|
1583
|
-
context.report({
|
|
1584
|
-
node: state.attribute,
|
|
1585
|
-
messageId: "noButtonNavigation",
|
|
1586
|
-
data: {
|
|
1587
|
-
reason: "history.replaceState/pushState without reading window.location"
|
|
1588
|
-
}
|
|
1589
|
-
});
|
|
1590
|
-
}
|
|
1698
|
+
reportHandlerExit(state);
|
|
1591
1699
|
},
|
|
1592
1700
|
MemberExpression(node) {
|
|
1593
1701
|
const state = getCurrentHandler();
|
|
1594
1702
|
if (!state) {
|
|
1595
1703
|
return;
|
|
1596
1704
|
}
|
|
1597
|
-
if (node.object.type === "Identifier" && node.object.name === "window" && node.property.type === "Identifier" && node.property.name === "open") {
|
|
1598
|
-
|
|
1599
|
-
state.reason = "window.open";
|
|
1600
|
-
}
|
|
1705
|
+
if (node.object.type === "Identifier" && node.object.name === "window" && node.property.type === "Identifier" && node.property.name === "open" && !state.reason) {
|
|
1706
|
+
state.reason = "window.open";
|
|
1601
1707
|
}
|
|
1602
1708
|
if (isWindowLocationMember(node) && node.property.type === "Identifier" && (node.property.name === "search" || node.property.name === "pathname" || node.property.name === "hash")) {
|
|
1603
1709
|
state.sawAllowedLocationRead = true;
|
|
1604
1710
|
}
|
|
1605
|
-
},
|
|
1606
|
-
AssignmentExpression(node) {
|
|
1607
|
-
const state = getCurrentHandler();
|
|
1608
|
-
if (!state) {
|
|
1609
|
-
return;
|
|
1610
|
-
}
|
|
1611
|
-
if (node.left.type !== "MemberExpression") {
|
|
1612
|
-
return;
|
|
1613
|
-
}
|
|
1614
|
-
const left = node.left;
|
|
1615
|
-
if (left.object.type === "Identifier" && left.object.name === "window" && left.property.type === "Identifier" && left.property.name === "location") {
|
|
1616
|
-
if (!state.reason) {
|
|
1617
|
-
state.reason = "assignment to window.location";
|
|
1618
|
-
}
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
1621
|
-
if (isWindowLocationMember(left)) {
|
|
1622
|
-
if (!state.reason) {
|
|
1623
|
-
state.reason = "assignment to window.location sub-property";
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
},
|
|
1627
|
-
CallExpression(node) {
|
|
1628
|
-
const state = getCurrentHandler();
|
|
1629
|
-
if (!state) {
|
|
1630
|
-
return;
|
|
1631
|
-
}
|
|
1632
|
-
const callee = node.callee;
|
|
1633
|
-
if (callee.type !== "MemberExpression") {
|
|
1634
|
-
return;
|
|
1635
|
-
}
|
|
1636
|
-
if (isWindowLocationMember(callee) && callee.property.type === "Identifier" && callee.property.name === "replace") {
|
|
1637
|
-
if (!state.reason) {
|
|
1638
|
-
state.reason = "window.location.replace";
|
|
1639
|
-
}
|
|
1640
|
-
return;
|
|
1641
|
-
}
|
|
1642
|
-
if (isWindowHistoryMember(callee) && callee.property.type === "Identifier" && (callee.property.name === "pushState" || callee.property.name === "replaceState")) {
|
|
1643
|
-
state.sawReplaceCall = true;
|
|
1644
|
-
}
|
|
1645
1711
|
}
|
|
1646
1712
|
};
|
|
1713
|
+
},
|
|
1714
|
+
defaultOptions: [],
|
|
1715
|
+
meta: {
|
|
1716
|
+
docs: {
|
|
1717
|
+
description: "Enforce using anchor tags for navigation instead of buttons whose onClick handlers change the path. Allow only query/hash updates via window.location.search or history.replaceState(window.location.pathname + \u2026)."
|
|
1718
|
+
},
|
|
1719
|
+
messages: {
|
|
1720
|
+
noButtonNavigation: "Use an anchor tag for navigation instead of a button whose onClick handler changes the path. Detected: {{reason}}. Only query/hash updates (reading window.location.search, .pathname, or .hash) are allowed."
|
|
1721
|
+
},
|
|
1722
|
+
schema: [],
|
|
1723
|
+
type: "suggestion"
|
|
1647
1724
|
}
|
|
1648
1725
|
};
|
|
1649
1726
|
|
|
1650
1727
|
// src/rules/no-multi-style-objects.ts
|
|
1728
|
+
var getPropertyName = (prop) => {
|
|
1729
|
+
const { key } = prop;
|
|
1730
|
+
if (key.type === "Identifier") {
|
|
1731
|
+
return key.name;
|
|
1732
|
+
}
|
|
1733
|
+
if (key.type === "Literal" && typeof key.value === "string") {
|
|
1734
|
+
return key.value;
|
|
1735
|
+
}
|
|
1736
|
+
return null;
|
|
1737
|
+
};
|
|
1651
1738
|
var noMultiStyleObjects = {
|
|
1652
|
-
meta: {
|
|
1653
|
-
type: "problem",
|
|
1654
|
-
docs: {
|
|
1655
|
-
description: "Disallow grouping CSS style objects in a single export; export each style separately."
|
|
1656
|
-
},
|
|
1657
|
-
schema: [],
|
|
1658
|
-
messages: {
|
|
1659
|
-
noMultiStyleObjects: "Do not group CSS style objects in a single export; export each style separately."
|
|
1660
|
-
}
|
|
1661
|
-
},
|
|
1662
|
-
defaultOptions: [],
|
|
1663
1739
|
create(context) {
|
|
1664
|
-
|
|
1740
|
+
const checkObjectExpression = (node) => {
|
|
1665
1741
|
if (!node.properties.length) {
|
|
1666
1742
|
return;
|
|
1667
1743
|
}
|
|
1668
|
-
const cssStyleProperties =
|
|
1669
|
-
for (const prop of node.properties) {
|
|
1744
|
+
const cssStyleProperties = node.properties.filter((prop) => {
|
|
1670
1745
|
if (prop.type !== "Property") {
|
|
1671
|
-
|
|
1672
|
-
}
|
|
1673
|
-
const key = prop.key;
|
|
1674
|
-
let name = null;
|
|
1675
|
-
if (key.type === "Identifier") {
|
|
1676
|
-
name = key.name;
|
|
1677
|
-
} else if (key.type === "Literal" && typeof key.value === "string") {
|
|
1678
|
-
name = key.value;
|
|
1679
|
-
}
|
|
1680
|
-
if (name && name.endsWith("Style")) {
|
|
1681
|
-
cssStyleProperties.push(prop);
|
|
1746
|
+
return false;
|
|
1682
1747
|
}
|
|
1683
|
-
|
|
1748
|
+
const name = getPropertyName(prop);
|
|
1749
|
+
return name !== null && name.endsWith("Style");
|
|
1750
|
+
});
|
|
1684
1751
|
if (cssStyleProperties.length > 1) {
|
|
1685
1752
|
context.report({
|
|
1686
|
-
|
|
1687
|
-
|
|
1753
|
+
messageId: "noMultiStyleObjects",
|
|
1754
|
+
node
|
|
1688
1755
|
});
|
|
1689
1756
|
}
|
|
1690
|
-
}
|
|
1757
|
+
};
|
|
1691
1758
|
return {
|
|
1692
1759
|
ExportDefaultDeclaration(node) {
|
|
1693
|
-
const declaration = node
|
|
1760
|
+
const { declaration } = node;
|
|
1694
1761
|
if (declaration && declaration.type === "ObjectExpression") {
|
|
1695
1762
|
checkObjectExpression(declaration);
|
|
1696
1763
|
}
|
|
1697
1764
|
},
|
|
1698
1765
|
ReturnStatement(node) {
|
|
1699
|
-
const argument = node
|
|
1766
|
+
const { argument } = node;
|
|
1700
1767
|
if (argument && argument.type === "ObjectExpression") {
|
|
1701
1768
|
checkObjectExpression(argument);
|
|
1702
1769
|
}
|
|
1703
1770
|
}
|
|
1704
1771
|
};
|
|
1772
|
+
},
|
|
1773
|
+
defaultOptions: [],
|
|
1774
|
+
meta: {
|
|
1775
|
+
docs: {
|
|
1776
|
+
description: "Disallow grouping CSS style objects in a single export; export each style separately."
|
|
1777
|
+
},
|
|
1778
|
+
messages: {
|
|
1779
|
+
noMultiStyleObjects: "Do not group CSS style objects in a single export; export each style separately."
|
|
1780
|
+
},
|
|
1781
|
+
schema: [],
|
|
1782
|
+
type: "problem"
|
|
1705
1783
|
}
|
|
1706
1784
|
};
|
|
1707
1785
|
|
|
1708
1786
|
// src/rules/no-useless-function.ts
|
|
1709
1787
|
var noUselessFunction = {
|
|
1710
|
-
meta: {
|
|
1711
|
-
type: "suggestion",
|
|
1712
|
-
docs: {
|
|
1713
|
-
description: "Disallow functions that have no parameters and just return an object literal; consider exporting the object directly, unless the function is used as a callback (e.g., in react-spring)."
|
|
1714
|
-
},
|
|
1715
|
-
schema: [],
|
|
1716
|
-
messages: {
|
|
1717
|
-
uselessFunction: "This function has no parameters and simply returns an object. Consider exporting the object directly instead of wrapping it in a function."
|
|
1718
|
-
}
|
|
1719
|
-
},
|
|
1720
|
-
defaultOptions: [],
|
|
1721
1788
|
create(context) {
|
|
1722
|
-
|
|
1723
|
-
const parent = node
|
|
1789
|
+
const isCallbackFunction = (node) => {
|
|
1790
|
+
const { parent } = node;
|
|
1724
1791
|
if (!parent || parent.type !== "CallExpression") {
|
|
1725
1792
|
return false;
|
|
1726
1793
|
}
|
|
@@ -1730,7 +1797,7 @@ var noUselessFunction = {
|
|
|
1730
1797
|
}
|
|
1731
1798
|
}
|
|
1732
1799
|
return false;
|
|
1733
|
-
}
|
|
1800
|
+
};
|
|
1734
1801
|
return {
|
|
1735
1802
|
ArrowFunctionExpression(node) {
|
|
1736
1803
|
if (node.params.length === 0 && node.body && node.body.type === "ObjectExpression") {
|
|
@@ -1738,55 +1805,73 @@ var noUselessFunction = {
|
|
|
1738
1805
|
return;
|
|
1739
1806
|
}
|
|
1740
1807
|
context.report({
|
|
1741
|
-
|
|
1742
|
-
|
|
1808
|
+
messageId: "uselessFunction",
|
|
1809
|
+
node
|
|
1743
1810
|
});
|
|
1744
1811
|
}
|
|
1745
1812
|
}
|
|
1746
1813
|
};
|
|
1814
|
+
},
|
|
1815
|
+
defaultOptions: [],
|
|
1816
|
+
meta: {
|
|
1817
|
+
docs: {
|
|
1818
|
+
description: "Disallow functions that have no parameters and just return an object literal; consider exporting the object directly, unless the function is used as a callback (e.g., in react-spring)."
|
|
1819
|
+
},
|
|
1820
|
+
messages: {
|
|
1821
|
+
uselessFunction: "This function has no parameters and simply returns an object. Consider exporting the object directly instead of wrapping it in a function."
|
|
1822
|
+
},
|
|
1823
|
+
schema: [],
|
|
1824
|
+
type: "suggestion"
|
|
1747
1825
|
}
|
|
1748
1826
|
};
|
|
1749
1827
|
|
|
1750
1828
|
// src/rules/min-var-length.ts
|
|
1829
|
+
var extractIdentifiersFromPattern = (pattern, identifiers = []) => {
|
|
1830
|
+
if (!pattern)
|
|
1831
|
+
return identifiers;
|
|
1832
|
+
switch (pattern.type) {
|
|
1833
|
+
case "Identifier":
|
|
1834
|
+
identifiers.push(pattern.name);
|
|
1835
|
+
break;
|
|
1836
|
+
case "ObjectPattern":
|
|
1837
|
+
pattern.properties.forEach((prop) => {
|
|
1838
|
+
if (prop.type === "Property") {
|
|
1839
|
+
extractIdentifiersFromPattern(prop.value, identifiers);
|
|
1840
|
+
} else if (prop.type === "RestElement") {
|
|
1841
|
+
extractIdentifiersFromPattern(prop.argument, identifiers);
|
|
1842
|
+
}
|
|
1843
|
+
});
|
|
1844
|
+
break;
|
|
1845
|
+
case "ArrayPattern":
|
|
1846
|
+
pattern.elements.filter((element) => element !== null).forEach((element) => {
|
|
1847
|
+
extractIdentifiersFromPattern(element, identifiers);
|
|
1848
|
+
});
|
|
1849
|
+
break;
|
|
1850
|
+
case "AssignmentPattern":
|
|
1851
|
+
extractIdentifiersFromPattern(pattern.left, identifiers);
|
|
1852
|
+
break;
|
|
1853
|
+
default:
|
|
1854
|
+
break;
|
|
1855
|
+
}
|
|
1856
|
+
return identifiers;
|
|
1857
|
+
};
|
|
1858
|
+
var getDeclaratorNames = (declarations) => declarations.filter((decl) => decl.id.type === "Identifier").map((decl) => decl.id.name);
|
|
1859
|
+
var collectParamNames = (params) => {
|
|
1860
|
+
const names = [];
|
|
1861
|
+
params.forEach((param) => {
|
|
1862
|
+
extractIdentifiersFromPattern(param, names);
|
|
1863
|
+
});
|
|
1864
|
+
return names;
|
|
1865
|
+
};
|
|
1751
1866
|
var minVarLength = {
|
|
1752
|
-
meta: {
|
|
1753
|
-
type: "problem",
|
|
1754
|
-
docs: {
|
|
1755
|
-
description: "Disallow variable names shorter than the configured minimum length unless an outer variable with a longer name starting with the same characters exists. You can exempt specific variable names using the allowedVars option."
|
|
1756
|
-
},
|
|
1757
|
-
schema: [
|
|
1758
|
-
{
|
|
1759
|
-
type: "object",
|
|
1760
|
-
properties: {
|
|
1761
|
-
minLength: {
|
|
1762
|
-
type: "number",
|
|
1763
|
-
default: 1
|
|
1764
|
-
},
|
|
1765
|
-
allowedVars: {
|
|
1766
|
-
type: "array",
|
|
1767
|
-
items: {
|
|
1768
|
-
type: "string",
|
|
1769
|
-
minLength: 1
|
|
1770
|
-
},
|
|
1771
|
-
default: []
|
|
1772
|
-
}
|
|
1773
|
-
},
|
|
1774
|
-
additionalProperties: false
|
|
1775
|
-
}
|
|
1776
|
-
],
|
|
1777
|
-
messages: {
|
|
1778
|
-
variableNameTooShort: "Variable '{{name}}' is too short. Minimum allowed length is {{minLength}} characters unless an outer variable with a longer name starting with '{{name}}' exists."
|
|
1779
|
-
}
|
|
1780
|
-
},
|
|
1781
|
-
defaultOptions: [{}],
|
|
1782
1867
|
create(context) {
|
|
1783
|
-
const sourceCode = context
|
|
1784
|
-
const options = context.options
|
|
1868
|
+
const { sourceCode } = context;
|
|
1869
|
+
const [options] = context.options;
|
|
1785
1870
|
const configuredMinLength = options && typeof options.minLength === "number" ? options.minLength : 1;
|
|
1786
1871
|
const configuredAllowedVars = options && Array.isArray(options.allowedVars) ? options.allowedVars : [];
|
|
1787
1872
|
const minLength = configuredMinLength;
|
|
1788
1873
|
const allowedVars = configuredAllowedVars;
|
|
1789
|
-
|
|
1874
|
+
const getAncestors = (node) => {
|
|
1790
1875
|
const ancestors = [];
|
|
1791
1876
|
let current = node.parent;
|
|
1792
1877
|
while (current) {
|
|
@@ -1794,9 +1879,9 @@ var minVarLength = {
|
|
|
1794
1879
|
current = current.parent;
|
|
1795
1880
|
}
|
|
1796
1881
|
return ancestors;
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
const scopeManager = sourceCode
|
|
1882
|
+
};
|
|
1883
|
+
const getScope = (node) => {
|
|
1884
|
+
const { scopeManager } = sourceCode;
|
|
1800
1885
|
if (!scopeManager) {
|
|
1801
1886
|
return null;
|
|
1802
1887
|
}
|
|
@@ -1805,119 +1890,79 @@ var minVarLength = {
|
|
|
1805
1890
|
return acquired;
|
|
1806
1891
|
}
|
|
1807
1892
|
return scopeManager.globalScope ?? null;
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1893
|
+
};
|
|
1894
|
+
const getVariablesInNearestBlock = (node) => {
|
|
1810
1895
|
let current = node.parent;
|
|
1811
1896
|
while (current && current.type !== "BlockStatement") {
|
|
1812
1897
|
current = current.parent;
|
|
1813
1898
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
for (const stmt of current.body) {
|
|
1817
|
-
if (stmt.type === "VariableDeclaration") {
|
|
1818
|
-
for (const decl of stmt.declarations) {
|
|
1819
|
-
if (decl.id && decl.id.type === "Identifier") {
|
|
1820
|
-
names.push(decl.id.name);
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
return names;
|
|
1827
|
-
}
|
|
1828
|
-
function extractIdentifiersFromPattern(pattern, identifiers = []) {
|
|
1829
|
-
if (!pattern)
|
|
1830
|
-
return identifiers;
|
|
1831
|
-
switch (pattern.type) {
|
|
1832
|
-
case "Identifier":
|
|
1833
|
-
identifiers.push(pattern.name);
|
|
1834
|
-
break;
|
|
1835
|
-
case "ObjectPattern":
|
|
1836
|
-
for (const prop of pattern.properties) {
|
|
1837
|
-
if (prop.type === "Property") {
|
|
1838
|
-
extractIdentifiersFromPattern(prop.value, identifiers);
|
|
1839
|
-
} else if (prop.type === "RestElement") {
|
|
1840
|
-
extractIdentifiersFromPattern(prop.argument, identifiers);
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
break;
|
|
1844
|
-
case "ArrayPattern":
|
|
1845
|
-
for (const element of pattern.elements) {
|
|
1846
|
-
if (element) {
|
|
1847
|
-
extractIdentifiersFromPattern(element, identifiers);
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
break;
|
|
1851
|
-
case "AssignmentPattern":
|
|
1852
|
-
extractIdentifiersFromPattern(pattern.left, identifiers);
|
|
1853
|
-
break;
|
|
1854
|
-
default:
|
|
1855
|
-
break;
|
|
1899
|
+
if (!current || current.type !== "BlockStatement" || !Array.isArray(current.body)) {
|
|
1900
|
+
return [];
|
|
1856
1901
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1902
|
+
const varDeclarations = current.body.filter((stmt) => stmt.type === "VariableDeclaration");
|
|
1903
|
+
return varDeclarations.flatMap((stmt) => getDeclaratorNames(stmt.declarations));
|
|
1904
|
+
};
|
|
1905
|
+
const isLongerMatchInScope = (shortName, varName) => varName.length >= minLength && varName.length > shortName.length && varName.startsWith(shortName);
|
|
1906
|
+
const checkScopeVariables = (shortName, node) => {
|
|
1860
1907
|
const startingScope = getScope(node);
|
|
1861
1908
|
let outer = startingScope && startingScope.upper ? startingScope.upper : null;
|
|
1862
1909
|
while (outer) {
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
return true;
|
|
1866
|
-
}
|
|
1910
|
+
if (outer.variables.some((variable) => isLongerMatchInScope(shortName, variable.name))) {
|
|
1911
|
+
return true;
|
|
1867
1912
|
}
|
|
1868
1913
|
outer = outer.upper;
|
|
1869
1914
|
}
|
|
1915
|
+
return false;
|
|
1916
|
+
};
|
|
1917
|
+
const checkBlockVariables = (shortName, node) => {
|
|
1870
1918
|
const blockVars = getVariablesInNearestBlock(node);
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1919
|
+
return blockVars.some((varName) => isLongerMatchInScope(shortName, varName));
|
|
1920
|
+
};
|
|
1921
|
+
const checkAncestorDeclarators = (shortName, node) => {
|
|
1922
|
+
const ancestors = getAncestors(node);
|
|
1923
|
+
return ancestors.some((anc) => anc.type === "VariableDeclarator" && anc.id && anc.id.type === "Identifier" && isLongerMatchInScope(shortName, anc.id.name));
|
|
1924
|
+
};
|
|
1925
|
+
const checkFunctionAncestor = (shortName, anc) => {
|
|
1926
|
+
const names = collectParamNames(anc.params);
|
|
1927
|
+
return names.some((paramName) => isLongerMatchInScope(shortName, paramName));
|
|
1928
|
+
};
|
|
1929
|
+
const checkCatchAncestor = (shortName, anc) => {
|
|
1930
|
+
if (!anc.param) {
|
|
1931
|
+
return false;
|
|
1875
1932
|
}
|
|
1933
|
+
const names = extractIdentifiersFromPattern(anc.param, []);
|
|
1934
|
+
return names.some((paramName) => isLongerMatchInScope(shortName, paramName));
|
|
1935
|
+
};
|
|
1936
|
+
const checkAncestorParams = (shortName, node) => {
|
|
1876
1937
|
const ancestors = getAncestors(node);
|
|
1877
|
-
|
|
1878
|
-
if (anc.type === "
|
|
1879
|
-
|
|
1880
|
-
if (outerName.length >= minLength && outerName.length > shortName.length && outerName.startsWith(shortName)) {
|
|
1881
|
-
return true;
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
if ((anc.type === "FunctionDeclaration" || anc.type === "FunctionExpression" || anc.type === "ArrowFunctionExpression") && Array.isArray(anc.params)) {
|
|
1885
|
-
for (const param of anc.params) {
|
|
1886
|
-
const names = extractIdentifiersFromPattern(param, []);
|
|
1887
|
-
for (const n of names) {
|
|
1888
|
-
if (n.length >= minLength && n.length > shortName.length && n.startsWith(shortName)) {
|
|
1889
|
-
return true;
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1938
|
+
return ancestors.some((anc) => {
|
|
1939
|
+
if (anc.type === "FunctionDeclaration" || anc.type === "FunctionExpression" || anc.type === "ArrowFunctionExpression") {
|
|
1940
|
+
return checkFunctionAncestor(shortName, anc);
|
|
1893
1941
|
}
|
|
1894
|
-
if (anc.type === "CatchClause"
|
|
1895
|
-
|
|
1896
|
-
for (const n of names) {
|
|
1897
|
-
if (n.length >= minLength && n.length > shortName.length && n.startsWith(shortName)) {
|
|
1898
|
-
return true;
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1942
|
+
if (anc.type === "CatchClause") {
|
|
1943
|
+
return checkCatchAncestor(shortName, anc);
|
|
1901
1944
|
}
|
|
1945
|
+
return false;
|
|
1946
|
+
});
|
|
1947
|
+
};
|
|
1948
|
+
const hasOuterCorrespondingIdentifier = (shortName, node) => checkScopeVariables(shortName, node) || checkBlockVariables(shortName, node) || checkAncestorDeclarators(shortName, node) || checkAncestorParams(shortName, node);
|
|
1949
|
+
const checkIdentifier = (node) => {
|
|
1950
|
+
const { name } = node;
|
|
1951
|
+
if (name.length >= minLength) {
|
|
1952
|
+
return;
|
|
1902
1953
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
context.report({
|
|
1913
|
-
node,
|
|
1914
|
-
messageId: "variableNameTooShort",
|
|
1915
|
-
data: { name, minLength }
|
|
1916
|
-
});
|
|
1917
|
-
}
|
|
1954
|
+
if (allowedVars.includes(name)) {
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
if (!hasOuterCorrespondingIdentifier(name, node)) {
|
|
1958
|
+
context.report({
|
|
1959
|
+
data: { minLength, name },
|
|
1960
|
+
messageId: "variableNameTooShort",
|
|
1961
|
+
node
|
|
1962
|
+
});
|
|
1918
1963
|
}
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1964
|
+
};
|
|
1965
|
+
const checkPattern = (pattern) => {
|
|
1921
1966
|
if (!pattern)
|
|
1922
1967
|
return;
|
|
1923
1968
|
switch (pattern.type) {
|
|
@@ -1925,20 +1970,18 @@ var minVarLength = {
|
|
|
1925
1970
|
checkIdentifier(pattern);
|
|
1926
1971
|
break;
|
|
1927
1972
|
case "ObjectPattern":
|
|
1928
|
-
|
|
1973
|
+
pattern.properties.forEach((prop) => {
|
|
1929
1974
|
if (prop.type === "Property") {
|
|
1930
1975
|
checkPattern(prop.value);
|
|
1931
1976
|
} else if (prop.type === "RestElement") {
|
|
1932
1977
|
checkPattern(prop.argument);
|
|
1933
1978
|
}
|
|
1934
|
-
}
|
|
1979
|
+
});
|
|
1935
1980
|
break;
|
|
1936
1981
|
case "ArrayPattern":
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1982
|
+
pattern.elements.filter((element) => element !== null).forEach((element) => {
|
|
1983
|
+
checkPattern(element);
|
|
1984
|
+
});
|
|
1942
1985
|
break;
|
|
1943
1986
|
case "AssignmentPattern":
|
|
1944
1987
|
checkPattern(pattern.left);
|
|
@@ -1946,49 +1989,64 @@ var minVarLength = {
|
|
|
1946
1989
|
default:
|
|
1947
1990
|
break;
|
|
1948
1991
|
}
|
|
1949
|
-
}
|
|
1992
|
+
};
|
|
1950
1993
|
return {
|
|
1951
|
-
|
|
1952
|
-
if (node.
|
|
1953
|
-
checkPattern(node.
|
|
1994
|
+
CatchClause(node) {
|
|
1995
|
+
if (node.param) {
|
|
1996
|
+
checkPattern(node.param);
|
|
1954
1997
|
}
|
|
1955
1998
|
},
|
|
1956
1999
|
"FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(node) {
|
|
1957
|
-
|
|
2000
|
+
node.params.forEach((param) => {
|
|
1958
2001
|
checkPattern(param);
|
|
1959
|
-
}
|
|
2002
|
+
});
|
|
1960
2003
|
},
|
|
1961
|
-
|
|
1962
|
-
if (node.
|
|
1963
|
-
checkPattern(node.
|
|
2004
|
+
VariableDeclarator(node) {
|
|
2005
|
+
if (node.id) {
|
|
2006
|
+
checkPattern(node.id);
|
|
1964
2007
|
}
|
|
1965
2008
|
}
|
|
1966
2009
|
};
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
// src/rules/max-depth-extended.ts
|
|
1971
|
-
var maxDepthExtended = {
|
|
2010
|
+
},
|
|
2011
|
+
defaultOptions: [{}],
|
|
1972
2012
|
meta: {
|
|
1973
|
-
type: "suggestion",
|
|
1974
2013
|
docs: {
|
|
1975
|
-
description: "
|
|
2014
|
+
description: "Disallow variable names shorter than the configured minimum length unless an outer variable with a longer name starting with the same characters exists. You can exempt specific variable names using the allowedVars option."
|
|
2015
|
+
},
|
|
2016
|
+
messages: {
|
|
2017
|
+
variableNameTooShort: "Variable '{{name}}' is too short. Minimum allowed length is {{minLength}} characters unless an outer variable with a longer name starting with '{{name}}' exists."
|
|
1976
2018
|
},
|
|
1977
2019
|
schema: [
|
|
1978
2020
|
{
|
|
1979
|
-
|
|
2021
|
+
additionalProperties: false,
|
|
2022
|
+
properties: {
|
|
2023
|
+
allowedVars: {
|
|
2024
|
+
default: [],
|
|
2025
|
+
items: {
|
|
2026
|
+
minLength: 1,
|
|
2027
|
+
type: "string"
|
|
2028
|
+
},
|
|
2029
|
+
type: "array"
|
|
2030
|
+
},
|
|
2031
|
+
minLength: {
|
|
2032
|
+
default: 1,
|
|
2033
|
+
type: "number"
|
|
2034
|
+
}
|
|
2035
|
+
},
|
|
2036
|
+
type: "object"
|
|
1980
2037
|
}
|
|
1981
2038
|
],
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
2039
|
+
type: "problem"
|
|
2040
|
+
}
|
|
2041
|
+
};
|
|
2042
|
+
|
|
2043
|
+
// src/rules/max-depth-extended.ts
|
|
2044
|
+
var maxDepthExtended = {
|
|
1987
2045
|
create(context) {
|
|
1988
|
-
const option = context.options
|
|
2046
|
+
const [option] = context.options;
|
|
1989
2047
|
const maxDepth = typeof option === "number" ? option : 1;
|
|
1990
2048
|
const functionStack = [];
|
|
1991
|
-
|
|
2049
|
+
const getAncestors = (node) => {
|
|
1992
2050
|
const ancestors = [];
|
|
1993
2051
|
let current = node.parent;
|
|
1994
2052
|
while (current) {
|
|
@@ -1996,18 +2054,23 @@ var maxDepthExtended = {
|
|
|
1996
2054
|
current = current.parent;
|
|
1997
2055
|
}
|
|
1998
2056
|
return ancestors;
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2057
|
+
};
|
|
2058
|
+
const isEarlyExitBlock = (node) => {
|
|
2001
2059
|
if (node.body.length !== 1) {
|
|
2002
2060
|
return false;
|
|
2003
2061
|
}
|
|
2004
|
-
const first = node.body
|
|
2062
|
+
const [first] = node.body;
|
|
2005
2063
|
if (!first) {
|
|
2006
2064
|
return false;
|
|
2007
2065
|
}
|
|
2008
2066
|
return first.type === "ReturnStatement" || first.type === "ThrowStatement";
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2067
|
+
};
|
|
2068
|
+
const isFunctionBody = (node) => {
|
|
2069
|
+
const ancestors = getAncestors(node);
|
|
2070
|
+
const [parent] = ancestors;
|
|
2071
|
+
return parent && (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression") && node === parent.body;
|
|
2072
|
+
};
|
|
2073
|
+
const incrementCurrentDepth = () => {
|
|
2011
2074
|
if (functionStack.length === 0) {
|
|
2012
2075
|
return null;
|
|
2013
2076
|
}
|
|
@@ -2019,8 +2082,8 @@ var maxDepthExtended = {
|
|
|
2019
2082
|
const nextDepth = currentDepth + 1;
|
|
2020
2083
|
functionStack[index] = nextDepth;
|
|
2021
2084
|
return nextDepth;
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2085
|
+
};
|
|
2086
|
+
const decrementCurrentDepth = () => {
|
|
2024
2087
|
if (functionStack.length === 0) {
|
|
2025
2088
|
return;
|
|
2026
2089
|
}
|
|
@@ -2030,30 +2093,25 @@ var maxDepthExtended = {
|
|
|
2030
2093
|
return;
|
|
2031
2094
|
}
|
|
2032
2095
|
functionStack[index] = currentDepth - 1;
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2096
|
+
};
|
|
2097
|
+
const checkDepth = (node, depth) => {
|
|
2035
2098
|
if (depth > maxDepth) {
|
|
2036
2099
|
context.report({
|
|
2037
|
-
|
|
2100
|
+
data: { depth, maxDepth },
|
|
2038
2101
|
messageId: "tooDeep",
|
|
2039
|
-
|
|
2102
|
+
node
|
|
2040
2103
|
});
|
|
2041
2104
|
}
|
|
2042
|
-
}
|
|
2105
|
+
};
|
|
2043
2106
|
return {
|
|
2044
|
-
FunctionDeclaration() {
|
|
2045
|
-
functionStack.push(0);
|
|
2046
|
-
},
|
|
2047
|
-
FunctionExpression() {
|
|
2048
|
-
functionStack.push(0);
|
|
2049
|
-
},
|
|
2050
2107
|
ArrowFunctionExpression() {
|
|
2051
2108
|
functionStack.push(0);
|
|
2052
2109
|
},
|
|
2110
|
+
"ArrowFunctionExpression:exit"() {
|
|
2111
|
+
functionStack.pop();
|
|
2112
|
+
},
|
|
2053
2113
|
BlockStatement(node) {
|
|
2054
|
-
|
|
2055
|
-
const parent = ancestors.length > 0 ? ancestors[0] : undefined;
|
|
2056
|
-
if (parent && (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression") && node === parent.body) {
|
|
2114
|
+
if (isFunctionBody(node)) {
|
|
2057
2115
|
return;
|
|
2058
2116
|
}
|
|
2059
2117
|
if (isEarlyExitBlock(node)) {
|
|
@@ -2065,9 +2123,7 @@ var maxDepthExtended = {
|
|
|
2065
2123
|
}
|
|
2066
2124
|
},
|
|
2067
2125
|
"BlockStatement:exit"(node) {
|
|
2068
|
-
|
|
2069
|
-
const parent = ancestors.length > 0 ? ancestors[0] : undefined;
|
|
2070
|
-
if (parent && (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression") && node === parent.body) {
|
|
2126
|
+
if (isFunctionBody(node)) {
|
|
2071
2127
|
return;
|
|
2072
2128
|
}
|
|
2073
2129
|
if (isEarlyExitBlock(node)) {
|
|
@@ -2075,39 +2131,105 @@ var maxDepthExtended = {
|
|
|
2075
2131
|
}
|
|
2076
2132
|
decrementCurrentDepth();
|
|
2077
2133
|
},
|
|
2134
|
+
FunctionDeclaration() {
|
|
2135
|
+
functionStack.push(0);
|
|
2136
|
+
},
|
|
2078
2137
|
"FunctionDeclaration:exit"() {
|
|
2079
2138
|
functionStack.pop();
|
|
2080
2139
|
},
|
|
2081
|
-
|
|
2082
|
-
functionStack.
|
|
2140
|
+
FunctionExpression() {
|
|
2141
|
+
functionStack.push(0);
|
|
2083
2142
|
},
|
|
2084
|
-
"
|
|
2143
|
+
"FunctionExpression:exit"() {
|
|
2085
2144
|
functionStack.pop();
|
|
2086
2145
|
}
|
|
2087
2146
|
};
|
|
2147
|
+
},
|
|
2148
|
+
defaultOptions: [1],
|
|
2149
|
+
meta: {
|
|
2150
|
+
docs: {
|
|
2151
|
+
description: "disallow too many nested blocks except when the block only contains an early exit (return or throw)"
|
|
2152
|
+
},
|
|
2153
|
+
messages: {
|
|
2154
|
+
tooDeep: "Blocks are nested too deeply ({{depth}}). Maximum allowed is {{maxDepth}} or an early exit."
|
|
2155
|
+
},
|
|
2156
|
+
schema: [
|
|
2157
|
+
{
|
|
2158
|
+
type: "number"
|
|
2159
|
+
}
|
|
2160
|
+
],
|
|
2161
|
+
type: "suggestion"
|
|
2088
2162
|
}
|
|
2089
2163
|
};
|
|
2090
2164
|
|
|
2091
2165
|
// src/rules/spring-naming-convention.ts
|
|
2166
|
+
var SPRINGS_SUFFIX = "Springs";
|
|
2167
|
+
var checkUseSpring = (context, firstElem, secondElem) => {
|
|
2168
|
+
const firstName = firstElem.name;
|
|
2169
|
+
const secondName = secondElem.name;
|
|
2170
|
+
if (!firstName.endsWith(SPRINGS_SUFFIX)) {
|
|
2171
|
+
context.report({
|
|
2172
|
+
messageId: "firstMustEndWithSprings",
|
|
2173
|
+
node: firstElem
|
|
2174
|
+
});
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
const base = firstName.slice(0, -SPRINGS_SUFFIX.length);
|
|
2178
|
+
if (!base) {
|
|
2179
|
+
context.report({
|
|
2180
|
+
messageId: "firstMustHaveBase",
|
|
2181
|
+
node: firstElem
|
|
2182
|
+
});
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
const expectedSecond = `${base}Api`;
|
|
2186
|
+
if (secondName !== expectedSecond) {
|
|
2187
|
+
context.report({
|
|
2188
|
+
data: { expected: expectedSecond },
|
|
2189
|
+
messageId: "secondMustMatch",
|
|
2190
|
+
node: secondElem
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
2194
|
+
var checkUseSprings = (context, firstElem, secondElem) => {
|
|
2195
|
+
const firstName = firstElem.name;
|
|
2196
|
+
const secondName = secondElem.name;
|
|
2197
|
+
if (!firstName.endsWith(SPRINGS_SUFFIX)) {
|
|
2198
|
+
context.report({
|
|
2199
|
+
messageId: "firstMustEndWithSprings",
|
|
2200
|
+
node: firstElem
|
|
2201
|
+
});
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
const basePlural = firstName.slice(0, -SPRINGS_SUFFIX.length);
|
|
2205
|
+
if (!basePlural) {
|
|
2206
|
+
context.report({
|
|
2207
|
+
messageId: "firstMustHaveBase",
|
|
2208
|
+
node: firstElem
|
|
2209
|
+
});
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
if (!basePlural.endsWith("s")) {
|
|
2213
|
+
context.report({
|
|
2214
|
+
messageId: "pluralRequired",
|
|
2215
|
+
node: firstElem
|
|
2216
|
+
});
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
const expectedSecond = `${basePlural}Api`;
|
|
2220
|
+
if (secondName !== expectedSecond) {
|
|
2221
|
+
context.report({
|
|
2222
|
+
data: { expected: expectedSecond },
|
|
2223
|
+
messageId: "secondMustMatch",
|
|
2224
|
+
node: secondElem
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
};
|
|
2092
2228
|
var springNamingConvention = {
|
|
2093
|
-
meta: {
|
|
2094
|
-
type: "problem",
|
|
2095
|
-
docs: {
|
|
2096
|
-
description: "Enforce correct naming for useSpring and useSprings hook destructuring"
|
|
2097
|
-
},
|
|
2098
|
-
schema: [],
|
|
2099
|
-
messages: {
|
|
2100
|
-
firstMustEndWithSprings: "The first variable must end with 'Springs'.",
|
|
2101
|
-
firstMustHaveBase: "The first variable must have a non-empty name before 'Springs'.",
|
|
2102
|
-
secondMustMatch: "The second variable must be named '{{expected}}'.",
|
|
2103
|
-
pluralRequired: "The first variable for useSprings should be plural (ending with 's') before 'Springs'."
|
|
2104
|
-
}
|
|
2105
|
-
},
|
|
2106
|
-
defaultOptions: [],
|
|
2107
2229
|
create(context) {
|
|
2108
2230
|
return {
|
|
2109
2231
|
VariableDeclarator(node) {
|
|
2110
|
-
const init = node
|
|
2232
|
+
const { init } = node;
|
|
2111
2233
|
if (!init || init.type !== "CallExpression" || init.callee.type !== "Identifier") {
|
|
2112
2234
|
return;
|
|
2113
2235
|
}
|
|
@@ -2118,87 +2240,74 @@ var springNamingConvention = {
|
|
|
2118
2240
|
if (node.id.type !== "ArrayPattern") {
|
|
2119
2241
|
return;
|
|
2120
2242
|
}
|
|
2121
|
-
const elements = node.id
|
|
2243
|
+
const { elements } = node.id;
|
|
2122
2244
|
if (elements.length < 2) {
|
|
2123
2245
|
return;
|
|
2124
2246
|
}
|
|
2125
|
-
const firstElem = elements
|
|
2126
|
-
const secondElem = elements[1];
|
|
2247
|
+
const [firstElem, secondElem] = elements;
|
|
2127
2248
|
if (!firstElem || firstElem.type !== "Identifier" || !secondElem || secondElem.type !== "Identifier") {
|
|
2128
2249
|
return;
|
|
2129
2250
|
}
|
|
2130
|
-
const firstName = firstElem.name;
|
|
2131
|
-
const secondName = secondElem.name;
|
|
2132
2251
|
if (hookName === "useSpring") {
|
|
2133
|
-
|
|
2134
|
-
context.report({
|
|
2135
|
-
node: firstElem,
|
|
2136
|
-
messageId: "firstMustEndWithSprings"
|
|
2137
|
-
});
|
|
2138
|
-
return;
|
|
2139
|
-
}
|
|
2140
|
-
const base = firstName.slice(0, -"Springs".length);
|
|
2141
|
-
if (!base) {
|
|
2142
|
-
context.report({
|
|
2143
|
-
node: firstElem,
|
|
2144
|
-
messageId: "firstMustHaveBase"
|
|
2145
|
-
});
|
|
2146
|
-
return;
|
|
2147
|
-
}
|
|
2148
|
-
const expectedSecond = `${base}Api`;
|
|
2149
|
-
if (secondName !== expectedSecond) {
|
|
2150
|
-
context.report({
|
|
2151
|
-
node: secondElem,
|
|
2152
|
-
messageId: "secondMustMatch",
|
|
2153
|
-
data: { expected: expectedSecond }
|
|
2154
|
-
});
|
|
2155
|
-
}
|
|
2252
|
+
checkUseSpring(context, firstElem, secondElem);
|
|
2156
2253
|
return;
|
|
2157
2254
|
}
|
|
2158
2255
|
if (hookName === "useSprings") {
|
|
2159
|
-
|
|
2160
|
-
context.report({
|
|
2161
|
-
node: firstElem,
|
|
2162
|
-
messageId: "firstMustEndWithSprings"
|
|
2163
|
-
});
|
|
2164
|
-
return;
|
|
2165
|
-
}
|
|
2166
|
-
const basePlural = firstName.slice(0, -"Springs".length);
|
|
2167
|
-
if (!basePlural) {
|
|
2168
|
-
context.report({
|
|
2169
|
-
node: firstElem,
|
|
2170
|
-
messageId: "firstMustHaveBase"
|
|
2171
|
-
});
|
|
2172
|
-
return;
|
|
2173
|
-
}
|
|
2174
|
-
if (!basePlural.endsWith("s")) {
|
|
2175
|
-
context.report({
|
|
2176
|
-
node: firstElem,
|
|
2177
|
-
messageId: "pluralRequired"
|
|
2178
|
-
});
|
|
2179
|
-
return;
|
|
2180
|
-
}
|
|
2181
|
-
const expectedSecond = `${basePlural}Api`;
|
|
2182
|
-
if (secondName !== expectedSecond) {
|
|
2183
|
-
context.report({
|
|
2184
|
-
node: secondElem,
|
|
2185
|
-
messageId: "secondMustMatch",
|
|
2186
|
-
data: { expected: expectedSecond }
|
|
2187
|
-
});
|
|
2188
|
-
}
|
|
2256
|
+
checkUseSprings(context, firstElem, secondElem);
|
|
2189
2257
|
}
|
|
2190
2258
|
}
|
|
2191
2259
|
};
|
|
2260
|
+
},
|
|
2261
|
+
defaultOptions: [],
|
|
2262
|
+
meta: {
|
|
2263
|
+
docs: {
|
|
2264
|
+
description: "Enforce correct naming for useSpring and useSprings hook destructuring"
|
|
2265
|
+
},
|
|
2266
|
+
messages: {
|
|
2267
|
+
firstMustEndWithSprings: "The first variable must end with 'Springs'.",
|
|
2268
|
+
firstMustHaveBase: "The first variable must have a non-empty name before 'Springs'.",
|
|
2269
|
+
pluralRequired: "The first variable for useSprings should be plural (ending with 's') before 'Springs'.",
|
|
2270
|
+
secondMustMatch: "The second variable must be named '{{expected}}'."
|
|
2271
|
+
},
|
|
2272
|
+
schema: [],
|
|
2273
|
+
type: "problem"
|
|
2192
2274
|
}
|
|
2193
2275
|
};
|
|
2194
2276
|
|
|
2195
2277
|
// src/rules/inline-style-limit.ts
|
|
2278
|
+
var DEFAULT_MAX_KEYS = 3;
|
|
2196
2279
|
var inlineStyleLimit = {
|
|
2280
|
+
create(context) {
|
|
2281
|
+
const [option] = context.options;
|
|
2282
|
+
const maxKeys = typeof option === "number" ? option : option && option.maxKeys || DEFAULT_MAX_KEYS;
|
|
2283
|
+
return {
|
|
2284
|
+
JSXAttribute(node) {
|
|
2285
|
+
if (node.name.type !== "JSXIdentifier" || node.name.name !== "style") {
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
if (!node.value || node.value.type !== "JSXExpressionContainer" || !node.value.expression || node.value.expression.type !== "ObjectExpression") {
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
const styleObject = node.value.expression;
|
|
2292
|
+
const keyCount = styleObject.properties.filter((prop) => prop.type === "Property").length;
|
|
2293
|
+
if (keyCount > maxKeys) {
|
|
2294
|
+
context.report({
|
|
2295
|
+
data: { max: maxKeys },
|
|
2296
|
+
messageId: "extractStyle",
|
|
2297
|
+
node
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
};
|
|
2302
|
+
},
|
|
2303
|
+
defaultOptions: [DEFAULT_MAX_KEYS],
|
|
2197
2304
|
meta: {
|
|
2198
|
-
type: "suggestion",
|
|
2199
2305
|
docs: {
|
|
2200
2306
|
description: "Disallow inline style objects with too many keys and encourage extracting them"
|
|
2201
2307
|
},
|
|
2308
|
+
messages: {
|
|
2309
|
+
extractStyle: "Inline style objects should be extracted into a separate object or file when containing more than {{max}} keys."
|
|
2310
|
+
},
|
|
2202
2311
|
schema: [
|
|
2203
2312
|
{
|
|
2204
2313
|
anyOf: [
|
|
@@ -2206,119 +2315,78 @@ var inlineStyleLimit = {
|
|
|
2206
2315
|
type: "number"
|
|
2207
2316
|
},
|
|
2208
2317
|
{
|
|
2209
|
-
|
|
2318
|
+
additionalProperties: false,
|
|
2210
2319
|
properties: {
|
|
2211
2320
|
maxKeys: {
|
|
2212
|
-
|
|
2213
|
-
|
|
2321
|
+
description: "Maximum number of keys allowed in an inline style object before it must be extracted.",
|
|
2322
|
+
type: "number"
|
|
2214
2323
|
}
|
|
2215
2324
|
},
|
|
2216
|
-
|
|
2325
|
+
type: "object"
|
|
2217
2326
|
}
|
|
2218
2327
|
]
|
|
2219
2328
|
}
|
|
2220
2329
|
],
|
|
2221
|
-
|
|
2222
|
-
extractStyle: "Inline style objects should be extracted into a separate object or file when containing more than {{max}} keys."
|
|
2223
|
-
}
|
|
2224
|
-
},
|
|
2225
|
-
defaultOptions: [3],
|
|
2226
|
-
create(context) {
|
|
2227
|
-
const option = context.options[0];
|
|
2228
|
-
const maxKeys = typeof option === "number" ? option : option && option.maxKeys || 3;
|
|
2229
|
-
return {
|
|
2230
|
-
JSXAttribute(node) {
|
|
2231
|
-
if (node.name.type !== "JSXIdentifier" || node.name.name !== "style") {
|
|
2232
|
-
return;
|
|
2233
|
-
}
|
|
2234
|
-
if (node.value && node.value.type === "JSXExpressionContainer" && node.value.expression && node.value.expression.type === "ObjectExpression") {
|
|
2235
|
-
const styleObject = node.value.expression;
|
|
2236
|
-
const keyCount = styleObject.properties.filter((prop) => prop.type === "Property").length;
|
|
2237
|
-
if (keyCount > maxKeys) {
|
|
2238
|
-
context.report({
|
|
2239
|
-
node,
|
|
2240
|
-
messageId: "extractStyle",
|
|
2241
|
-
data: { max: maxKeys }
|
|
2242
|
-
});
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
}
|
|
2246
|
-
};
|
|
2330
|
+
type: "suggestion"
|
|
2247
2331
|
}
|
|
2248
2332
|
};
|
|
2249
2333
|
|
|
2250
2334
|
// src/rules/no-inline-prop-types.ts
|
|
2251
2335
|
var noInlinePropTypes = {
|
|
2252
|
-
meta: {
|
|
2253
|
-
type: "suggestion",
|
|
2254
|
-
docs: {
|
|
2255
|
-
description: "Enforce that component prop types are not defined inline (using an object literal) but rather use a named type or interface."
|
|
2256
|
-
},
|
|
2257
|
-
schema: [],
|
|
2258
|
-
messages: {
|
|
2259
|
-
noInlinePropTypes: "Inline prop type definitions are not allowed. Use a named type alias or interface instead of an inline object type."
|
|
2260
|
-
}
|
|
2261
|
-
},
|
|
2262
|
-
defaultOptions: [],
|
|
2263
2336
|
create(context) {
|
|
2264
|
-
|
|
2265
|
-
if (param.type
|
|
2266
|
-
|
|
2267
|
-
if (annotation.type === "TSTypeLiteral") {
|
|
2268
|
-
context.report({
|
|
2269
|
-
node: param,
|
|
2270
|
-
messageId: "noInlinePropTypes"
|
|
2271
|
-
});
|
|
2272
|
-
}
|
|
2337
|
+
const checkParameter = (param) => {
|
|
2338
|
+
if (param.type !== "ObjectPattern" || !param.typeAnnotation || param.typeAnnotation.type !== "TSTypeAnnotation") {
|
|
2339
|
+
return;
|
|
2273
2340
|
}
|
|
2274
|
-
|
|
2341
|
+
const annotation = param.typeAnnotation.typeAnnotation;
|
|
2342
|
+
if (annotation.type === "TSTypeLiteral") {
|
|
2343
|
+
context.report({
|
|
2344
|
+
messageId: "noInlinePropTypes",
|
|
2345
|
+
node: param
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2275
2349
|
return {
|
|
2276
2350
|
"FunctionDeclaration, ArrowFunctionExpression, FunctionExpression"(node) {
|
|
2277
2351
|
if (node.params.length === 0) {
|
|
2278
2352
|
return;
|
|
2279
2353
|
}
|
|
2280
|
-
const firstParam = node.params
|
|
2354
|
+
const [firstParam] = node.params;
|
|
2281
2355
|
if (!firstParam) {
|
|
2282
2356
|
return;
|
|
2283
2357
|
}
|
|
2284
2358
|
checkParameter(firstParam);
|
|
2285
2359
|
}
|
|
2286
2360
|
};
|
|
2361
|
+
},
|
|
2362
|
+
defaultOptions: [],
|
|
2363
|
+
meta: {
|
|
2364
|
+
docs: {
|
|
2365
|
+
description: "Enforce that component prop types are not defined inline (using an object literal) but rather use a named type or interface."
|
|
2366
|
+
},
|
|
2367
|
+
messages: {
|
|
2368
|
+
noInlinePropTypes: "Inline prop type definitions are not allowed. Use a named type alias or interface instead of an inline object type."
|
|
2369
|
+
},
|
|
2370
|
+
schema: [],
|
|
2371
|
+
type: "suggestion"
|
|
2287
2372
|
}
|
|
2288
2373
|
};
|
|
2289
2374
|
|
|
2290
2375
|
// src/rules/no-unnecessary-div.ts
|
|
2291
2376
|
import { AST_NODE_TYPES as AST_NODE_TYPES3 } from "@typescript-eslint/utils";
|
|
2292
2377
|
var noUnnecessaryDiv = {
|
|
2293
|
-
meta: {
|
|
2294
|
-
type: "suggestion",
|
|
2295
|
-
docs: {
|
|
2296
|
-
description: "Flag unnecessary <div> wrappers that enclose a single JSX element. Remove the wrapper if it doesn't add semantic or functional value, or replace it with a semantic element if wrapping is needed."
|
|
2297
|
-
},
|
|
2298
|
-
schema: [],
|
|
2299
|
-
messages: {
|
|
2300
|
-
unnecessaryDivWrapper: "Unnecessary <div> wrapper detected. Remove it if not needed, or replace with a semantic element that reflects its purpose."
|
|
2301
|
-
}
|
|
2302
|
-
},
|
|
2303
|
-
defaultOptions: [],
|
|
2304
2378
|
create(context) {
|
|
2305
|
-
|
|
2379
|
+
const isDivElement = (node) => {
|
|
2306
2380
|
const nameNode = node.openingElement.name;
|
|
2307
2381
|
return nameNode.type === AST_NODE_TYPES3.JSXIdentifier && nameNode.name === "div";
|
|
2308
|
-
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
if (child.type === AST_NODE_TYPES3.JSXText) {
|
|
2313
|
-
if (child.value.trim() !== "") {
|
|
2314
|
-
result.push(child);
|
|
2315
|
-
}
|
|
2316
|
-
} else {
|
|
2317
|
-
result.push(child);
|
|
2318
|
-
}
|
|
2382
|
+
};
|
|
2383
|
+
const isMeaningfulChild = (child) => {
|
|
2384
|
+
if (child.type === AST_NODE_TYPES3.JSXText) {
|
|
2385
|
+
return child.value.trim() !== "";
|
|
2319
2386
|
}
|
|
2320
|
-
return
|
|
2321
|
-
}
|
|
2387
|
+
return true;
|
|
2388
|
+
};
|
|
2389
|
+
const getMeaningfulChildren = (node) => node.children.filter(isMeaningfulChild);
|
|
2322
2390
|
return {
|
|
2323
2391
|
JSXElement(node) {
|
|
2324
2392
|
if (!isDivElement(node)) {
|
|
@@ -2328,44 +2396,55 @@ var noUnnecessaryDiv = {
|
|
|
2328
2396
|
if (meaningfulChildren.length !== 1) {
|
|
2329
2397
|
return;
|
|
2330
2398
|
}
|
|
2331
|
-
const onlyChild = meaningfulChildren
|
|
2399
|
+
const [onlyChild] = meaningfulChildren;
|
|
2332
2400
|
if (!onlyChild) {
|
|
2333
2401
|
return;
|
|
2334
2402
|
}
|
|
2335
2403
|
if (onlyChild.type === AST_NODE_TYPES3.JSXElement) {
|
|
2336
2404
|
context.report({
|
|
2337
|
-
|
|
2338
|
-
|
|
2405
|
+
messageId: "unnecessaryDivWrapper",
|
|
2406
|
+
node
|
|
2339
2407
|
});
|
|
2340
2408
|
}
|
|
2341
2409
|
}
|
|
2342
2410
|
};
|
|
2411
|
+
},
|
|
2412
|
+
defaultOptions: [],
|
|
2413
|
+
meta: {
|
|
2414
|
+
docs: {
|
|
2415
|
+
description: "Flag unnecessary <div> wrappers that enclose a single JSX element. Remove the wrapper if it doesn't add semantic or functional value, or replace it with a semantic element if wrapping is needed."
|
|
2416
|
+
},
|
|
2417
|
+
messages: {
|
|
2418
|
+
unnecessaryDivWrapper: "Unnecessary <div> wrapper detected. Remove it if not needed, or replace with a semantic element that reflects its purpose."
|
|
2419
|
+
},
|
|
2420
|
+
schema: [],
|
|
2421
|
+
type: "suggestion"
|
|
2343
2422
|
}
|
|
2344
2423
|
};
|
|
2345
2424
|
|
|
2346
2425
|
// src/index.ts
|
|
2347
2426
|
var src_default = {
|
|
2348
2427
|
rules: {
|
|
2349
|
-
"no-nested-jsx-return": noNestedJSXReturn,
|
|
2350
2428
|
"explicit-object-types": explicitObjectTypes,
|
|
2351
|
-
"
|
|
2352
|
-
"no-transition-cssproperties": noTransitionCSSProperties,
|
|
2353
|
-
"no-explicit-return-type": noExplicitReturnTypes,
|
|
2354
|
-
"max-jsxnesting": maxJSXNesting,
|
|
2355
|
-
"seperate-style-files": seperateStyleFiles,
|
|
2356
|
-
"no-unnecessary-key": noUnnecessaryKey,
|
|
2357
|
-
"sort-exports": sortExports,
|
|
2429
|
+
"inline-style-limit": inlineStyleLimit,
|
|
2358
2430
|
"localize-react-props": localizeReactProps,
|
|
2359
|
-
"
|
|
2431
|
+
"max-depth-extended": maxDepthExtended,
|
|
2432
|
+
"max-jsxnesting": maxJSXNesting,
|
|
2433
|
+
"min-var-length": minVarLength,
|
|
2360
2434
|
"no-button-navigation": noButtonNavigation,
|
|
2435
|
+
"no-explicit-return-type": noExplicitReturnTypes,
|
|
2436
|
+
"no-inline-prop-types": noInlinePropTypes,
|
|
2361
2437
|
"no-multi-style-objects": noMultiStyleObjects,
|
|
2438
|
+
"no-nested-jsx-return": noNestedJSXReturn,
|
|
2439
|
+
"no-or-none-component": noOrNoneComponent,
|
|
2440
|
+
"no-transition-cssproperties": noTransitionCSSProperties,
|
|
2441
|
+
"no-unnecessary-div": noUnnecessaryDiv,
|
|
2442
|
+
"no-unnecessary-key": noUnnecessaryKey,
|
|
2362
2443
|
"no-useless-function": noUselessFunction,
|
|
2363
|
-
"
|
|
2364
|
-
"
|
|
2365
|
-
"
|
|
2366
|
-
"
|
|
2367
|
-
"no-inline-prop-types": noInlinePropTypes,
|
|
2368
|
-
"no-unnecessary-div": noUnnecessaryDiv
|
|
2444
|
+
"seperate-style-files": seperateStyleFiles,
|
|
2445
|
+
"sort-exports": sortExports,
|
|
2446
|
+
"sort-keys-fixable": sortKeysFixable,
|
|
2447
|
+
"spring-naming-convention": springNamingConvention
|
|
2369
2448
|
}
|
|
2370
2449
|
};
|
|
2371
2450
|
export {
|