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
|
@@ -17,40 +17,19 @@ type AnyFunctionNode =
|
|
|
17
17
|
| TSESTree.ArrowFunctionExpression;
|
|
18
18
|
|
|
19
19
|
export const noNestedJSXReturn: TSESLint.RuleModule<MessageIds, Options> = {
|
|
20
|
-
meta: {
|
|
21
|
-
type: "problem",
|
|
22
|
-
docs: {
|
|
23
|
-
description:
|
|
24
|
-
"Disallow nested functions that return non-component, non-singular JSX to enforce one component per file"
|
|
25
|
-
},
|
|
26
|
-
schema: [],
|
|
27
|
-
messages: {
|
|
28
|
-
nestedFunctionJSX:
|
|
29
|
-
"Nested function returning non-component, non-singular JSX detected. Extract it into its own component.",
|
|
30
|
-
nestedArrowJSX:
|
|
31
|
-
"Nested arrow function returning non-component, non-singular JSX detected. Extract it into its own component.",
|
|
32
|
-
nestedArrowFragment:
|
|
33
|
-
"Nested arrow function returning a non-singular JSX fragment detected. Extract it into its own component."
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
defaultOptions: [],
|
|
38
|
-
|
|
39
20
|
create(context) {
|
|
40
21
|
// Returns true if the node is a JSX element or fragment.
|
|
41
|
-
|
|
22
|
+
const isJSX = (
|
|
42
23
|
node: TSESTree.Node | null | undefined
|
|
43
|
-
): node is TSESTree.JSXElement | TSESTree.JSXFragment
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
function getLeftmostJSXIdentifier(
|
|
24
|
+
): node is TSESTree.JSXElement | TSESTree.JSXFragment =>
|
|
25
|
+
node !== null &&
|
|
26
|
+
node !== undefined &&
|
|
27
|
+
(node.type === AST_NODE_TYPES.JSXElement ||
|
|
28
|
+
node.type === AST_NODE_TYPES.JSXFragment);
|
|
29
|
+
|
|
30
|
+
const getLeftmostJSXIdentifier = (
|
|
52
31
|
name: TSESTree.JSXTagNameExpression
|
|
53
|
-
)
|
|
32
|
+
) => {
|
|
54
33
|
let current: TSESTree.JSXTagNameExpression = name;
|
|
55
34
|
while (current.type === AST_NODE_TYPES.JSXMemberExpression) {
|
|
56
35
|
current = current.object;
|
|
@@ -59,10 +38,12 @@ export const noNestedJSXReturn: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
59
38
|
return current;
|
|
60
39
|
}
|
|
61
40
|
return null;
|
|
62
|
-
}
|
|
41
|
+
};
|
|
63
42
|
|
|
64
43
|
// Returns true if the JSX element is a component (its tag name starts with an uppercase letter).
|
|
65
|
-
|
|
44
|
+
const isJSXComponentElement = (
|
|
45
|
+
node: TSESTree.Node | null | undefined
|
|
46
|
+
) => {
|
|
66
47
|
if (!node || node.type !== AST_NODE_TYPES.JSXElement) {
|
|
67
48
|
return false;
|
|
68
49
|
}
|
|
@@ -78,13 +59,23 @@ export const noNestedJSXReturn: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
78
59
|
return false;
|
|
79
60
|
}
|
|
80
61
|
return /^[A-Z]/.test(leftmost.name);
|
|
81
|
-
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const hasNoMeaningfulChildren = (children: TSESTree.JSXChild[]) => {
|
|
65
|
+
const filtered = children.filter((child) => {
|
|
66
|
+
if (child.type === AST_NODE_TYPES.JSXText) {
|
|
67
|
+
return child.value.trim() !== "";
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
});
|
|
71
|
+
return filtered.length === 0;
|
|
72
|
+
};
|
|
82
73
|
|
|
83
74
|
// Returns true if the returned JSX is singular.
|
|
84
75
|
// For both JSXElement and JSXFragment, singular means 0 or 1 non-whitespace child.
|
|
85
|
-
|
|
76
|
+
const isSingularJSXReturn = (
|
|
86
77
|
node: TSESTree.JSXElement | TSESTree.JSXFragment
|
|
87
|
-
) {
|
|
78
|
+
) => {
|
|
88
79
|
if (!isJSX(node)) return false;
|
|
89
80
|
|
|
90
81
|
const children = node.children.filter((child) => {
|
|
@@ -100,69 +91,85 @@ export const noNestedJSXReturn: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
100
91
|
}
|
|
101
92
|
|
|
102
93
|
// Check if the returned element has exactly one child.
|
|
103
|
-
if (children.length
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
94
|
+
if (children.length !== 1) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
108
97
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
child.type === AST_NODE_TYPES.JSXElement ||
|
|
113
|
-
child.type === AST_NODE_TYPES.JSXFragment
|
|
114
|
-
) {
|
|
115
|
-
const innerChildren = child.children.filter(
|
|
116
|
-
(innerChild) => {
|
|
117
|
-
if (innerChild.type === AST_NODE_TYPES.JSXText) {
|
|
118
|
-
return innerChild.value.trim() !== "";
|
|
119
|
-
}
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
);
|
|
123
|
-
return innerChildren.length === 0;
|
|
124
|
-
}
|
|
125
|
-
// If it’s not a JSX element (maybe a simple expression), it's acceptable.
|
|
126
|
-
return true;
|
|
98
|
+
const [child] = children;
|
|
99
|
+
if (!child) {
|
|
100
|
+
return false;
|
|
127
101
|
}
|
|
128
102
|
|
|
129
|
-
// If
|
|
130
|
-
|
|
131
|
-
|
|
103
|
+
// If the singular child is also a JSX element or fragment,
|
|
104
|
+
// ensure that it doesn't have any meaningful children.
|
|
105
|
+
if (
|
|
106
|
+
child.type === AST_NODE_TYPES.JSXElement ||
|
|
107
|
+
child.type === AST_NODE_TYPES.JSXFragment
|
|
108
|
+
) {
|
|
109
|
+
return hasNoMeaningfulChildren(child.children);
|
|
110
|
+
}
|
|
111
|
+
// If it's not a JSX element (maybe a simple expression), it's acceptable.
|
|
112
|
+
return true;
|
|
113
|
+
};
|
|
132
114
|
|
|
133
115
|
// Stack to track nested function nodes.
|
|
134
116
|
const functionStack: AnyFunctionNode[] = [];
|
|
135
|
-
|
|
117
|
+
const pushFunction = (node: AnyFunctionNode) => {
|
|
136
118
|
functionStack.push(node);
|
|
137
|
-
}
|
|
138
|
-
|
|
119
|
+
};
|
|
120
|
+
const popFunction = () => {
|
|
139
121
|
functionStack.pop();
|
|
140
|
-
}
|
|
122
|
+
};
|
|
141
123
|
|
|
142
124
|
return {
|
|
125
|
+
// For implicit returns in arrow functions, use the same checks.
|
|
126
|
+
"ArrowFunctionExpression > JSXElement"(node: TSESTree.JSXElement) {
|
|
127
|
+
if (functionStack.length <= 1) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (
|
|
131
|
+
!isJSXComponentElement(node) &&
|
|
132
|
+
!isSingularJSXReturn(node)
|
|
133
|
+
) {
|
|
134
|
+
context.report({
|
|
135
|
+
messageId: "nestedArrowJSX",
|
|
136
|
+
node
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"ArrowFunctionExpression > JSXFragment"(
|
|
141
|
+
node: TSESTree.JSXFragment
|
|
142
|
+
) {
|
|
143
|
+
if (functionStack.length <= 1) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (!isSingularJSXReturn(node)) {
|
|
147
|
+
context.report({
|
|
148
|
+
messageId: "nestedArrowFragment",
|
|
149
|
+
node
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"ArrowFunctionExpression:exit"() {
|
|
154
|
+
popFunction();
|
|
155
|
+
},
|
|
143
156
|
"FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(
|
|
144
157
|
node: AnyFunctionNode
|
|
145
158
|
) {
|
|
146
159
|
pushFunction(node);
|
|
147
160
|
},
|
|
148
|
-
"FunctionDeclaration:exit"(
|
|
161
|
+
"FunctionDeclaration:exit"() {
|
|
149
162
|
popFunction();
|
|
150
163
|
},
|
|
151
|
-
"FunctionExpression:exit"(
|
|
152
|
-
popFunction();
|
|
153
|
-
},
|
|
154
|
-
"ArrowFunctionExpression:exit"(
|
|
155
|
-
_node: TSESTree.ArrowFunctionExpression
|
|
156
|
-
) {
|
|
164
|
+
"FunctionExpression:exit"() {
|
|
157
165
|
popFunction();
|
|
158
166
|
},
|
|
159
|
-
|
|
160
167
|
// For explicit return statements, report if the returned JSX is not a component and not singular.
|
|
161
168
|
ReturnStatement(node: TSESTree.ReturnStatement) {
|
|
162
169
|
if (functionStack.length <= 1) {
|
|
163
170
|
return;
|
|
164
171
|
}
|
|
165
|
-
const argument = node
|
|
172
|
+
const { argument } = node;
|
|
166
173
|
if (!isJSX(argument)) {
|
|
167
174
|
return;
|
|
168
175
|
}
|
|
@@ -171,40 +178,28 @@ export const noNestedJSXReturn: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
171
178
|
!isSingularJSXReturn(argument)
|
|
172
179
|
) {
|
|
173
180
|
context.report({
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
|
|
180
|
-
// For implicit returns in arrow functions, use the same checks.
|
|
181
|
-
"ArrowFunctionExpression > JSXElement"(node: TSESTree.JSXElement) {
|
|
182
|
-
if (functionStack.length <= 1) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (
|
|
186
|
-
!isJSXComponentElement(node) &&
|
|
187
|
-
!isSingularJSXReturn(node)
|
|
188
|
-
) {
|
|
189
|
-
context.report({
|
|
190
|
-
node,
|
|
191
|
-
messageId: "nestedArrowJSX"
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
"ArrowFunctionExpression > JSXFragment"(
|
|
196
|
-
node: TSESTree.JSXFragment
|
|
197
|
-
) {
|
|
198
|
-
if (functionStack.length <= 1) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
if (!isSingularJSXReturn(node)) {
|
|
202
|
-
context.report({
|
|
203
|
-
node,
|
|
204
|
-
messageId: "nestedArrowFragment"
|
|
181
|
+
messageId: "nestedFunctionJSX",
|
|
182
|
+
node
|
|
205
183
|
});
|
|
206
184
|
}
|
|
207
185
|
}
|
|
208
186
|
};
|
|
187
|
+
},
|
|
188
|
+
defaultOptions: [],
|
|
189
|
+
meta: {
|
|
190
|
+
docs: {
|
|
191
|
+
description:
|
|
192
|
+
"Disallow nested functions that return non-component, non-singular JSX to enforce one component per file"
|
|
193
|
+
},
|
|
194
|
+
messages: {
|
|
195
|
+
nestedArrowFragment:
|
|
196
|
+
"Nested arrow function returning a non-singular JSX fragment detected. Extract it into its own component.",
|
|
197
|
+
nestedArrowJSX:
|
|
198
|
+
"Nested arrow function returning non-component, non-singular JSX detected. Extract it into its own component.",
|
|
199
|
+
nestedFunctionJSX:
|
|
200
|
+
"Nested function returning non-component, non-singular JSX detected. Extract it into its own component."
|
|
201
|
+
},
|
|
202
|
+
schema: [],
|
|
203
|
+
type: "problem"
|
|
209
204
|
}
|
|
210
205
|
};
|
|
@@ -4,25 +4,10 @@ type Options = [];
|
|
|
4
4
|
type MessageIds = "useLogicalAnd";
|
|
5
5
|
|
|
6
6
|
export const noOrNoneComponent: TSESLint.RuleModule<MessageIds, Options> = {
|
|
7
|
-
meta: {
|
|
8
|
-
type: "suggestion",
|
|
9
|
-
docs: {
|
|
10
|
-
description:
|
|
11
|
-
"Prefer using logical && operator over ternary with null/undefined for conditional JSX rendering."
|
|
12
|
-
},
|
|
13
|
-
schema: [],
|
|
14
|
-
messages: {
|
|
15
|
-
useLogicalAnd:
|
|
16
|
-
"Prefer using the logical '&&' operator instead of a ternary with null/undefined for conditional rendering."
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
defaultOptions: [],
|
|
21
|
-
|
|
22
7
|
create(context) {
|
|
23
8
|
return {
|
|
24
9
|
ConditionalExpression(node: TSESTree.ConditionalExpression) {
|
|
25
|
-
const alternate = node
|
|
10
|
+
const { alternate } = node;
|
|
26
11
|
|
|
27
12
|
// Check if alternate is explicitly null or undefined
|
|
28
13
|
const isNullAlternate =
|
|
@@ -40,7 +25,7 @@ export const noOrNoneComponent: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
40
25
|
}
|
|
41
26
|
|
|
42
27
|
// Check if the node is within a JSX expression container.
|
|
43
|
-
const parent = node
|
|
28
|
+
const { parent } = node;
|
|
44
29
|
if (!parent || parent.type !== "JSXExpressionContainer") {
|
|
45
30
|
return;
|
|
46
31
|
}
|
|
@@ -53,12 +38,25 @@ export const noOrNoneComponent: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
53
38
|
containerParent.type !== "JSXAttribute"
|
|
54
39
|
) {
|
|
55
40
|
context.report({
|
|
56
|
-
|
|
57
|
-
|
|
41
|
+
messageId: "useLogicalAnd",
|
|
42
|
+
node
|
|
58
43
|
});
|
|
59
44
|
}
|
|
60
45
|
}
|
|
61
46
|
};
|
|
47
|
+
},
|
|
48
|
+
defaultOptions: [],
|
|
49
|
+
meta: {
|
|
50
|
+
docs: {
|
|
51
|
+
description:
|
|
52
|
+
"Prefer using logical && operator over ternary with null/undefined for conditional JSX rendering."
|
|
53
|
+
},
|
|
54
|
+
messages: {
|
|
55
|
+
useLogicalAnd:
|
|
56
|
+
"Prefer using the logical '&&' operator instead of a ternary with null/undefined for conditional rendering."
|
|
57
|
+
},
|
|
58
|
+
schema: [],
|
|
59
|
+
type: "suggestion"
|
|
62
60
|
}
|
|
63
61
|
};
|
|
64
62
|
|
|
@@ -13,27 +13,62 @@ import { TSESLint, TSESTree } from "@typescript-eslint/utils";
|
|
|
13
13
|
type Options = [];
|
|
14
14
|
type MessageIds = "forbiddenTransition";
|
|
15
15
|
|
|
16
|
+
const getKeyName = (prop: TSESTree.Property) => {
|
|
17
|
+
if (prop.key.type === "Identifier") {
|
|
18
|
+
return prop.key.name;
|
|
19
|
+
}
|
|
20
|
+
if (prop.key.type !== "Literal") {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return typeof prop.key.value === "string"
|
|
24
|
+
? prop.key.value
|
|
25
|
+
: String(prop.key.value);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const checkPropForTransition = (
|
|
29
|
+
context: TSESLint.RuleContext<MessageIds, Options>,
|
|
30
|
+
prop: TSESTree.Property
|
|
31
|
+
) => {
|
|
32
|
+
if (prop.computed) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const keyName = getKeyName(prop);
|
|
36
|
+
if (keyName === "transition") {
|
|
37
|
+
context.report({
|
|
38
|
+
messageId: "forbiddenTransition",
|
|
39
|
+
node: prop
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
16
44
|
export const noTransitionCSSProperties: TSESLint.RuleModule<
|
|
17
45
|
MessageIds,
|
|
18
46
|
Options
|
|
19
47
|
> = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
docs: {
|
|
23
|
-
description:
|
|
24
|
-
"Objects typed as CSSProperties must not include a 'transition' property as it conflicts with react-spring."
|
|
25
|
-
},
|
|
26
|
-
schema: [], // no options
|
|
27
|
-
messages: {
|
|
28
|
-
forbiddenTransition:
|
|
29
|
-
"Objects typed as CSSProperties must not include a 'transition' property as it conflicts with react-spring."
|
|
30
|
-
}
|
|
31
|
-
},
|
|
48
|
+
create(context) {
|
|
49
|
+
const { sourceCode } = context;
|
|
32
50
|
|
|
33
|
-
|
|
51
|
+
const isCSSPropertiesType = (typeAnnotation: TSESTree.TypeNode) => {
|
|
52
|
+
if (typeAnnotation.type !== "TSTypeReference") {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
34
55
|
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
const { typeName } = typeAnnotation;
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
typeName.type === "Identifier" &&
|
|
60
|
+
typeName.name === "CSSProperties"
|
|
61
|
+
) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
typeName.type === "TSQualifiedName" &&
|
|
67
|
+
typeName.right &&
|
|
68
|
+
typeName.right.type === "Identifier" &&
|
|
69
|
+
typeName.right.name === "CSSProperties"
|
|
70
|
+
);
|
|
71
|
+
};
|
|
37
72
|
|
|
38
73
|
return {
|
|
39
74
|
VariableDeclarator(node: TSESTree.VariableDeclarator) {
|
|
@@ -46,39 +81,17 @@ export const noTransitionCSSProperties: TSESLint.RuleModule<
|
|
|
46
81
|
return;
|
|
47
82
|
}
|
|
48
83
|
|
|
49
|
-
|
|
50
|
-
const typeAnnotation = node.id.typeAnnotation.typeAnnotation;
|
|
84
|
+
const { typeAnnotation } = node.id.typeAnnotation;
|
|
51
85
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
typeAnnotation &&
|
|
55
|
-
typeAnnotation.type === "TSTypeReference"
|
|
56
|
-
) {
|
|
57
|
-
const typeName = typeAnnotation.typeName;
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
typeName.type === "Identifier" &&
|
|
61
|
-
typeName.name === "CSSProperties"
|
|
62
|
-
) {
|
|
63
|
-
isStyleType = true;
|
|
64
|
-
} else if (
|
|
65
|
-
typeName.type === "TSQualifiedName" &&
|
|
66
|
-
typeName.right &&
|
|
67
|
-
typeName.right.type === "Identifier" &&
|
|
68
|
-
typeName.right.name === "CSSProperties"
|
|
69
|
-
) {
|
|
70
|
-
isStyleType = true;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
86
|
+
// Check if the type annotation is CSSProperties
|
|
87
|
+
let isStyleType = isCSSPropertiesType(typeAnnotation);
|
|
73
88
|
|
|
74
89
|
// Fallback: if the AST shape doesn't match, check the raw text of the annotation.
|
|
75
90
|
if (!isStyleType) {
|
|
76
91
|
const annotationText = sourceCode.getText(
|
|
77
92
|
node.id.typeAnnotation
|
|
78
93
|
);
|
|
79
|
-
|
|
80
|
-
isStyleType = true;
|
|
81
|
-
}
|
|
94
|
+
isStyleType = annotationText.includes("CSSProperties");
|
|
82
95
|
}
|
|
83
96
|
|
|
84
97
|
if (!isStyleType) {
|
|
@@ -86,40 +99,33 @@ export const noTransitionCSSProperties: TSESLint.RuleModule<
|
|
|
86
99
|
}
|
|
87
100
|
|
|
88
101
|
// Check that the initializer is an object literal.
|
|
89
|
-
const init = node
|
|
102
|
+
const { init } = node;
|
|
90
103
|
if (!init || init.type !== "ObjectExpression") {
|
|
91
104
|
return;
|
|
92
105
|
}
|
|
93
106
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
let keyName: string | null = null;
|
|
104
|
-
|
|
105
|
-
if (prop.key.type === "Identifier") {
|
|
106
|
-
keyName = prop.key.name;
|
|
107
|
-
} else if (prop.key.type === "Literal") {
|
|
108
|
-
if (typeof prop.key.value === "string") {
|
|
109
|
-
keyName = prop.key.value;
|
|
110
|
-
} else {
|
|
111
|
-
keyName = String(prop.key.value);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (keyName === "transition") {
|
|
116
|
-
context.report({
|
|
117
|
-
node: prop,
|
|
118
|
-
messageId: "forbiddenTransition"
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
107
|
+
const properties = init.properties.filter(
|
|
108
|
+
(prop): prop is TSESTree.Property =>
|
|
109
|
+
prop.type === "Property"
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
properties.forEach((prop) => {
|
|
113
|
+
checkPropForTransition(context, prop);
|
|
114
|
+
});
|
|
122
115
|
}
|
|
123
116
|
};
|
|
117
|
+
},
|
|
118
|
+
defaultOptions: [],
|
|
119
|
+
meta: {
|
|
120
|
+
docs: {
|
|
121
|
+
description:
|
|
122
|
+
"Objects typed as CSSProperties must not include a 'transition' property as it conflicts with react-spring."
|
|
123
|
+
},
|
|
124
|
+
messages: {
|
|
125
|
+
forbiddenTransition:
|
|
126
|
+
"Objects typed as CSSProperties must not include a 'transition' property as it conflicts with react-spring."
|
|
127
|
+
},
|
|
128
|
+
schema: [], // no options,
|
|
129
|
+
type: "problem"
|
|
124
130
|
}
|
|
125
131
|
};
|
|
@@ -4,45 +4,24 @@ type Options = [];
|
|
|
4
4
|
type MessageIds = "unnecessaryDivWrapper";
|
|
5
5
|
|
|
6
6
|
export const noUnnecessaryDiv: TSESLint.RuleModule<MessageIds, Options> = {
|
|
7
|
-
meta: {
|
|
8
|
-
type: "suggestion",
|
|
9
|
-
docs: {
|
|
10
|
-
description:
|
|
11
|
-
"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."
|
|
12
|
-
},
|
|
13
|
-
schema: [],
|
|
14
|
-
messages: {
|
|
15
|
-
unnecessaryDivWrapper:
|
|
16
|
-
"Unnecessary <div> wrapper detected. Remove it if not needed, or replace with a semantic element that reflects its purpose."
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
defaultOptions: [],
|
|
21
|
-
|
|
22
7
|
create(context) {
|
|
23
|
-
|
|
8
|
+
const isDivElement = (node: TSESTree.JSXElement) => {
|
|
24
9
|
const nameNode = node.openingElement.name;
|
|
25
10
|
return (
|
|
26
11
|
nameNode.type === AST_NODE_TYPES.JSXIdentifier &&
|
|
27
12
|
nameNode.name === "div"
|
|
28
13
|
);
|
|
29
|
-
}
|
|
14
|
+
};
|
|
30
15
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const result: TSESTree.JSXChild[] = [];
|
|
35
|
-
for (const child of node.children) {
|
|
36
|
-
if (child.type === AST_NODE_TYPES.JSXText) {
|
|
37
|
-
if (child.value.trim() !== "") {
|
|
38
|
-
result.push(child);
|
|
39
|
-
}
|
|
40
|
-
} else {
|
|
41
|
-
result.push(child);
|
|
42
|
-
}
|
|
16
|
+
const isMeaningfulChild = (child: TSESTree.JSXChild) => {
|
|
17
|
+
if (child.type === AST_NODE_TYPES.JSXText) {
|
|
18
|
+
return child.value.trim() !== "";
|
|
43
19
|
}
|
|
44
|
-
return
|
|
45
|
-
}
|
|
20
|
+
return true;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getMeaningfulChildren = (node: TSESTree.JSXElement) =>
|
|
24
|
+
node.children.filter(isMeaningfulChild);
|
|
46
25
|
|
|
47
26
|
return {
|
|
48
27
|
JSXElement(node: TSESTree.JSXElement) {
|
|
@@ -56,18 +35,31 @@ export const noUnnecessaryDiv: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
56
35
|
return;
|
|
57
36
|
}
|
|
58
37
|
|
|
59
|
-
const onlyChild = meaningfulChildren
|
|
38
|
+
const [onlyChild] = meaningfulChildren;
|
|
60
39
|
if (!onlyChild) {
|
|
61
40
|
return;
|
|
62
41
|
}
|
|
63
42
|
|
|
64
43
|
if (onlyChild.type === AST_NODE_TYPES.JSXElement) {
|
|
65
44
|
context.report({
|
|
66
|
-
|
|
67
|
-
|
|
45
|
+
messageId: "unnecessaryDivWrapper",
|
|
46
|
+
node
|
|
68
47
|
});
|
|
69
48
|
}
|
|
70
49
|
}
|
|
71
50
|
};
|
|
51
|
+
},
|
|
52
|
+
defaultOptions: [],
|
|
53
|
+
meta: {
|
|
54
|
+
docs: {
|
|
55
|
+
description:
|
|
56
|
+
"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."
|
|
57
|
+
},
|
|
58
|
+
messages: {
|
|
59
|
+
unnecessaryDivWrapper:
|
|
60
|
+
"Unnecessary <div> wrapper detected. Remove it if not needed, or replace with a semantic element that reflects its purpose."
|
|
61
|
+
},
|
|
62
|
+
schema: [],
|
|
63
|
+
type: "suggestion"
|
|
72
64
|
}
|
|
73
65
|
};
|