eslint-plugin-absolute 0.2.0 → 0.2.1

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