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.
Files changed (49) hide show
  1. package/.absolutejs/eslint.cache.json +49 -0
  2. package/.absolutejs/prettier.cache.json +49 -0
  3. package/.absolutejs/tsconfig.tsbuildinfo +1 -0
  4. package/.claude/settings.local.json +10 -0
  5. package/dist/index.js +1787 -1457
  6. package/eslint.config.mjs +107 -0
  7. package/package.json +15 -12
  8. package/src/index.ts +45 -0
  9. package/src/rules/explicit-object-types.ts +75 -0
  10. package/src/rules/inline-style-limit.ts +88 -0
  11. package/src/rules/localize-react-props.ts +454 -0
  12. package/src/rules/max-depth-extended.ts +153 -0
  13. package/src/rules/{max-jsx-nesting.js → max-jsx-nesting.ts} +37 -38
  14. package/src/rules/min-var-length.ts +360 -0
  15. package/src/rules/no-button-navigation.ts +270 -0
  16. package/src/rules/no-explicit-return-types.ts +83 -0
  17. package/src/rules/no-inline-prop-types.ts +68 -0
  18. package/src/rules/no-multi-style-objects.ts +80 -0
  19. package/src/rules/no-nested-jsx-return.ts +205 -0
  20. package/src/rules/no-or-none-component.ts +63 -0
  21. package/src/rules/no-transition-cssproperties.ts +131 -0
  22. package/src/rules/no-unnecessary-div.ts +65 -0
  23. package/src/rules/no-unnecessary-key.ts +111 -0
  24. package/src/rules/no-useless-function.ts +56 -0
  25. package/src/rules/seperate-style-files.ts +79 -0
  26. package/src/rules/sort-exports.ts +424 -0
  27. package/src/rules/sort-keys-fixable.ts +647 -0
  28. package/src/rules/spring-naming-convention.ts +160 -0
  29. package/tsconfig.json +4 -1
  30. package/src/index.js +0 -45
  31. package/src/rules/explicit-object-types.js +0 -54
  32. package/src/rules/inline-style-limit.js +0 -77
  33. package/src/rules/localize-react-props.js +0 -418
  34. package/src/rules/max-depth-extended.js +0 -124
  35. package/src/rules/min-var-length.js +0 -300
  36. package/src/rules/no-button-navigation.js +0 -232
  37. package/src/rules/no-explicit-return-types.js +0 -64
  38. package/src/rules/no-inline-prop-types.js +0 -55
  39. package/src/rules/no-multi-style-objects.js +0 -70
  40. package/src/rules/no-nested-jsx-return.js +0 -154
  41. package/src/rules/no-or-none-component.js +0 -50
  42. package/src/rules/no-transition-cssproperties.js +0 -102
  43. package/src/rules/no-unnecessary-div.js +0 -40
  44. package/src/rules/no-unnecessary-key.js +0 -128
  45. package/src/rules/no-useless-function.js +0 -43
  46. package/src/rules/seperate-style-files.js +0 -62
  47. package/src/rules/sort-exports.js +0 -397
  48. package/src/rules/sort-keys-fixable.js +0 -459
  49. 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.js
