eslint-plugin-absolute 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.absolutejs/eslint.cache.json +49 -0
- package/.absolutejs/prettier.cache.json +49 -0
- package/.absolutejs/tsconfig.tsbuildinfo +1 -0
- package/.claude/settings.local.json +8 -3
- package/dist/index.js +1321 -1419
- package/eslint.config.mjs +107 -0
- package/package.json +10 -8
- package/src/index.ts +15 -15
- package/src/rules/explicit-object-types.ts +42 -40
- package/src/rules/inline-style-limit.ts +56 -54
- package/src/rules/localize-react-props.ts +261 -266
- package/src/rules/max-depth-extended.ts +55 -66
- package/src/rules/max-jsx-nesting.ts +28 -36
- package/src/rules/min-var-length.ts +238 -208
- package/src/rules/no-button-navigation.ts +114 -156
- package/src/rules/no-explicit-return-types.ts +32 -36
- package/src/rules/no-inline-prop-types.ts +30 -30
- package/src/rules/no-multi-style-objects.ts +35 -42
- package/src/rules/no-nested-jsx-return.ts +100 -105
- package/src/rules/no-or-none-component.ts +17 -19
- package/src/rules/no-transition-cssproperties.ts +76 -70
- package/src/rules/no-unnecessary-div.ts +26 -34
- package/src/rules/no-unnecessary-key.ts +41 -75
- package/src/rules/no-useless-function.ts +18 -20
- package/src/rules/seperate-style-files.ts +19 -21
- package/src/rules/sort-exports.ts +252 -248
- package/src/rules/sort-keys-fixable.ts +354 -328
- package/src/rules/spring-naming-convention.ts +104 -89
- package/tsconfig.json +2 -0
|
@@ -11,36 +11,21 @@ type HandlerState = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export const noButtonNavigation: TSESLint.RuleModule<MessageIds, Options> = {
|
|
14
|
-
meta: {
|
|
15
|
-
type: "suggestion",
|
|
16
|
-
docs: {
|
|
17
|
-
description:
|
|
18
|
-
"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 + …)."
|
|
19
|
-
},
|
|
20
|
-
schema: [],
|
|
21
|
-
messages: {
|
|
22
|
-
noButtonNavigation:
|
|
23
|
-
"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."
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
defaultOptions: [],
|
|
28
|
-
|
|
29
14
|
create(context) {
|
|
30
15
|
const handlerStack: HandlerState[] = [];
|
|
31
16
|
|
|
32
|
-
|
|
17
|
+
const getCurrentHandler = () => {
|
|
33
18
|
const state = handlerStack[handlerStack.length - 1];
|
|
34
19
|
if (!state) {
|
|
35
20
|
return null;
|
|
36
21
|
}
|
|
37
22
|
return state;
|
|
38
|
-
}
|
|
23
|
+
};
|
|
39
24
|
|
|
40
|
-
|
|
25
|
+
const isOnClickButtonHandler = (
|
|
41
26
|
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression
|
|
42
|
-
)
|
|
43
|
-
const parent = node
|
|
27
|
+
) => {
|
|
28
|
+
const { parent } = node;
|
|
44
29
|
if (!parent || parent.type !== "JSXExpressionContainer") {
|
|
45
30
|
return null;
|
|
46
31
|
}
|
|
@@ -66,8 +51,7 @@ export const noButtonNavigation: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
66
51
|
) {
|
|
67
52
|
return null;
|
|
68
53
|
}
|
|
69
|
-
const
|
|
70
|
-
const tagNameNode = openingElement.name;
|
|
54
|
+
const tagNameNode = openingElementCandidate.name;
|
|
71
55
|
if (
|
|
72
56
|
tagNameNode.type !== "JSXIdentifier" ||
|
|
73
57
|
tagNameNode.name !== "button"
|
|
@@ -75,47 +59,60 @@ export const noButtonNavigation: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
75
59
|
return null;
|
|
76
60
|
}
|
|
77
61
|
return attr;
|
|
78
|
-
}
|
|
62
|
+
};
|
|
79
63
|
|
|
80
|
-
|
|
81
|
-
member
|
|
82
|
-
): boolean {
|
|
83
|
-
const object = member.object;
|
|
64
|
+
const isWindowLocationMember = (member: TSESTree.MemberExpression) => {
|
|
65
|
+
const { object } = member;
|
|
84
66
|
if (object.type !== "MemberExpression") {
|
|
85
67
|
return false;
|
|
86
68
|
}
|
|
87
69
|
const outerObject = object.object;
|
|
88
70
|
const outerProperty = object.property;
|
|
89
|
-
|
|
71
|
+
return (
|
|
90
72
|
outerObject.type === "Identifier" &&
|
|
91
73
|
outerObject.name === "window" &&
|
|
92
74
|
outerProperty.type === "Identifier" &&
|
|
93
75
|
outerProperty.name === "location"
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
76
|
+
);
|
|
77
|
+
};
|
|
99
78
|
|
|
100
|
-
|
|
101
|
-
member
|
|
102
|
-
): boolean {
|
|
103
|
-
const object = member.object;
|
|
79
|
+
const isWindowHistoryMember = (member: TSESTree.MemberExpression) => {
|
|
80
|
+
const { object } = member;
|
|
104
81
|
if (object.type !== "MemberExpression") {
|
|
105
82
|
return false;
|
|
106
83
|
}
|
|
107
84
|
const outerObject = object.object;
|
|
108
85
|
const outerProperty = object.property;
|
|
109
|
-
|
|
86
|
+
return (
|
|
110
87
|
outerObject.type === "Identifier" &&
|
|
111
88
|
outerObject.name === "window" &&
|
|
112
89
|
outerProperty.type === "Identifier" &&
|
|
113
90
|
outerProperty.name === "history"
|
|
114
|
-
)
|
|
115
|
-
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const reportHandlerExit = (state: HandlerState) => {
|
|
95
|
+
const { reason, sawReplaceCall, sawAllowedLocationRead } = state;
|
|
96
|
+
|
|
97
|
+
if (reason) {
|
|
98
|
+
context.report({
|
|
99
|
+
data: { reason },
|
|
100
|
+
messageId: "noButtonNavigation",
|
|
101
|
+
node: state.attribute
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (sawReplaceCall && !sawAllowedLocationRead) {
|
|
107
|
+
context.report({
|
|
108
|
+
data: {
|
|
109
|
+
reason: "history.replaceState/pushState without reading window.location"
|
|
110
|
+
},
|
|
111
|
+
messageId: "noButtonNavigation",
|
|
112
|
+
node: state.attribute
|
|
113
|
+
});
|
|
116
114
|
}
|
|
117
|
-
|
|
118
|
-
}
|
|
115
|
+
};
|
|
119
116
|
|
|
120
117
|
return {
|
|
121
118
|
ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression) {
|
|
@@ -126,8 +123,8 @@ export const noButtonNavigation: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
126
123
|
handlerStack.push({
|
|
127
124
|
attribute: attr,
|
|
128
125
|
reason: null,
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
sawAllowedLocationRead: false,
|
|
127
|
+
sawReplaceCall: false
|
|
131
128
|
});
|
|
132
129
|
},
|
|
133
130
|
"ArrowFunctionExpression:exit"(
|
|
@@ -142,30 +139,67 @@ export const noButtonNavigation: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
142
139
|
return;
|
|
143
140
|
}
|
|
144
141
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
reportHandlerExit(state);
|
|
143
|
+
},
|
|
144
|
+
AssignmentExpression(node: TSESTree.AssignmentExpression) {
|
|
145
|
+
const state = getCurrentHandler();
|
|
146
|
+
if (!state) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (node.left.type !== "MemberExpression") {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const { left } = node;
|
|
148
153
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
// window.location = ...
|
|
155
|
+
if (
|
|
156
|
+
left.object.type === "Identifier" &&
|
|
157
|
+
left.object.name === "window" &&
|
|
158
|
+
left.property.type === "Identifier" &&
|
|
159
|
+
left.property.name === "location" &&
|
|
160
|
+
!state.reason
|
|
161
|
+
) {
|
|
162
|
+
state.reason = "assignment to window.location";
|
|
155
163
|
return;
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
messageId: "noButtonNavigation",
|
|
162
|
-
data: {
|
|
163
|
-
reason: "history.replaceState/pushState without reading window.location"
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
+
// window.location.href = ... OR window.location.pathname = ...
|
|
167
|
+
if (isWindowLocationMember(left) && !state.reason) {
|
|
168
|
+
state.reason = "assignment to window.location sub-property";
|
|
166
169
|
}
|
|
167
170
|
},
|
|
171
|
+
CallExpression(node: TSESTree.CallExpression) {
|
|
172
|
+
const state = getCurrentHandler();
|
|
173
|
+
if (!state) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const { callee } = node;
|
|
177
|
+
|
|
178
|
+
if (callee.type !== "MemberExpression") {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
168
181
|
|
|
182
|
+
// 3) window.location.replace(...)
|
|
183
|
+
if (
|
|
184
|
+
isWindowLocationMember(callee) &&
|
|
185
|
+
callee.property.type === "Identifier" &&
|
|
186
|
+
callee.property.name === "replace" &&
|
|
187
|
+
!state.reason
|
|
188
|
+
) {
|
|
189
|
+
state.reason = "window.location.replace";
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 4) window.history.pushState(...) or replaceState(...)
|
|
194
|
+
if (
|
|
195
|
+
isWindowHistoryMember(callee) &&
|
|
196
|
+
callee.property.type === "Identifier" &&
|
|
197
|
+
(callee.property.name === "pushState" ||
|
|
198
|
+
callee.property.name === "replaceState")
|
|
199
|
+
) {
|
|
200
|
+
state.sawReplaceCall = true;
|
|
201
|
+
}
|
|
202
|
+
},
|
|
169
203
|
FunctionExpression(node: TSESTree.FunctionExpression) {
|
|
170
204
|
const attr = isOnClickButtonHandler(node);
|
|
171
205
|
if (!attr) {
|
|
@@ -174,8 +208,8 @@ export const noButtonNavigation: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
174
208
|
handlerStack.push({
|
|
175
209
|
attribute: attr,
|
|
176
210
|
reason: null,
|
|
177
|
-
|
|
178
|
-
|
|
211
|
+
sawAllowedLocationRead: false,
|
|
212
|
+
sawReplaceCall: false
|
|
179
213
|
});
|
|
180
214
|
},
|
|
181
215
|
"FunctionExpression:exit"(node: TSESTree.FunctionExpression) {
|
|
@@ -188,30 +222,8 @@ export const noButtonNavigation: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
188
222
|
return;
|
|
189
223
|
}
|
|
190
224
|
|
|
191
|
-
|
|
192
|
-
const sawReplaceCall = state.sawReplaceCall;
|
|
193
|
-
const sawAllowedLocationRead = state.sawAllowedLocationRead;
|
|
194
|
-
|
|
195
|
-
if (reason) {
|
|
196
|
-
context.report({
|
|
197
|
-
node: state.attribute,
|
|
198
|
-
messageId: "noButtonNavigation",
|
|
199
|
-
data: { reason }
|
|
200
|
-
});
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (sawReplaceCall && !sawAllowedLocationRead) {
|
|
205
|
-
context.report({
|
|
206
|
-
node: state.attribute,
|
|
207
|
-
messageId: "noButtonNavigation",
|
|
208
|
-
data: {
|
|
209
|
-
reason: "history.replaceState/pushState without reading window.location"
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
}
|
|
225
|
+
reportHandlerExit(state);
|
|
213
226
|
},
|
|
214
|
-
|
|
215
227
|
MemberExpression(node: TSESTree.MemberExpression) {
|
|
216
228
|
const state = getCurrentHandler();
|
|
217
229
|
if (!state) {
|
|
@@ -223,11 +235,10 @@ export const noButtonNavigation: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
223
235
|
node.object.type === "Identifier" &&
|
|
224
236
|
node.object.name === "window" &&
|
|
225
237
|
node.property.type === "Identifier" &&
|
|
226
|
-
node.property.name === "open"
|
|
238
|
+
node.property.name === "open" &&
|
|
239
|
+
!state.reason
|
|
227
240
|
) {
|
|
228
|
-
|
|
229
|
-
state.reason = "window.open";
|
|
230
|
-
}
|
|
241
|
+
state.reason = "window.open";
|
|
231
242
|
}
|
|
232
243
|
|
|
233
244
|
// 5) Reading window.location.search, .pathname, or .hash
|
|
@@ -240,73 +251,20 @@ export const noButtonNavigation: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
240
251
|
) {
|
|
241
252
|
state.sawAllowedLocationRead = true;
|
|
242
253
|
}
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
AssignmentExpression(node: TSESTree.AssignmentExpression) {
|
|
246
|
-
const state = getCurrentHandler();
|
|
247
|
-
if (!state) {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
if (node.left.type !== "MemberExpression") {
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
const left = node.left;
|
|
254
|
-
|
|
255
|
-
// window.location = ...
|
|
256
|
-
if (
|
|
257
|
-
left.object.type === "Identifier" &&
|
|
258
|
-
left.object.name === "window" &&
|
|
259
|
-
left.property.type === "Identifier" &&
|
|
260
|
-
left.property.name === "location"
|
|
261
|
-
) {
|
|
262
|
-
if (!state.reason) {
|
|
263
|
-
state.reason = "assignment to window.location";
|
|
264
|
-
}
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// window.location.href = ... OR window.location.pathname = ...
|
|
269
|
-
if (isWindowLocationMember(left)) {
|
|
270
|
-
if (!state.reason) {
|
|
271
|
-
state.reason =
|
|
272
|
-
"assignment to window.location sub-property";
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
|
|
277
|
-
CallExpression(node: TSESTree.CallExpression) {
|
|
278
|
-
const state = getCurrentHandler();
|
|
279
|
-
if (!state) {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const callee = node.callee;
|
|
283
|
-
|
|
284
|
-
if (callee.type !== "MemberExpression") {
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// 3) window.location.replace(...)
|
|
289
|
-
if (
|
|
290
|
-
isWindowLocationMember(callee) &&
|
|
291
|
-
callee.property.type === "Identifier" &&
|
|
292
|
-
callee.property.name === "replace"
|
|
293
|
-
) {
|
|
294
|
-
if (!state.reason) {
|
|
295
|
-
state.reason = "window.location.replace";
|
|
296
|
-
}
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// 4) window.history.pushState(...) or replaceState(...)
|
|
301
|
-
if (
|
|
302
|
-
isWindowHistoryMember(callee) &&
|
|
303
|
-
callee.property.type === "Identifier" &&
|
|
304
|
-
(callee.property.name === "pushState" ||
|
|
305
|
-
callee.property.name === "replaceState")
|
|
306
|
-
) {
|
|
307
|
-
state.sawReplaceCall = true;
|
|
308
|
-
}
|
|
309
254
|
}
|
|
310
255
|
};
|
|
256
|
+
},
|
|
257
|
+
defaultOptions: [],
|
|
258
|
+
meta: {
|
|
259
|
+
docs: {
|
|
260
|
+
description:
|
|
261
|
+
"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 + …)."
|
|
262
|
+
},
|
|
263
|
+
messages: {
|
|
264
|
+
noButtonNavigation:
|
|
265
|
+
"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."
|
|
266
|
+
},
|
|
267
|
+
schema: [],
|
|
268
|
+
type: "suggestion"
|
|
311
269
|
}
|
|
312
270
|
};
|
|
@@ -9,50 +9,31 @@ type AnyFunctionNode =
|
|
|
9
9
|
| TSESTree.ArrowFunctionExpression;
|
|
10
10
|
|
|
11
11
|
export const noExplicitReturnTypes: TSESLint.RuleModule<MessageIds, Options> = {
|
|
12
|
-
meta: {
|
|
13
|
-
type: "suggestion",
|
|
14
|
-
docs: {
|
|
15
|
-
description:
|
|
16
|
-
"Disallow explicit return type annotations on functions, except when using type predicates for type guards or inline object literal returns (e.g., style objects)."
|
|
17
|
-
},
|
|
18
|
-
schema: [],
|
|
19
|
-
messages: {
|
|
20
|
-
noExplicitReturnType:
|
|
21
|
-
"Explicit return types are disallowed; rely on TypeScript's inference instead."
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
defaultOptions: [],
|
|
26
|
-
|
|
27
12
|
create(context) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
13
|
+
const hasSingleObjectReturn = (body: TSESTree.BlockStatement) => {
|
|
14
|
+
const returnStatements = body.body.filter(
|
|
15
|
+
(stmt) => stmt.type === "ReturnStatement"
|
|
16
|
+
);
|
|
31
17
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
returnCount++;
|
|
35
|
-
const arg = stmt.argument;
|
|
36
|
-
if (arg && arg.type === "ObjectExpression") {
|
|
37
|
-
returnedObject = arg;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
18
|
+
if (returnStatements.length !== 1) {
|
|
19
|
+
return false;
|
|
40
20
|
}
|
|
41
21
|
|
|
42
|
-
|
|
43
|
-
|
|
22
|
+
const [returnStmt] = returnStatements;
|
|
23
|
+
return returnStmt?.argument?.type === "ObjectExpression";
|
|
24
|
+
};
|
|
44
25
|
|
|
45
26
|
return {
|
|
46
27
|
"FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(
|
|
47
28
|
node: AnyFunctionNode
|
|
48
29
|
) {
|
|
49
|
-
const returnType = node
|
|
30
|
+
const { returnType } = node;
|
|
50
31
|
if (!returnType) {
|
|
51
32
|
return;
|
|
52
33
|
}
|
|
53
34
|
|
|
54
35
|
// Allow type predicate annotations for type guards.
|
|
55
|
-
const typeAnnotation = returnType
|
|
36
|
+
const { typeAnnotation } = returnType;
|
|
56
37
|
if (
|
|
57
38
|
typeAnnotation &&
|
|
58
39
|
typeAnnotation.type === "TSTypePredicate"
|
|
@@ -70,18 +51,33 @@ export const noExplicitReturnTypes: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
70
51
|
}
|
|
71
52
|
|
|
72
53
|
// Allow if the function has a block body with a single return statement that returns an object literal.
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
54
|
+
if (
|
|
55
|
+
node.body &&
|
|
56
|
+
node.body.type === "BlockStatement" &&
|
|
57
|
+
hasSingleObjectReturn(node.body)
|
|
58
|
+
) {
|
|
59
|
+
return;
|
|
77
60
|
}
|
|
78
61
|
|
|
79
62
|
// Otherwise, report an error.
|
|
80
63
|
context.report({
|
|
81
|
-
|
|
82
|
-
|
|
64
|
+
messageId: "noExplicitReturnType",
|
|
65
|
+
node: returnType
|
|
83
66
|
});
|
|
84
67
|
}
|
|
85
68
|
};
|
|
69
|
+
},
|
|
70
|
+
defaultOptions: [],
|
|
71
|
+
meta: {
|
|
72
|
+
docs: {
|
|
73
|
+
description:
|
|
74
|
+
"Disallow explicit return type annotations on functions, except when using type predicates for type guards or inline object literal returns (e.g., style objects)."
|
|
75
|
+
},
|
|
76
|
+
messages: {
|
|
77
|
+
noExplicitReturnType:
|
|
78
|
+
"Explicit return types are disallowed; rely on TypeScript's inference instead."
|
|
79
|
+
},
|
|
80
|
+
schema: [],
|
|
81
|
+
type: "suggestion"
|
|
86
82
|
}
|
|
87
83
|
};
|
|
@@ -4,44 +4,31 @@ type Options = [];
|
|
|
4
4
|
type MessageIds = "noInlinePropTypes";
|
|
5
5
|
|
|
6
6
|
export const noInlinePropTypes: TSESLint.RuleModule<MessageIds, Options> = {
|
|
7
|
-
meta: {
|
|
8
|
-
type: "suggestion",
|
|
9
|
-
docs: {
|
|
10
|
-
description:
|
|
11
|
-
"Enforce that component prop types are not defined inline (using an object literal) but rather use a named type or interface."
|
|
12
|
-
},
|
|
13
|
-
schema: [],
|
|
14
|
-
messages: {
|
|
15
|
-
noInlinePropTypes:
|
|
16
|
-
"Inline prop type definitions are not allowed. Use a named type alias or interface instead of an inline object type."
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
defaultOptions: [],
|
|
21
|
-
|
|
22
7
|
create(context) {
|
|
23
8
|
/**
|
|
24
9
|
* Checks the node representing a parameter to determine if it is an ObjectPattern with an inline type literal.
|
|
25
10
|
* @param {ASTNode} param The parameter node from the function declaration/expression.
|
|
26
11
|
*/
|
|
27
|
-
|
|
12
|
+
const checkParameter = (param: TSESTree.Parameter) => {
|
|
28
13
|
// Ensure we are dealing with a destructured object pattern with a type annotation.
|
|
29
14
|
if (
|
|
30
|
-
param.type
|
|
31
|
-
param.typeAnnotation
|
|
32
|
-
param.typeAnnotation.type
|
|
15
|
+
param.type !== "ObjectPattern" ||
|
|
16
|
+
!param.typeAnnotation ||
|
|
17
|
+
param.typeAnnotation.type !== "TSTypeAnnotation"
|
|
33
18
|
) {
|
|
34
|
-
|
|
35
|
-
const annotation = param.typeAnnotation.typeAnnotation;
|
|
36
|
-
// If the type is an inline object (TSTypeLiteral), we want to report it.
|
|
37
|
-
if (annotation.type === "TSTypeLiteral") {
|
|
38
|
-
context.report({
|
|
39
|
-
node: param,
|
|
40
|
-
messageId: "noInlinePropTypes"
|
|
41
|
-
});
|
|
42
|
-
}
|
|
19
|
+
return;
|
|
43
20
|
}
|
|
44
|
-
|
|
21
|
+
|
|
22
|
+
// The actual type annotation node (for example, { mode: string } yields a TSTypeLiteral).
|
|
23
|
+
const annotation = param.typeAnnotation.typeAnnotation;
|
|
24
|
+
// If the type is an inline object (TSTypeLiteral), we want to report it.
|
|
25
|
+
if (annotation.type === "TSTypeLiteral") {
|
|
26
|
+
context.report({
|
|
27
|
+
messageId: "noInlinePropTypes",
|
|
28
|
+
node: param
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
45
32
|
|
|
46
33
|
return {
|
|
47
34
|
// Applies to FunctionDeclaration, ArrowFunctionExpression, and FunctionExpression nodes.
|
|
@@ -56,7 +43,7 @@ export const noInlinePropTypes: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
56
43
|
return;
|
|
57
44
|
}
|
|
58
45
|
|
|
59
|
-
const firstParam = node.params
|
|
46
|
+
const [firstParam] = node.params;
|
|
60
47
|
if (!firstParam) {
|
|
61
48
|
return;
|
|
62
49
|
}
|
|
@@ -64,5 +51,18 @@ export const noInlinePropTypes: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
64
51
|
checkParameter(firstParam);
|
|
65
52
|
}
|
|
66
53
|
};
|
|
54
|
+
},
|
|
55
|
+
defaultOptions: [],
|
|
56
|
+
meta: {
|
|
57
|
+
docs: {
|
|
58
|
+
description:
|
|
59
|
+
"Enforce that component prop types are not defined inline (using an object literal) but rather use a named type or interface."
|
|
60
|
+
},
|
|
61
|
+
messages: {
|
|
62
|
+
noInlinePropTypes:
|
|
63
|
+
"Inline prop type definitions are not allowed. Use a named type alias or interface instead of an inline object type."
|
|
64
|
+
},
|
|
65
|
+
schema: [],
|
|
66
|
+
type: "suggestion"
|
|
67
67
|
}
|
|
68
68
|
};
|
|
@@ -9,79 +9,72 @@ import { TSESLint, TSESTree } from "@typescript-eslint/utils";
|
|
|
9
9
|
type Options = [];
|
|
10
10
|
type MessageIds = "noMultiStyleObjects";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"Do not group CSS style objects in a single export; export each style separately."
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
defaultOptions: [],
|
|
12
|
+
const getPropertyName = (prop: TSESTree.Property) => {
|
|
13
|
+
const { key } = prop;
|
|
14
|
+
if (key.type === "Identifier") {
|
|
15
|
+
return key.name;
|
|
16
|
+
}
|
|
17
|
+
if (key.type === "Literal" && typeof key.value === "string") {
|
|
18
|
+
return key.value;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
27
22
|
|
|
23
|
+
export const noMultiStyleObjects: TSESLint.RuleModule<MessageIds, Options> = {
|
|
28
24
|
create(context) {
|
|
29
25
|
/**
|
|
30
26
|
* Checks if the given ObjectExpression node contains multiple properties
|
|
31
27
|
* that look like CSS style objects (i.e. property keys ending with "Style").
|
|
32
28
|
*/
|
|
33
|
-
|
|
29
|
+
const checkObjectExpression = (node: TSESTree.ObjectExpression) => {
|
|
34
30
|
if (!node.properties.length) {
|
|
35
31
|
return;
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
const cssStyleProperties
|
|
39
|
-
|
|
40
|
-
for (const prop of node.properties) {
|
|
34
|
+
const cssStyleProperties = node.properties.filter((prop) => {
|
|
41
35
|
if (prop.type !== "Property") {
|
|
42
|
-
|
|
36
|
+
return false;
|
|
43
37
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (key.type === "Identifier") {
|
|
49
|
-
name = key.name;
|
|
50
|
-
} else if (
|
|
51
|
-
key.type === "Literal" &&
|
|
52
|
-
typeof key.value === "string"
|
|
53
|
-
) {
|
|
54
|
-
name = key.value;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (name && name.endsWith("Style")) {
|
|
58
|
-
cssStyleProperties.push(prop);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
38
|
+
const name = getPropertyName(prop);
|
|
39
|
+
return name !== null && name.endsWith("Style");
|
|
40
|
+
});
|
|
61
41
|
|
|
62
42
|
if (cssStyleProperties.length > 1) {
|
|
63
43
|
context.report({
|
|
64
|
-
|
|
65
|
-
|
|
44
|
+
messageId: "noMultiStyleObjects",
|
|
45
|
+
node
|
|
66
46
|
});
|
|
67
47
|
}
|
|
68
|
-
}
|
|
48
|
+
};
|
|
69
49
|
|
|
70
50
|
return {
|
|
71
51
|
// Check default exports that are object literals.
|
|
72
52
|
ExportDefaultDeclaration(node: TSESTree.ExportDefaultDeclaration) {
|
|
73
|
-
const declaration = node
|
|
53
|
+
const { declaration } = node;
|
|
74
54
|
if (declaration && declaration.type === "ObjectExpression") {
|
|
75
55
|
checkObjectExpression(declaration);
|
|
76
56
|
}
|
|
77
57
|
},
|
|
78
58
|
// Optionally, also check for object literals returned from exported functions.
|
|
79
59
|
ReturnStatement(node: TSESTree.ReturnStatement) {
|
|
80
|
-
const argument = node
|
|
60
|
+
const { argument } = node;
|
|
81
61
|
if (argument && argument.type === "ObjectExpression") {
|
|
82
62
|
checkObjectExpression(argument);
|
|
83
63
|
}
|
|
84
64
|
}
|
|
85
65
|
};
|
|
66
|
+
},
|
|
67
|
+
defaultOptions: [],
|
|
68
|
+
meta: {
|
|
69
|
+
docs: {
|
|
70
|
+
description:
|
|
71
|
+
"Disallow grouping CSS style objects in a single export; export each style separately."
|
|
72
|
+
},
|
|
73
|
+
messages: {
|
|
74
|
+
noMultiStyleObjects:
|
|
75
|
+
"Do not group CSS style objects in a single export; export each style separately."
|
|
76
|
+
},
|
|
77
|
+
schema: [], // no options,
|
|
78
|
+
type: "problem"
|
|
86
79
|
}
|
|
87
80
|
};
|