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