eslint-plugin-absolute 0.1.6 → 0.2.0
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/.claude/settings.local.json +5 -0
- package/dist/index.js +1226 -798
- package/package.json +12 -11
- package/src/index.ts +45 -0
- package/src/rules/explicit-object-types.ts +73 -0
- package/src/rules/{inline-style-limit.js → inline-style-limit.ts} +16 -7
- package/src/rules/localize-react-props.ts +459 -0
- package/src/rules/max-depth-extended.ts +164 -0
- package/src/rules/{max-jsx-nesting.js → max-jsx-nesting.ts} +15 -8
- package/src/rules/{min-var-length.js → min-var-length.ts} +75 -45
- package/src/rules/no-button-navigation.ts +312 -0
- package/src/rules/no-explicit-return-types.ts +87 -0
- package/src/rules/{no-inline-prop-types.js → no-inline-prop-types.ts} +22 -9
- package/src/rules/no-multi-style-objects.ts +87 -0
- package/src/rules/no-nested-jsx-return.ts +210 -0
- package/src/rules/no-or-none-component.ts +65 -0
- package/src/rules/{no-transition-cssproperties.js → no-transition-cssproperties.ts} +50 -27
- package/src/rules/no-unnecessary-div.ts +73 -0
- package/src/rules/{no-unnecessary-key.js → no-unnecessary-key.ts} +43 -26
- package/src/rules/no-useless-function.ts +58 -0
- package/src/rules/seperate-style-files.ts +81 -0
- package/src/rules/sort-exports.ts +420 -0
- package/src/rules/sort-keys-fixable.ts +621 -0
- package/src/rules/spring-naming-convention.ts +145 -0
- package/tsconfig.json +2 -1
- package/src/index.js +0 -45
- package/src/rules/explicit-object-types.js +0 -54
- package/src/rules/localize-react-props.js +0 -418
- package/src/rules/max-depth-extended.js +0 -124
- package/src/rules/no-button-navigation.js +0 -232
- package/src/rules/no-explicit-return-types.js +0 -64
- 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-unnecessary-div.js +0 -40
- 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
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { TSESLint, TSESTree } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
type Options = [];
|
|
4
|
+
type MessageIds = "noButtonNavigation";
|
|
5
|
+
|
|
6
|
+
type HandlerState = {
|
|
7
|
+
attribute: TSESTree.JSXAttribute;
|
|
8
|
+
reason: string | null;
|
|
9
|
+
sawReplaceCall: boolean;
|
|
10
|
+
sawAllowedLocationRead: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
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
|
+
create(context) {
|
|
30
|
+
const handlerStack: HandlerState[] = [];
|
|
31
|
+
|
|
32
|
+
function getCurrentHandler(): HandlerState | null {
|
|
33
|
+
const state = handlerStack[handlerStack.length - 1];
|
|
34
|
+
if (!state) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return state;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isOnClickButtonHandler(
|
|
41
|
+
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression
|
|
42
|
+
): TSESTree.JSXAttribute | null {
|
|
43
|
+
const parent = node.parent;
|
|
44
|
+
if (!parent || parent.type !== "JSXExpressionContainer") {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const attributeCandidate = parent.parent;
|
|
48
|
+
if (
|
|
49
|
+
!attributeCandidate ||
|
|
50
|
+
attributeCandidate.type !== "JSXAttribute"
|
|
51
|
+
) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const attr = attributeCandidate;
|
|
55
|
+
if (
|
|
56
|
+
!attr.name ||
|
|
57
|
+
attr.name.type !== "JSXIdentifier" ||
|
|
58
|
+
attr.name.name !== "onClick"
|
|
59
|
+
) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const openingElementCandidate = attr.parent;
|
|
63
|
+
if (
|
|
64
|
+
!openingElementCandidate ||
|
|
65
|
+
openingElementCandidate.type !== "JSXOpeningElement"
|
|
66
|
+
) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const openingElement = openingElementCandidate;
|
|
70
|
+
const tagNameNode = openingElement.name;
|
|
71
|
+
if (
|
|
72
|
+
tagNameNode.type !== "JSXIdentifier" ||
|
|
73
|
+
tagNameNode.name !== "button"
|
|
74
|
+
) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return attr;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isWindowLocationMember(
|
|
81
|
+
member: TSESTree.MemberExpression
|
|
82
|
+
): boolean {
|
|
83
|
+
const object = member.object;
|
|
84
|
+
if (object.type !== "MemberExpression") {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const outerObject = object.object;
|
|
88
|
+
const outerProperty = object.property;
|
|
89
|
+
if (
|
|
90
|
+
outerObject.type === "Identifier" &&
|
|
91
|
+
outerObject.name === "window" &&
|
|
92
|
+
outerProperty.type === "Identifier" &&
|
|
93
|
+
outerProperty.name === "location"
|
|
94
|
+
) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isWindowHistoryMember(
|
|
101
|
+
member: TSESTree.MemberExpression
|
|
102
|
+
): boolean {
|
|
103
|
+
const object = member.object;
|
|
104
|
+
if (object.type !== "MemberExpression") {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const outerObject = object.object;
|
|
108
|
+
const outerProperty = object.property;
|
|
109
|
+
if (
|
|
110
|
+
outerObject.type === "Identifier" &&
|
|
111
|
+
outerObject.name === "window" &&
|
|
112
|
+
outerProperty.type === "Identifier" &&
|
|
113
|
+
outerProperty.name === "history"
|
|
114
|
+
) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression) {
|
|
122
|
+
const attr = isOnClickButtonHandler(node);
|
|
123
|
+
if (!attr) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
handlerStack.push({
|
|
127
|
+
attribute: attr,
|
|
128
|
+
reason: null,
|
|
129
|
+
sawReplaceCall: false,
|
|
130
|
+
sawAllowedLocationRead: false
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
"ArrowFunctionExpression:exit"(
|
|
134
|
+
node: TSESTree.ArrowFunctionExpression
|
|
135
|
+
) {
|
|
136
|
+
const attr = isOnClickButtonHandler(node);
|
|
137
|
+
if (!attr) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const state = handlerStack.pop();
|
|
141
|
+
if (!state) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const reason = state.reason;
|
|
146
|
+
const sawReplaceCall = state.sawReplaceCall;
|
|
147
|
+
const sawAllowedLocationRead = state.sawAllowedLocationRead;
|
|
148
|
+
|
|
149
|
+
if (reason) {
|
|
150
|
+
context.report({
|
|
151
|
+
node: state.attribute,
|
|
152
|
+
messageId: "noButtonNavigation",
|
|
153
|
+
data: { reason }
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (sawReplaceCall && !sawAllowedLocationRead) {
|
|
159
|
+
context.report({
|
|
160
|
+
node: state.attribute,
|
|
161
|
+
messageId: "noButtonNavigation",
|
|
162
|
+
data: {
|
|
163
|
+
reason: "history.replaceState/pushState without reading window.location"
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
FunctionExpression(node: TSESTree.FunctionExpression) {
|
|
170
|
+
const attr = isOnClickButtonHandler(node);
|
|
171
|
+
if (!attr) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
handlerStack.push({
|
|
175
|
+
attribute: attr,
|
|
176
|
+
reason: null,
|
|
177
|
+
sawReplaceCall: false,
|
|
178
|
+
sawAllowedLocationRead: false
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
"FunctionExpression:exit"(node: TSESTree.FunctionExpression) {
|
|
182
|
+
const attr = isOnClickButtonHandler(node);
|
|
183
|
+
if (!attr) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const state = handlerStack.pop();
|
|
187
|
+
if (!state) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const reason = state.reason;
|
|
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
|
+
}
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
MemberExpression(node: TSESTree.MemberExpression) {
|
|
216
|
+
const state = getCurrentHandler();
|
|
217
|
+
if (!state) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 1) window.open(...)
|
|
222
|
+
if (
|
|
223
|
+
node.object.type === "Identifier" &&
|
|
224
|
+
node.object.name === "window" &&
|
|
225
|
+
node.property.type === "Identifier" &&
|
|
226
|
+
node.property.name === "open"
|
|
227
|
+
) {
|
|
228
|
+
if (!state.reason) {
|
|
229
|
+
state.reason = "window.open";
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 5) Reading window.location.search, .pathname, or .hash
|
|
234
|
+
if (
|
|
235
|
+
isWindowLocationMember(node) &&
|
|
236
|
+
node.property.type === "Identifier" &&
|
|
237
|
+
(node.property.name === "search" ||
|
|
238
|
+
node.property.name === "pathname" ||
|
|
239
|
+
node.property.name === "hash")
|
|
240
|
+
) {
|
|
241
|
+
state.sawAllowedLocationRead = true;
|
|
242
|
+
}
|
|
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
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { TSESLint, TSESTree } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
type Options = [];
|
|
4
|
+
type MessageIds = "noExplicitReturnType";
|
|
5
|
+
|
|
6
|
+
type AnyFunctionNode =
|
|
7
|
+
| TSESTree.FunctionDeclaration
|
|
8
|
+
| TSESTree.FunctionExpression
|
|
9
|
+
| TSESTree.ArrowFunctionExpression;
|
|
10
|
+
|
|
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
|
+
create(context) {
|
|
28
|
+
function hasSingleObjectReturn(body: TSESTree.BlockStatement) {
|
|
29
|
+
let returnCount = 0;
|
|
30
|
+
let returnedObject: TSESTree.ObjectExpression | null = null;
|
|
31
|
+
|
|
32
|
+
for (const stmt of body.body) {
|
|
33
|
+
if (stmt.type === "ReturnStatement") {
|
|
34
|
+
returnCount++;
|
|
35
|
+
const arg = stmt.argument;
|
|
36
|
+
if (arg && arg.type === "ObjectExpression") {
|
|
37
|
+
returnedObject = arg;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return returnCount === 1 && returnedObject !== null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
"FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(
|
|
47
|
+
node: AnyFunctionNode
|
|
48
|
+
) {
|
|
49
|
+
const returnType = node.returnType;
|
|
50
|
+
if (!returnType) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Allow type predicate annotations for type guards.
|
|
55
|
+
const typeAnnotation = returnType.typeAnnotation;
|
|
56
|
+
if (
|
|
57
|
+
typeAnnotation &&
|
|
58
|
+
typeAnnotation.type === "TSTypePredicate"
|
|
59
|
+
) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Allow if it's an arrow function that directly returns an object literal.
|
|
64
|
+
if (
|
|
65
|
+
node.type === "ArrowFunctionExpression" &&
|
|
66
|
+
node.expression === true &&
|
|
67
|
+
node.body.type === "ObjectExpression"
|
|
68
|
+
) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Allow if the function has a block body with a single return statement that returns an object literal.
|
|
73
|
+
if (node.body && node.body.type === "BlockStatement") {
|
|
74
|
+
if (hasSingleObjectReturn(node.body)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Otherwise, report an error.
|
|
80
|
+
context.report({
|
|
81
|
+
node: returnType,
|
|
82
|
+
messageId: "noExplicitReturnType"
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { TSESLint, TSESTree } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
type Options = [];
|
|
4
|
+
type MessageIds = "noInlinePropTypes";
|
|
5
|
+
|
|
6
|
+
export const noInlinePropTypes: TSESLint.RuleModule<MessageIds, Options> = {
|
|
2
7
|
meta: {
|
|
3
8
|
type: "suggestion",
|
|
4
9
|
docs: {
|
|
5
10
|
description:
|
|
6
|
-
"Enforce that component prop types are not defined inline (using an object literal) but rather use a named type or interface."
|
|
7
|
-
category: "Best Practices",
|
|
8
|
-
recommended: false
|
|
11
|
+
"Enforce that component prop types are not defined inline (using an object literal) but rather use a named type or interface."
|
|
9
12
|
},
|
|
10
13
|
schema: [],
|
|
11
14
|
messages: {
|
|
@@ -14,15 +17,16 @@ export default {
|
|
|
14
17
|
}
|
|
15
18
|
},
|
|
16
19
|
|
|
20
|
+
defaultOptions: [],
|
|
21
|
+
|
|
17
22
|
create(context) {
|
|
18
23
|
/**
|
|
19
24
|
* Checks the node representing a parameter to determine if it is an ObjectPattern with an inline type literal.
|
|
20
25
|
* @param {ASTNode} param The parameter node from the function declaration/expression.
|
|
21
26
|
*/
|
|
22
|
-
function checkParameter(param) {
|
|
27
|
+
function checkParameter(param: TSESTree.Parameter) {
|
|
23
28
|
// Ensure we are dealing with a destructured object pattern with a type annotation.
|
|
24
29
|
if (
|
|
25
|
-
param &&
|
|
26
30
|
param.type === "ObjectPattern" &&
|
|
27
31
|
param.typeAnnotation &&
|
|
28
32
|
param.typeAnnotation.type === "TSTypeAnnotation"
|
|
@@ -42,13 +46,22 @@ export default {
|
|
|
42
46
|
return {
|
|
43
47
|
// Applies to FunctionDeclaration, ArrowFunctionExpression, and FunctionExpression nodes.
|
|
44
48
|
"FunctionDeclaration, ArrowFunctionExpression, FunctionExpression"(
|
|
45
|
-
node
|
|
49
|
+
node:
|
|
50
|
+
| TSESTree.FunctionDeclaration
|
|
51
|
+
| TSESTree.ArrowFunctionExpression
|
|
52
|
+
| TSESTree.FunctionExpression
|
|
46
53
|
) {
|
|
47
54
|
// It is common to define props as the first parameter.
|
|
55
|
+
if (node.params.length === 0) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
const firstParam = node.params[0];
|
|
49
|
-
if (firstParam) {
|
|
50
|
-
|
|
60
|
+
if (!firstParam) {
|
|
61
|
+
return;
|
|
51
62
|
}
|
|
63
|
+
|
|
64
|
+
checkParameter(firstParam);
|
|
52
65
|
}
|
|
53
66
|
};
|
|
54
67
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { TSESLint, TSESTree } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Disallow grouping CSS style objects in a single export.
|
|
5
|
+
* Instead of exporting an object that contains multiple CSS style objects,
|
|
6
|
+
* export each style separately.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type Options = [];
|
|
10
|
+
type MessageIds = "noMultiStyleObjects";
|
|
11
|
+
|
|
12
|
+
export const noMultiStyleObjects: TSESLint.RuleModule<MessageIds, Options> = {
|
|
13
|
+
meta: {
|
|
14
|
+
type: "problem",
|
|
15
|
+
docs: {
|
|
16
|
+
description:
|
|
17
|
+
"Disallow grouping CSS style objects in a single export; export each style separately."
|
|
18
|
+
},
|
|
19
|
+
schema: [], // no options
|
|
20
|
+
messages: {
|
|
21
|
+
noMultiStyleObjects:
|
|
22
|
+
"Do not group CSS style objects in a single export; export each style separately."
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
defaultOptions: [],
|
|
27
|
+
|
|
28
|
+
create(context) {
|
|
29
|
+
/**
|
|
30
|
+
* Checks if the given ObjectExpression node contains multiple properties
|
|
31
|
+
* that look like CSS style objects (i.e. property keys ending with "Style").
|
|
32
|
+
*/
|
|
33
|
+
function checkObjectExpression(node: TSESTree.ObjectExpression) {
|
|
34
|
+
if (!node.properties.length) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const cssStyleProperties: TSESTree.Property[] = [];
|
|
39
|
+
|
|
40
|
+
for (const prop of node.properties) {
|
|
41
|
+
if (prop.type !== "Property") {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const key = prop.key;
|
|
46
|
+
let name: string | null = null;
|
|
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
|
+
}
|
|
61
|
+
|
|
62
|
+
if (cssStyleProperties.length > 1) {
|
|
63
|
+
context.report({
|
|
64
|
+
node,
|
|
65
|
+
messageId: "noMultiStyleObjects"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
// Check default exports that are object literals.
|
|
72
|
+
ExportDefaultDeclaration(node: TSESTree.ExportDefaultDeclaration) {
|
|
73
|
+
const declaration = node.declaration;
|
|
74
|
+
if (declaration && declaration.type === "ObjectExpression") {
|
|
75
|
+
checkObjectExpression(declaration);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
// Optionally, also check for object literals returned from exported functions.
|
|
79
|
+
ReturnStatement(node: TSESTree.ReturnStatement) {
|
|
80
|
+
const argument = node.argument;
|
|
81
|
+
if (argument && argument.type === "ObjectExpression") {
|
|
82
|
+
checkObjectExpression(argument);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
};
|