3
- var no_nested_jsx_return_default = {
4
- meta: {
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
- function isJSX(node) {
14
- return node && (node.type === "JSXElement" || node.type === "JSXFragment");
15
- }
16
- function isJSXComponentElement(node) {
17
- if (node && node.type === "JSXElement") {
18
- const opening = node.openingElement;
19
- if (opening && opening.name) {
20
- if (opening.name.type === "JSXIdentifier") {
21
- return /^[A-Z]/.test(opening.name.name);
22
- }
23
- if (opening.name.type === "JSXMemberExpression") {
24
- let current = opening.name;
25
- while (current && current.type === "JSXMemberExpression") {
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
- return false;
34
- }
35
- function isSingularJSXReturn(node) {
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
- let children = [];
39
- if (node.type === "JSXElement" || node.type === "JSXFragment") {
40
- children = node.children.filter((child) => {
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 children.length === 0;
48
+ return true;
49
+ });
50
+ if (children.length === 0) {
51
+ return true;
60
52
  }
61
- return false;
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
- function pushFunction(node) {
66
+ const pushFunction = (node) => {
65
67
  functionStack.push(node);
66
- }
67
- function popFunction() {
68
+ };
69
+ const popFunction = () => {
68
70
  functionStack.pop();
69
- }
71
+ };
70
72
  return {
71
- "FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(node) {
72
- pushFunction(node);
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
- "FunctionDeclaration:exit"(node) {
95
+ "ArrowFunctionExpression:exit"() {
75
96
  popFunction();
76
97
  },
77
- "FunctionExpression:exit"(node) {
98
+ "FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(node) {
99
+ pushFunction(node);
100
+ },
101
+ "FunctionDeclaration:exit"() {
78
102
  popFunction();
79
103
  },
80
- "ArrowFunctionExpression:exit"(node) {
104
+ "FunctionExpression:exit"() {
81
105
  popFunction();
82
106
  },
83
107
  ReturnStatement(node) {
84
- if (functionStack.length > 1 && isJSX(node.argument) && !isJSXComponentElement(node.argument) && !isSingularJSXReturn(node.argument)) {
85
- context.report({
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
- "ArrowFunctionExpression > JSXElement"(node) {
92
- if (functionStack.length > 1 && !isJSXComponentElement(node) && !isSingularJSXReturn(node)) {
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
- node,
103
- message: "Nested arrow function returning a non-singular JSX fragment detected. Extract it into its own component."
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: "Require explicit type annotations for object literals and arrays of object literals",
117
- recommended: false
127
+ description: "Disallow nested functions that return non-component, non-singular JSX to enforce one component per file"
118
128
  },
119
- schema: []
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
- function isObjectLiteral(node) {
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
- node: node.id,
134
- message: "Object literal must have an explicit type annotation."
151
+ messageId: "objectLiteralNeedsType",
152
+ node: node.id
135
153
  });
136
154
  return;
137
155
  }
138
- if (node.init.type === "ArrayExpression") {
139
- const hasObjectLiteral = node.init.elements.some((element) => element && isObjectLiteral(element));
140
- if (hasObjectLiteral) {
141
- context.report({
142
- node: node.id,
143
- message: "Array of object literals must have an explicit type annotation."
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: "enforce sorted keys in object literals with auto-fix (limited to simple cases, preserving comments)",
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
- unsorted: "Object keys are not sorted."
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.getSourceCode();
191
- const options = context.options[0] || {};
192
- const order = options.order || "asc";
193
- const caseSensitive = options.caseSensitive !== undefined ? options.caseSensitive : false;
194
- const natural = options.natural !== undefined ? options.natural : false;
195
- const minKeys = options.minKeys !== undefined ? options.minKeys : 2;
196
- const variablesBeforeFunctions = options.variablesBeforeFunctions !== undefined ? options.variablesBeforeFunctions : false;
197
- function compareKeys(a, b) {
198
- let keyA = a;
199
- let keyB = b;
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
- keyA = keyA.toLowerCase();
202
- keyB = keyB.toLowerCase();
202
+ left = left.toLowerCase();
203
+ right = right.toLowerCase();
203
204
  }
204
205
  if (natural) {
205
- return keyA.localeCompare(keyB, undefined, { numeric: true });
206
+ return left.localeCompare(right, undefined, {
207
+ numeric: true
208
+ });
206
209
  }
207
- return keyA.localeCompare(keyB);
208
- }
209
- function isFunctionProperty(prop) {
210
- return prop.value && (prop.value.type === "FunctionExpression" || prop.value.type === "ArrowFunctionExpression" || prop.method === true);
211
- }
212
- function buildSortedText(fixableProps) {
213
- const sorted = fixableProps.slice().sort((a, b) => {
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 aIsFunc = isFunctionProperty(a);
216
- const bIsFunc = isFunctionProperty(b);
217
- if (aIsFunc !== bIsFunc) {
218
- return aIsFunc ? 1 : -1;
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 keyA = a.key.type === "Identifier" ? a.key.name : String(a.key.value);
222
- const keyB = b.key.type === "Identifier" ? b.key.name : String(b.key.value);
223
- let res = compareKeys(keyA, keyB);
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
- return sorted.map((prop) => {
230
- const leadingComments = sourceCode.getCommentsBefore(prop) || [];
231
- const trailingComments = sourceCode.getCommentsAfter(prop) || [];
232
- const leadingText = leadingComments.length > 0 ? leadingComments.map((comment) => sourceCode.getText(comment)).join(`
233
- `) + `
234
- ` : "";
235
- const trailingText = trailingComments.length > 0 ? `
236
- ` + trailingComments.map((comment) => sourceCode.getText(comment)).join(`
237
- `) : "";
238
- return leadingText + sourceCode.getText(prop) + trailingText;
239
- }).join(", ");
240
- }
241
- function checkObjectExpression(node) {
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 === "Property") {
250
- if (prop.computed) {
251
- autoFixable = false;
252
- }
253
- if (prop.key.type === "Identifier") {
254
- keyName = prop.key.name;
255
- } else if (prop.key.type === "Literal") {
256
- keyName = String(prop.key.value);
257
- } else {
258
- autoFixable = false;
259
- }
260
- if (prop.value) {
261
- if (prop.value.type === "FunctionExpression" || prop.value.type === "ArrowFunctionExpression" || prop.method === true) {
262
- isFunc = true;
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
- return { keyName, node: prop, isFunction: isFunc };
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
- for (let i = 1;i < keys.length; i++) {
272
- const prev = keys[i - 1];
273
- const curr = keys[i];
274
- if (prev.keyName === null || curr.keyName === null) {
275
- continue;
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
- } else {
321
- if (compareKeys(prev.keyName, curr.keyName) > 0) {
322
- context.report({
323
- node: curr.node.key,
324
- messageId: "unsorted",
325
- fix: !fixProvided && autoFixable ? (fixer) => {
326
- const fixableProps = node.properties.filter((prop) => prop.type === "Property" && !prop.computed && (prop.key.type === "Identifier" || prop.key.type === "Literal") || false);
327
- if (fixableProps.length < minKeys) {
328
- return null;
329
- }
330
- const sortedText = buildSortedText(fixableProps);
331
- const firstProp = fixableProps[0];
332
- const lastProp = fixableProps[fixableProps.length - 1];
333
- return fixer.replaceTextRange([
334
- firstProp.range[0],
335
- lastProp.range[1]
336
- ], sortedText);
337
- } : null
338
- });
339
- fixProvided = true;
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
- function checkJSXAttributeObject(attr) {
345
- if (attr.value && attr.value.type === "JSXExpressionContainer" && attr.value.expression && attr.value.expression.type === "ObjectExpression") {
346
- checkObjectExpression(attr.value.expression);
408
+ };
409
+ const getAttrName = (attr) => {
410
+ if (attr.type !== "JSXAttribute" || attr.name.type !== "JSXIdentifier") {
411
+ return "";
347
412
  }
348
- }
349
- function checkJSXOpeningElement(node) {
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
- if (attrs.some((a) => a.type !== "JSXAttribute"))
433
+ }
434
+ if (attrs.some((attr) => attr.type !== "JSXAttribute")) {
354
435
  return;
355
- if (attrs.some((a) => a.name.type !== "JSXIdentifier"))
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
- if (!outOfOrder)
440
+ const names = attrs.map((attr) => getAttrName(attr));
441
+ if (!isOutOfOrder(names)) {
370
442
  return;
371
- for (let i = 1;i < attrs.length; i++) {
372
- const between = sourceCode.text.slice(attrs[i - 1].range[1], attrs[i].range[0]);
373
- if (between.includes("{")) {
374
- context.report({
375
- node: attrs[i].name,
376
- messageId: "unsorted"
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 sorted = attrs.slice().sort((a, b) => cmp(a.name.name, b.name.name));
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([first.range[0], last.range[1]], replacement);
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
- create(context) {
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: "Disallow explicit return type annotations on functions, except when using type predicates for type guards or inline object literal returns (e.g., style objects).",
475
- recommended: false
488
+ description: "enforce sorted keys in object literals with auto-fix (limited to simple cases, preserving comments)"
476
489
  },
477
- schema: [],
490
+ fixable: "code",
478
491
  messages: {
479
- noExplicitReturnType: "Explicit return types are disallowed; rely on TypeScript's inference instead."
480
- }
481
- },
482
- create(context) {
483
- return {
484
- "FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(node) {
485
- if (node.returnType) {
486
- const typeAnnotation = node.returnType.typeAnnotation;
487
- if (typeAnnotation && typeAnnotation.type === "TSTypePredicate") {
488
- return;
489
- }
490
- if (node.type === "ArrowFunctionExpression" && node.expression && node.body && node.body.type === "ObjectExpression") {
491
- return;
492
- }
493
- if (node.body && node.body.type === "BlockStatement") {
494
- const returns = node.body.body.filter((stmt) => stmt.type === "ReturnStatement");
495
- if (returns.length === 1 && returns[0].argument && returns[0].argument.type === "ObjectExpression") {
496
- return;
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
- context.report({
500
- node: node.returnType,
501
- messageId: "noExplicitReturnType"
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/max-jsx-nesting.js
510
- var max_jsx_nesting_default = {
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: "Warn when JSX elements are nested too deeply, suggesting refactoring into a separate component.",
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
- tooDeeplyNested: "JSX element is nested too deeply ({{level}} levels, allowed is {{maxAllowed}} levels). Consider refactoring into a separate component."
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 maxAllowed = context.options[0];
529
- function getJSXNestingLevel(node) {
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
- if (current.type === "JSXElement" || current.type === "JSXFragment") {
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
- node,
663
+ data: { level, maxAllowed },
546
664
  messageId: "tooDeeplyNested",
547
- data: { level, maxAllowed }
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 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.",
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
- moveToFile: 'Style object "{{name}}" is typed as {{typeName}}. Move it to its own file under the style folder.'
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.getFilename();
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
- if (!identifier.typeAnnotation)
702
+ const idTypeAnnotation = identifier.typeAnnotation;
703
+ if (!idTypeAnnotation || idTypeAnnotation.type !== "TSTypeAnnotation") {
579
704
  return;
580
- const typeNode = identifier.typeAnnotation.typeAnnotation;
581
- if (typeNode.type === "TSTypeReference" && typeNode.typeName) {
582
- let typeName = null;
583
- if (typeNode.typeName.type === "Identifier") {
584
- typeName = typeNode.typeName.name;
585
- } else if (typeNode.typeName.type === "TSQualifiedName") {
586
- typeName = typeNode.typeName.right.name;
587
- }
588
- if (typeName === "CSSProperties") {
589
- context.report({
590
- node,
591
- messageId: "moveToFile",
592
- data: {
593
- name: identifier.name,
594
- typeName
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: "enforce that the key prop is only used on components rendered as part of a mapping",
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
- unnecessaryKey: "The key prop should only be used on elements that are directly rendered as part of an array mapping."
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
- function getAncestors(node) {
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
- function isInsideMapCall(ancestors) {
628
- return ancestors.some((node) => {
629
- if (node.type === "CallExpression" && node.callee && node.callee.type === "MemberExpression") {
630
- const property = node.callee.property;
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 = typeof context.getAncestors === "function" ? context.getAncestors() : getAncestors(node);
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
- node: keyAttribute,
653
- messageId: "unnecessaryKey"
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: "Enforce that top-level export declarations are sorted by exported name and, optionally, that variable exports come before function exports",
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
- alphabetical: "Export declarations are not sorted alphabetically. Expected order: {{expectedOrder}}.",
698
- variablesBeforeFunctions: "Non-function exports should come before function exports."
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.getSourceCode();
703
- const options = context.options[0] || {};
704
- const order = options.order || "asc";
705
- const caseSensitive = options.caseSensitive !== undefined ? options.caseSensitive : false;
706
- const natural = options.natural !== undefined ? options.natural : false;
707
- const minKeys = options.minKeys !== undefined ? options.minKeys : 2;
708
- const variablesBeforeFunctions = options.variablesBeforeFunctions !== undefined ? options.variablesBeforeFunctions : false;
709
- function generateExportText(node) {
710
- return sourceCode.getText(node).trim().replace(/\s*;?\s*$/, ";");
711
- }
712
- function compareStrings(a, b) {
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
- strA = strA.toLowerCase();
717
- strB = strB.toLowerCase();
865
+ left = left.toLowerCase();
866
+ right = right.toLowerCase();
718
867
  }
719
- let cmp = natural ? strA.localeCompare(strB, undefined, { numeric: true }) : strA.localeCompare(strB);
868
+ const cmp = natural ? left.localeCompare(right, undefined, { numeric: true }) : left.localeCompare(right);
720
869
  return order === "asc" ? cmp : -cmp;
721
- }
722
- function getExportName(node) {
723
- if (node.declaration) {
724
- const decl = node.declaration;
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
- return false;
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 (variablesBeforeFunctions) {
765
- if (a.isFunction !== b.isFunction) {
766
- return a.isFunction ? 1 : -1;
767
- }
879
+ if (declaration.type !== "VariableDeclaration") {
880
+ return false;
768
881
  }
769
- return compareStrings(a.name, b.name);
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
- if (visited.has(node)) {
885
+ const [firstDeclarator] = declaration.declarations;
886
+ if (!firstDeclarator) {
776
887
  return false;
777
888
  }
778
- visited.add(node);
779
- if (node.type === "Identifier" && laterNames.has(node.name)) {
780
- return true;
889
+ const { init } = firstDeclarator;
890
+ if (!init) {
891
+ return false;
781
892
  }
782
- for (const key in node) {
783
- if (Object.prototype.hasOwnProperty.call(node, key)) {
784
- const value = node[key];
785
- if (Array.isArray(value)) {
786
- for (const element of value) {
787
- if (element && typeof element === "object") {
788
- if (hasForwardDependency(element, laterNames, visited)) {
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
- return false;
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
- const items = block.map((node) => {
807
- const name = getExportName(node);
808
- if (name === null) {
809
- return null;
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
- const sortedItems = items.slice().sort(sortComparator);
822
- let reportNeeded = false;
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
- for (let i = 1;i < items.length; i++) {
825
- if (sortComparator(items[i - 1], items[i]) > 0) {
826
- reportNeeded = true;
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
- if (!reportNeeded) {
834
- return;
835
- }
836
- const exportNames = items.map((item) => item.name);
837
- for (let i = 0;i < items.length; i++) {
838
- const laterNames = new Set(exportNames.slice(i + 1));
839
- const nodeToCheck = items[i].node.declaration || items[i].node;
840
- if (hasForwardDependency(nodeToCheck, laterNames)) {
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((n) => {
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 first = block[0].range[0];
873
- const last = block[block.length - 1].range[1];
874
- const originalText = sourceCode.getText().slice(first, last);
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([first, last], sortedText);
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.body;
885
- let block = [];
886
- for (let i = 0;i < body.length; i++) {
887
- const n = body[i];
888
- if (n.type === "ExportNamedDeclaration" && !n.source && getExportName(n) !== null) {
889
- block.push(n);
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
- if (block.length) {
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.js
906
- var localize_react_props_default = {
907
- meta: {
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
- function getJSXElementName(jsxElement) {
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 === "JSXIdentifier") {
1097
+ if (nameNode.type === AST_NODE_TYPES2.JSXIdentifier) {
923
1098
  return nameNode.name;
924
1099
  }
925
- if (nameNode.type === "JSXMemberExpression") {
926
- let current = nameNode;
927
- while (current.property) {
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
- function isUseStateCall(node) {
937
- return node && node.type === "CallExpression" && node.callee && (node.callee.type === "Identifier" && node.callee.name === "useState" || node.callee.type === "MemberExpression" && node.callee.property && node.callee.property.name === "useState");
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 === "JSXElement") {
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
- function isContextProviderValueProp(node) {
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.type === "JSXAttribute" && current.name && current.name.name === "value") {
956
- if (current.parent && current.parent.type === "JSXOpeningElement") {
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
- function isCustomJSXElement(jsxElement) {
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 === "JSXIdentifier") {
1146
+ if (nameNode.type === AST_NODE_TYPES2.JSXIdentifier) {
986
1147
  return /^[A-Z]/.test(nameNode.name);
987
1148
  }
988
- if (nameNode.type === "JSXMemberExpression") {
989
- let current = nameNode;
990
- while (current.object) {
991
- current = current.object;
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 === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
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
- function analyzeVariableUsage(declarationNode, varName, componentFunction) {
1008
- const usage = { jsxUsageSet: new Set, hasOutsideUsage: false };
1009
- const sourceCode = context.getSourceCode();
1010
- const visitorKeys = sourceCode.visitorKeys || {};
1011
- const stack = [];
1012
- if (componentFunction.body.type === "BlockStatement") {
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 usage;
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
- function getHookSet(componentFunction) {
1067
- if (!componentHookVars.has(componentFunction)) {
1068
- componentHookVars.set(componentFunction, new Set);
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 componentHookVars.get(componentFunction);
1071
- }
1072
- function hasHookDependency(node, hookSet) {
1073
- const sourceCode = context.getSourceCode();
1074
- const visitorKeys = sourceCode.visitorKeys || {};
1075
- const stack = [node];
1076
- while (stack.length) {
1077
- const currentNode = stack.pop();
1078
- if (!currentNode)
1079
- continue;
1080
- if (currentNode.type === "Identifier") {
1081
- if (hookSet.has(currentNode.name)) {
1082
- return true;
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
- return {
1103
- VariableDeclarator(node) {
1104
- const componentFunction = getComponentFunction(node);
1105
- if (!componentFunction || !componentFunction.body)
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
- if (!groups.has(key)) {
1155
- groups.set(key, []);
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 === 1) {
1161
- const candidate = candidates[0];
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: "Prefer using logical && operator over ternary with null/undefined for conditional JSX rendering.",
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
- useLogicalAnd: "Prefer using the logical '&&' operator instead of a ternary with null/undefined for conditional rendering."
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.alternate;
1190
- if (alternate && (alternate.type === "Literal" && alternate.value === null || alternate.type === "Identifier" && alternate.name === "undefined")) {
1191
- if (node.parent && node.parent.type === "JSXExpressionContainer") {
1192
- const containerParent = node.parent.parent;
1193
- if (containerParent && containerParent.type !== "JSXAttribute") {
1194
- context.report({
1195
- node,
1196
- messageId: "useLogicalAnd"
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: "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).",
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
- schema: []
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
- function urlUsesAllowedLocation(argNode) {
1219
- let allowed = false;
1220
- const visited = new WeakSet;
1221
- function check(n) {
1222
- if (allowed || !n || typeof n !== "object" || visited.has(n))
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
- visited.add(n);
1225
- if (n.type === "MemberExpression" && n.object.type === "MemberExpression" && n.object.object.type === "Identifier" && n.object.object.name === "window" && n.object.property.type === "Identifier" && n.object.property.name === "location" && n.property.type === "Identifier" && (n.property.name === "pathname" || n.property.name === "search" || n.property.name === "hash")) {
1226
- allowed = true;
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
- for (const key of Object.keys(n)) {
1230
- if (key === "parent")
1231
- continue;
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
- check(argNode);
1241
- return allowed;
1242
- }
1243
- function containsWindowNavigation(node) {
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
- visited.add(n);
1252
- if (n.type === "MemberExpression" && n.object.type === "Identifier" && n.object.name === "window" && n.property.type === "Identifier" && n.property.name === "open") {
1253
- reason = "window.open";
1470
+ }
1471
+ if (node.left.type !== "MemberExpression") {
1254
1472
  return;
1255
1473
  }
1256
- if (n.type === "AssignmentExpression" && n.left.type === "MemberExpression") {
1257
- const left = n.left;
1258
- if (left.object.type === "Identifier" && left.object.name === "window" && left.property.type === "Identifier" && left.property.name === "location") {
1259
- reason = "assignment to window.location";
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 (n.type === "MemberExpression" && n.object.type === "MemberExpression" && n.object.object.type === "Identifier" && n.object.object.name === "window" && n.object.property.type === "Identifier" && n.object.property.name === "location" && n.property.type === "Identifier" && n.property.name === "replace") {
1268
- if (parent && parent.type === "CallExpression") {
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
- if (n.type === "MemberExpression" && n.object.type === "MemberExpression" && n.object.object.type === "Identifier" && n.object.object.name === "window" && n.object.property.type === "Identifier" && n.object.property.name === "history" && n.property.type === "Identifier" && (n.property.name === "pushState" || n.property.name === "replaceState")) {
1274
- sawReplaceCall = true;
1482
+ },
1483
+ CallExpression(node) {
1484
+ const state = getCurrentHandler();
1485
+ if (!state) {
1486
+ return;
1275
1487
  }
1276
- if (n.type === "MemberExpression" && n.object.type === "MemberExpression" && n.object.object.type === "Identifier" && n.object.object.name === "window" && n.object.property.type === "Identifier" && n.object.property.name === "location" && n.property.type === "Identifier" && (n.property.name === "search" || n.property.name === "pathname" || n.property.name === "hash")) {
1277
- sawAllowedLocationRead = true;
1488
+ const { callee } = node;
1489
+ if (callee.type !== "MemberExpression") {
1490
+ return;
1278
1491
  }
1279
- for (const key of Object.keys(n)) {
1280
- if (key === "parent" || reason)
1281
- continue;
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
- inspect(node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" ? node.body : node, null);
1293
- if (reason) {
1294
- return { shouldReport: true, reason };
1295
- }
1296
- if (sawReplaceCall && !sawAllowedLocationRead) {
1297
- return {
1298
- shouldReport: true,
1299
- reason: "history.replace/pushState without reading window.location"
1300
- };
1301
- }
1302
- return { shouldReport: false, reason: null };
1303
- }
1304
- return {
1305
- JSXElement(node) {
1306
- const { openingElement } = node;
1307
- if (openingElement.name.type === "JSXIdentifier" && openingElement.name.name === "button") {
1308
- for (const attr of openingElement.attributes) {
1309
- if (attr.type === "JSXAttribute" && attr.name.name === "onClick" && attr.value?.type === "JSXExpressionContainer") {
1310
- const expr = attr.value.expression;
1311
- if (expr.type === "ArrowFunctionExpression" || expr.type === "FunctionExpression") {
1312
- const { shouldReport, reason } = containsWindowNavigation(expr);
1313
- if (shouldReport) {
1314
- context.report({
1315
- node: attr,
1316
- message: `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.`
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: "Disallow grouping CSS style objects in a single export; export each style separately.",
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
- schema: []
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
- function checkObjectExpression(node) {
1341
- if (node.properties && node.properties.length > 0) {
1342
- const cssStyleProperties = node.properties.filter((prop) => {
1343
- if (prop.key) {
1344
- if (prop.key.type === "Identifier") {
1345
- return prop.key.name.endsWith("Style");
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
- if (node.declaration && node.declaration.type === "ObjectExpression") {
1364
- checkObjectExpression(node.declaration);
1583
+ const { declaration } = node;
1584
+ if (declaration && declaration.type === "ObjectExpression") {
1585
+ checkObjectExpression(declaration);
1365
1586
  }
1366
1587
  },
1367
1588
  ReturnStatement(node) {
1368
- if (node.argument && node.argument.type === "ObjectExpression") {
1369
- checkObjectExpression(node.argument);
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 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).",
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
- fixable: null
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
- function isCallbackFunction(node) {
1389
- return node.parent && node.parent.type === "CallExpression" && node.parent.arguments.includes(node);
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
- node,
1399
- message: "This function has no parameters and simply returns an object. Consider exporting the object directly instead of wrapping it in a function."
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 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.",
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
- variableNameTooShort: "Variable '{{name}}' is too short. Minimum allowed length is {{minLength}} characters unless an outer variable with a longer name starting with '{{name}}' exists."
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.getSourceCode();
1441
- const options = context.options[0] || {};
1442
- const minLength = typeof options.minLength === "number" ? options.minLength : 1;
1443
- const allowedVars = options.allowedVars || [];
1444
- function getAncestors(node) {
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 identifiers;
1504
- }
1505
- function hasOuterCorrespondingIdentifier(shortName, node) {
1506
- let currentScope = getScope(node);
1507
- let outer = currentScope.upper;
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
- for (const variable of outer.variables) {
1510
- if (variable.name.length >= minLength && variable.name.length > shortName.length && variable.name.startsWith(shortName)) {
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
- for (const name of blockVars) {
1518
- if (name.length >= minLength && name.length > shortName.length && name.startsWith(shortName)) {
1519
- return true;
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
- for (const anc of ancestors) {
1524
- if (anc.type === "VariableDeclarator" && anc.id && anc.id.type === "Identifier") {
1525
- const outerName = anc.id.name;
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" && anc.param) {
1541
- const names = extractIdentifiersFromPattern(anc.param, []);
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
- return false;
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
- function checkPattern(pattern) {
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
- for (const prop of pattern.properties) {
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
- for (const element of pattern.elements) {
1584
- if (element)
1585
- checkPattern(element);
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
- VariableDeclarator(node) {
1597
- if (node.id) {
1598
- checkPattern(node.id);
1817
+ CatchClause(node) {
1818
+ if (node.param) {
1819
+ checkPattern(node.param);
1599
1820
  }
1600
1821
  },
1601
1822
  "FunctionDeclaration, FunctionExpression, ArrowFunctionExpression"(node) {
1602
- for (const param of node.params) {
1823
+ node.params.forEach((param) => {
1603
1824
  checkPattern(param);
1604
- }
1825
+ });
1605
1826
  },
1606
- CatchClause(node) {
1607
- if (node.param) {
1608
- checkPattern(node.param);
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: "disallow too many nested blocks except when the block only contains an early exit (return or throw)",
1621
- category: "Best Practices",
1622
- recommended: false
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
- type: "number"
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 maxDepth = typeof context.options[0] === "number" ? context.options[0] : 1;
1869
+ const [option] = context.options;
1870
+ const maxDepth = typeof option === "number" ? option : 1;
1632
1871
  const functionStack = [];
1633
- function getAncestors(node) {
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
- function isEarlyExitBlock(node) {
1643
- return node.body.length === 1 && (node.body[0].type === "ReturnStatement" || node.body[0].type === "ThrowStatement");
1644
- }
1645
- function checkDepth(node, depth) {
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
- node,
1649
- message: "Blocks are nested too deeply ({{depth}}). Maximum allowed is {{maxDepth}} or an early exit.",
1650
- data: { depth, maxDepth }
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
- const ancestors = getAncestors(node);
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
- if (functionStack.length > 0) {
1674
- functionStack[functionStack.length - 1]++;
1675
- checkDepth(node, functionStack[functionStack.length - 1]);
1943
+ const depth = incrementCurrentDepth();
1944
+ if (depth !== null) {
1945
+ checkDepth(node, depth);
1676
1946
  }
1677
1947
  },
1678
1948
  "BlockStatement:exit"(node) {
1679
- const ancestors = getAncestors(node);
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
- if (functionStack.length > 0) {
1688
- functionStack[functionStack.length - 1]--;
1689
- }
1955
+ decrementCurrentDepth();
1956
+ },
1957
+ FunctionDeclaration() {
1958
+ functionStack.push(0);
1690
1959
  },
1691
1960
  "FunctionDeclaration:exit"() {
1692
1961
  functionStack.pop();
1693
1962
  },
1694
- "FunctionExpression:exit"() {
1695
- functionStack.pop();
1963
+ FunctionExpression() {
1964
+ functionStack.push(0);
1696
1965
  },
1697
- "ArrowFunctionExpression:exit"() {
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: "Enforce correct naming for useSpring and useSprings hook destructuring",
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
- schema: []
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
- if (node.init && node.init.type === "CallExpression" && node.init.callee && node.init.callee.type === "Identifier" && (node.init.callee.name === "useSpring" || node.init.callee.name === "useSprings")) {
1719
- const hookName = node.init.callee.name;
1720
- if (node.id && node.id.type === "ArrayPattern") {
1721
- const elements = node.id.elements;
1722
- if (elements.length < 2) {
1723
- return;
1724
- }
1725
- const firstElem = elements[0];
1726
- const secondElem = elements[1];
1727
- if (!firstElem || firstElem.type !== "Identifier" || !secondElem || secondElem.type !== "Identifier") {
1728
- return;
1729
- }
1730
- const firstName = firstElem.name;
1731
- const secondName = secondElem.name;
1732
- if (hookName === "useSpring") {
1733
- if (!firstName.endsWith("Springs")) {
1734
- context.report({
1735
- node: firstElem,
1736
- message: "The first variable from useSpring must end with 'Springs'."
1737
- });
1738
- } else {
1739
- const base = firstName.slice(0, -"Springs".length);
1740
- if (!base) {
1741
- context.report({
1742
- node: firstElem,
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.js
1794
- var inline_style_limit_default = {
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
- category: "Best Practices",
1800
- recommended: false
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
- type: "object",
2141
+ additionalProperties: false,
1810
2142
  properties: {
1811
2143
  maxKeys: {
1812
- type: "number",
1813
- description: "Maximum number of keys allowed in an inline style object before it must be extracted."
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
- additionalProperties: false
2148
+ type: "object"
1817
2149
  }
1818
2150
  ]
1819
2151
  }
1820
2152
  ],
1821
- messages: {
1822
- extractStyle: "Inline style objects should be extracted into a separate object or file when containing more than {{max}} keys."
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 option = context.options[0];
1827
- const maxKeys = typeof option === "number" ? option : option && option.maxKeys || 3;
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
- JSXAttribute(node) {
1830
- if (node.name.name !== "style") {
2173
+ "FunctionDeclaration, ArrowFunctionExpression, FunctionExpression"(node) {
2174
+ if (node.params.length === 0) {
1831
2175
  return;
1832
2176
  }
1833
- if (node.value && node.value.type === "JSXExpressionContainer" && node.value.expression && node.value.expression.type === "ObjectExpression") {
1834
- const styleObject = node.value.expression;
1835
- const keyCount = styleObject.properties.filter((prop) => prop.type === "Property").length;
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
- create(context) {
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.js
1887
- var no_unnecessary_div_default = {
1888
- meta: {
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.openingElement.name && node.openingElement.name.name === "div") {
1900
- const meaningfulChildren = node.children.filter((child) => {
1901
- if (child.type === "JSXText") {
1902
- return child.value.trim() !== "";
1903
- }
1904
- return true;
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.js
2248
+ // src/index.ts
1919
2249
  var src_default = {
1920
2250
  rules: {
1921
- "no-nested-jsx-return": no_nested_jsx_return_default,
1922
- "explicit-object-types": explicit_object_types_default,
1923
- "sort-keys-fixable": sort_keys_fixable_default,
1924
- "no-transition-cssproperties": no_transition_cssproperties_default,
1925
- "no-explicit-return-type": no_explicit_return_types_default,
1926
- "max-jsxnesting": max_jsx_nesting_default,
1927
- "seperate-style-files": seperate_style_files_default,
1928
- "no-unnecessary-key": no_unnecessary_key_default,
1929
- "sort-exports": sort_exports_default,
1930
- "localize-react-props": localize_react_props_default,
1931
- "no-or-none-component": no_or_none_component_default,
1932
- "no-button-navigation": no_button_navigation_default,
1933
- "no-multi-style-objects": no_multi_style_objects_default,
1934
- "no-useless-function": no_useless_function_default,
1935
- "min-var-length": min_var_length_default,
1936
- "max-depth-extended": max_depth_extended_default,
1937
- "spring-naming-convention": spring_naming_convention_default,
1938
- "inline-style-limit": inline_style_limit_default,
1939
- "no-inline-prop-types": no_inline_prop_types_default,
1940
- "no-unnecessary-div": no_unnecessary_div_default
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 {