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