@conorroberts/utils 0.0.66 → 0.0.69

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.
@@ -15,7 +15,7 @@ import { definePlugin, defineRule } from "oxlint";
15
15
  * @property {boolean} returnsJsx
16
16
  */
17
17
  const JSX_NODE_TYPES$2 = new Set(["JSXElement", "JSXFragment"]);
18
- const FUNCTION_NODE_TYPES$3 = new Set([
18
+ const FUNCTION_NODE_TYPES$4 = new Set([
19
19
  "FunctionDeclaration",
20
20
  "FunctionExpression",
21
21
  "ArrowFunctionExpression"
@@ -24,12 +24,12 @@ const FUNCTION_NODE_TYPES$3 = new Set([
24
24
  * @param {unknown} node
25
25
  * @returns {node is ESTNode & { type: string }}
26
26
  */
27
- const isNode$3 = (node) => Boolean(node && typeof node === "object" && "type" in node);
27
+ const isNode$4 = (node) => Boolean(node && typeof node === "object" && "type" in node);
28
28
  /**
29
29
  * @param {unknown} node
30
30
  * @returns {node is FunctionLikeNode}
31
31
  */
32
- const isFunctionLike$3 = (node) => isNode$3(node) && FUNCTION_NODE_TYPES$3.has(node.type);
32
+ const isFunctionLike$4 = (node) => isNode$4(node) && FUNCTION_NODE_TYPES$4.has(node.type);
33
33
  /**
34
34
  * @param {unknown} name
35
35
  * @returns {name is string}
@@ -50,13 +50,13 @@ const getFunctionName$2 = (node) => {
50
50
  if (node.id.type === "Identifier") return node.id.name;
51
51
  }
52
52
  const parent = node.parent;
53
- if (!parent || !isNode$3(parent)) return "";
53
+ if (!parent || !isNode$4(parent)) return "";
54
54
  if (parent.type === "VariableDeclarator") return parent.id && parent.id.type === "Identifier" ? parent.id.name : "";
55
55
  if (parent.type === "AssignmentExpression") return parent.left && parent.left.type === "Identifier" ? parent.left.name : "";
56
56
  if (parent.type === "Property" || parent.type === "MethodDefinition") return "";
57
57
  if (parent.type === "CallExpression") {
58
58
  const callParent = parent.parent;
59
- if (callParent && isNode$3(callParent)) {
59
+ if (callParent && isNode$4(callParent)) {
60
60
  if (callParent.type === "VariableDeclarator") return callParent.id && callParent.id.type === "Identifier" ? callParent.id.name : "";
61
61
  if (callParent.type === "AssignmentExpression") return callParent.left && callParent.left.type === "Identifier" ? callParent.left.name : "";
62
62
  }
@@ -67,25 +67,25 @@ const getFunctionName$2 = (node) => {
67
67
  * @param {ESTExpression | null | undefined} root
68
68
  */
69
69
  const expressionContainsJsx$2 = (root) => {
70
- if (!root || !isNode$3(root)) return false;
70
+ if (!root || !isNode$4(root)) return false;
71
71
  const stack = [root];
72
72
  while (stack.length > 0) {
73
73
  const current = stack.pop();
74
- if (!current || !isNode$3(current)) continue;
74
+ if (!current || !isNode$4(current)) continue;
75
75
  if (JSX_NODE_TYPES$2.has(current.type)) return true;
76
- if (FUNCTION_NODE_TYPES$3.has(current.type) && current !== root) continue;
76
+ if (FUNCTION_NODE_TYPES$4.has(current.type) && current !== root) continue;
77
77
  for (const key of Object.keys(current)) {
78
78
  if (key === "parent") continue;
79
79
  const value = current[key];
80
80
  if (!value) continue;
81
81
  if (Array.isArray(value)) {
82
- for (const element of value) if (isNode$3(element)) stack.push(element);
83
- } else if (isNode$3(value)) stack.push(value);
82
+ for (const element of value) if (isNode$4(element)) stack.push(element);
83
+ } else if (isNode$4(value)) stack.push(value);
84
84
  }
85
85
  }
86
86
  return false;
87
87
  };
88
- const rule$9 = defineRule({
88
+ const rule$11 = defineRule({
89
89
  meta: {
90
90
  type: "problem",
91
91
  docs: {
@@ -126,20 +126,20 @@ const rule$9 = defineRule({
126
126
  const fnCtx = currentFunction();
127
127
  if (!fnCtx) return;
128
128
  const argument = node.argument;
129
- if (!argument || isFunctionLike$3(argument)) return;
129
+ if (!argument || isFunctionLike$4(argument)) return;
130
130
  if (expressionContainsJsx$2(argument)) fnCtx.returnsJsx = true;
131
131
  };
132
132
  return {
133
133
  FunctionDeclaration(node) {
134
- if (isFunctionLike$3(node)) enterFunction(node);
134
+ if (isFunctionLike$4(node)) enterFunction(node);
135
135
  },
136
136
  "FunctionDeclaration:exit": exitFunction,
137
137
  FunctionExpression(node) {
138
- if (isFunctionLike$3(node)) enterFunction(node);
138
+ if (isFunctionLike$4(node)) enterFunction(node);
139
139
  },
140
140
  "FunctionExpression:exit": exitFunction,
141
141
  ArrowFunctionExpression(node) {
142
- if (isFunctionLike$3(node)) enterFunction(node);
142
+ if (isFunctionLike$4(node)) enterFunction(node);
143
143
  },
144
144
  "ArrowFunctionExpression:exit": exitFunction,
145
145
  ReturnStatement(node) {
@@ -148,7 +148,30 @@ const rule$9 = defineRule({
148
148
  };
149
149
  }
150
150
  });
151
- const jsxComponentPascalCaseRule = rule$9;
151
+ const jsxComponentPascalCaseRule = rule$11;
152
+
153
+ //#endregion
154
+ //#region src/oxlint-plugins/no-array-type.js
155
+ const rule$10 = defineRule({
156
+ meta: {
157
+ type: "problem",
158
+ docs: {
159
+ description: "Disallow Array<T> type syntax in favor of T[] syntax",
160
+ recommended: true
161
+ },
162
+ schema: []
163
+ },
164
+ createOnce(context) {
165
+ return { TSTypeReference(node) {
166
+ if (node.type !== "TSTypeReference") return;
167
+ if (node.typeName?.type === "Identifier" && node.typeName.name === "Array" && node.typeParameters) context.report({
168
+ node,
169
+ message: "Use T[] syntax instead of Array<T>"
170
+ });
171
+ } };
172
+ }
173
+ });
174
+ const noArrayTypeRule = rule$10;
152
175
 
153
176
  //#endregion
154
177
  //#region src/oxlint-plugins/no-component-date-instantiation.js
@@ -167,7 +190,7 @@ const jsxComponentPascalCaseRule = rule$9;
167
190
  * @property {boolean} returnsJsx
168
191
  * @property {NewExpressionNode[]} dateInstantiations
169
192
  */
170
- const FUNCTION_NODE_TYPES$2 = new Set([
193
+ const FUNCTION_NODE_TYPES$3 = new Set([
171
194
  "FunctionDeclaration",
172
195
  "FunctionExpression",
173
196
  "ArrowFunctionExpression"
@@ -176,18 +199,18 @@ const FUNCTION_NODE_TYPES$2 = new Set([
176
199
  * @param {unknown} node
177
200
  * @returns {node is ESTNode & { type: string }}
178
201
  */
179
- const isNode$2 = (node) => Boolean(node && typeof node === "object" && "type" in node);
202
+ const isNode$3 = (node) => Boolean(node && typeof node === "object" && "type" in node);
180
203
  /**
181
204
  * @param {unknown} node
182
205
  * @returns {node is FunctionLikeNode}
183
206
  */
184
- const isFunctionLike$2 = (node) => isNode$2(node) && FUNCTION_NODE_TYPES$2.has(node.type);
207
+ const isFunctionLike$3 = (node) => isNode$3(node) && FUNCTION_NODE_TYPES$3.has(node.type);
185
208
  /**
186
209
  * Check if a function name follows React component naming convention (PascalCase)
187
210
  * @param {unknown} name
188
211
  * @returns {name is string}
189
212
  */
190
- const isComponentName = (name) => typeof name === "string" && /^[A-Z]/.test(name);
213
+ const isComponentName$1 = (name) => typeof name === "string" && /^[A-Z]/.test(name);
191
214
  /**
192
215
  * Get the name of a function node
193
216
  * @param {FunctionLikeNode} node
@@ -199,7 +222,7 @@ const getFunctionName$1 = (node) => {
199
222
  if (node.id.type === "Identifier") return node.id.name;
200
223
  }
201
224
  const parent = node.parent;
202
- if (!parent || !isNode$2(parent)) return "";
225
+ if (!parent || !isNode$3(parent)) return "";
203
226
  if (parent.type === "VariableDeclarator") return parent.id && parent.id.type === "Identifier" ? parent.id.name : "";
204
227
  if (parent.type === "AssignmentExpression") return parent.left && parent.left.type === "Identifier" ? parent.left.name : "";
205
228
  if (parent.type === "Property" || parent.type === "MethodDefinition") return parent.key && parent.key.type === "Identifier" ? parent.key.name : "";
@@ -211,7 +234,7 @@ const getFunctionName$1 = (node) => {
211
234
  * @returns {boolean}
212
235
  */
213
236
  const isJSXNode = (node) => {
214
- if (!node || !isNode$2(node)) return false;
237
+ if (!node || !isNode$3(node)) return false;
215
238
  return node.type === "JSXElement" || node.type === "JSXFragment";
216
239
  };
217
240
  /**
@@ -223,7 +246,7 @@ const isDateInstantiation = (node) => {
223
246
  if (node.callee.type === "Identifier" && node.callee.name === "Date") return true;
224
247
  return false;
225
248
  };
226
- const rule$8 = defineRule({
249
+ const rule$9 = defineRule({
227
250
  meta: {
228
251
  type: "problem",
229
252
  docs: {
@@ -257,7 +280,7 @@ const rule$8 = defineRule({
257
280
  const fnCtx = functionStack.pop();
258
281
  if (!fnCtx) return;
259
282
  if (!fnCtx.returnsJsx) return;
260
- if (!isComponentName(fnCtx.name)) return;
283
+ if (!isComponentName$1(fnCtx.name)) return;
261
284
  for (const dateNode of fnCtx.dateInstantiations) context.report({
262
285
  node: dateNode,
263
286
  message: `Avoid instantiating Date in the top scope of component '${fnCtx.name}'. Date instances change on every render. Move it inside an effect, event handler, or use useMemo/useCallback.`
@@ -280,15 +303,15 @@ const rule$8 = defineRule({
280
303
  };
281
304
  return {
282
305
  FunctionDeclaration(node) {
283
- if (isFunctionLike$2(node)) enterFunction(node);
306
+ if (isFunctionLike$3(node)) enterFunction(node);
284
307
  },
285
308
  "FunctionDeclaration:exit": exitFunction,
286
309
  FunctionExpression(node) {
287
- if (isFunctionLike$2(node)) enterFunction(node);
310
+ if (isFunctionLike$3(node)) enterFunction(node);
288
311
  },
289
312
  "FunctionExpression:exit": exitFunction,
290
313
  ArrowFunctionExpression(node) {
291
- if (isFunctionLike$2(node)) enterFunction(node);
314
+ if (isFunctionLike$3(node)) enterFunction(node);
292
315
  },
293
316
  "ArrowFunctionExpression:exit": exitFunction,
294
317
  ReturnStatement(node) {
@@ -300,136 +323,7 @@ const rule$8 = defineRule({
300
323
  };
301
324
  }
302
325
  });
303
- const noComponentDateInstantiationRule = rule$8;
304
-
305
- //#endregion
306
- //#region src/oxlint-plugins/no-delete.js
307
- /** @typedef {import("oxlint").ESTree.Node} ESTNode */
308
- const rule$7 = defineRule({
309
- meta: {
310
- type: "problem",
311
- docs: {
312
- description: "Disallow the 'delete' operator",
313
- recommended: true
314
- },
315
- schema: []
316
- },
317
- createOnce(context) {
318
- return { UnaryExpression(node) {
319
- if (node.type !== "UnaryExpression") return;
320
- if (node.operator === "delete") context.report({
321
- node,
322
- message: "Use of 'delete' operator is disallowed. Use object destructuring or set properties to undefined instead."
323
- });
324
- } };
325
- }
326
- });
327
- const noDeleteRule = rule$7;
328
-
329
- //#endregion
330
- //#region src/oxlint-plugins/no-emoji.js
331
- /** @typedef {import("oxlint").ESTree.Node} ESTNode */
332
- /**
333
- * Regex pattern to match emojis
334
- * Covers most common emoji ranges in Unicode
335
- */
336
- const EMOJI_REGEX = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F000}-\u{1F02F}\u{1F0A0}-\u{1F0FF}\u{1F100}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{FE00}-\u{FE0F}\u{203C}\u{2049}\u{20E3}\u{2139}\u{2194}-\u{2199}\u{21A9}-\u{21AA}\u{231A}-\u{231B}\u{2328}\u{23CF}\u{23E9}-\u{23F3}\u{23F8}-\u{23FA}\u{24C2}\u{25AA}-\u{25AB}\u{25B6}\u{25C0}\u{25FB}-\u{25FE}\u{2600}-\u{2604}\u{260E}\u{2611}\u{2614}-\u{2615}\u{2618}\u{261D}\u{2620}\u{2622}-\u{2623}\u{2626}\u{262A}\u{262E}-\u{262F}\u{2638}-\u{263A}\u{2640}\u{2642}\u{2648}-\u{2653}\u{265F}-\u{2660}\u{2663}\u{2665}-\u{2666}\u{2668}\u{267B}\u{267E}-\u{267F}\u{2692}-\u{2697}\u{2699}\u{269B}-\u{269C}\u{26A0}-\u{26A1}\u{26A7}\u{26AA}-\u{26AB}\u{26B0}-\u{26B1}\u{26BD}-\u{26BE}\u{26C4}-\u{26C5}\u{26C8}\u{26CE}\u{26CF}\u{26D1}\u{26D3}-\u{26D4}\u{26E9}-\u{26EA}\u{26F0}-\u{26F5}\u{26F7}-\u{26FA}\u{26FD}\u{2702}\u{2705}\u{2708}-\u{270D}\u{270F}\u{2712}\u{2714}\u{2716}\u{271D}\u{2721}\u{2728}\u{2733}-\u{2734}\u{2744}\u{2747}\u{274C}\u{274E}\u{2753}-\u{2755}\u{2757}\u{2763}-\u{2764}\u{2795}-\u{2797}\u{27A1}\u{27B0}\u{27BF}\u{2934}-\u{2935}\u{2B05}-\u{2B07}\u{2B1B}-\u{2B1C}\u{2B50}\u{2B55}\u{3030}\u{303D}\u{3297}\u{3299}]/gu;
337
- /**
338
- * Find emojis in a string
339
- * @param {string} text
340
- * @returns {RegExpMatchArray | null}
341
- */
342
- const findEmojis = (text) => {
343
- return text.match(EMOJI_REGEX);
344
- };
345
- /**
346
- * Get a preview of the emoji found
347
- * @param {string} text
348
- * @returns {string}
349
- */
350
- const getEmojiPreview = (text) => {
351
- const emojis = findEmojis(text);
352
- if (!emojis || emojis.length === 0) return "";
353
- const uniqueEmojis = [...new Set(emojis)];
354
- const preview = uniqueEmojis.slice(0, 3).join(" ");
355
- return uniqueEmojis.length > 3 ? `${preview} ...` : preview;
356
- };
357
- const rule$6 = defineRule({
358
- meta: {
359
- type: "problem",
360
- docs: {
361
- description: "Disallow the use of emojis in code. Use icons from a component library instead.",
362
- recommended: true
363
- },
364
- schema: []
365
- },
366
- createOnce(context) {
367
- return {
368
- StringLiteral(node) {
369
- if (node.type !== "StringLiteral") return;
370
- const emojis = findEmojis(node.value);
371
- if (emojis && emojis.length > 0) {
372
- const preview = getEmojiPreview(node.value);
373
- context.report({
374
- node,
375
- message: `Emojis are not allowed in code. Found: ${preview}. Use icons from a component library instead.`
376
- });
377
- }
378
- },
379
- TemplateLiteral(node) {
380
- if (node.type !== "TemplateLiteral") return;
381
- for (const quasi of node.quasis) {
382
- if (quasi.type !== "TemplateElement") continue;
383
- const text = quasi.value.raw;
384
- const emojis = findEmojis(text);
385
- if (emojis && emojis.length > 0) {
386
- const preview = getEmojiPreview(text);
387
- context.report({
388
- node: quasi,
389
- message: `Emojis are not allowed in code. Found: ${preview}. Use icons from a component library instead.`
390
- });
391
- }
392
- }
393
- },
394
- JSXText(node) {
395
- if (node.type !== "JSXText") return;
396
- const emojis = findEmojis(node.value);
397
- if (emojis && emojis.length > 0) {
398
- const preview = getEmojiPreview(node.value);
399
- context.report({
400
- node,
401
- message: `Emojis are not allowed in code. Found: ${preview}. Use icons from a component library instead.`
402
- });
403
- }
404
- }
405
- };
406
- }
407
- });
408
- const noEmojiRule = rule$6;
409
-
410
- //#endregion
411
- //#region src/oxlint-plugins/no-finally.js
412
- /** @typedef {import("oxlint").ESTree.Node} ESTNode */
413
- const rule$5 = defineRule({
414
- meta: {
415
- type: "problem",
416
- docs: {
417
- description: "Disallow 'finally' blocks in try/catch/finally statements",
418
- recommended: true
419
- },
420
- schema: []
421
- },
422
- createOnce(context) {
423
- return { TryStatement(node) {
424
- if (node.type !== "TryStatement") return;
425
- if (node.finalizer) context.report({
426
- node: node.finalizer,
427
- message: "Use of 'finally' blocks is disallowed. Handle cleanup explicitly in try/catch blocks instead."
428
- });
429
- } };
430
- }
431
- });
432
- const noFinallyRule = rule$5;
326
+ const noComponentDateInstantiationRule = rule$9;
433
327
 
434
328
  //#endregion
435
329
  //#region src/oxlint-plugins/no-inline-components.js
@@ -464,21 +358,38 @@ const noFinallyRule = rule$5;
464
358
  * @property {NestedFunctionRecord[]} nestedJsxChildren
465
359
  */
466
360
  const JSX_NODE_TYPES$1 = new Set(["JSXElement", "JSXFragment"]);
467
- const FUNCTION_NODE_TYPES$1 = new Set([
361
+ const FUNCTION_NODE_TYPES$2 = new Set([
468
362
  "FunctionDeclaration",
469
363
  "FunctionExpression",
470
364
  "ArrowFunctionExpression"
471
365
  ]);
472
366
  /**
367
+ * @param {unknown} name
368
+ * @returns {name is string}
369
+ */
370
+ const isComponentName = (name) => typeof name === "string" && /^[A-Z]/.test(name);
371
+ /**
473
372
  * @param {unknown} node
474
373
  * @returns {node is ESTNode & { type: string }}
475
374
  */
476
- const isNode$1 = (node) => Boolean(node && typeof node === "object" && "type" in node);
375
+ const isNode$2 = (node) => Boolean(node && typeof node === "object" && "type" in node);
477
376
  /**
478
377
  * @param {unknown} node
479
378
  * @returns {node is FunctionLikeNode}
480
379
  */
481
- const isFunctionLike$1 = (node) => isNode$1(node) && FUNCTION_NODE_TYPES$1.has(node.type);
380
+ const isFunctionLike$2 = (node) => isNode$2(node) && FUNCTION_NODE_TYPES$2.has(node.type);
381
+ /**
382
+ * @param {ESTNode | null | undefined} node
383
+ * @returns {FunctionLikeNode | null}
384
+ */
385
+ const getEnclosingFunction$1 = (node) => {
386
+ const findFunction = (current) => {
387
+ if (!current) return null;
388
+ if (isFunctionLike$2(current)) return current;
389
+ return findFunction(isNode$2(current) ? current.parent ?? null : null);
390
+ };
391
+ return findFunction(isNode$2(node) ? node.parent ?? null : null);
392
+ };
482
393
  /**
483
394
  * @param {FunctionLikeNode} node
484
395
  */
@@ -486,16 +397,16 @@ const isFunctionUsedAsJsxProp = (node) => {
486
397
  const checkJsxProp = (current) => {
487
398
  if (!current) return false;
488
399
  if (current.type === "JSXAttribute") return true;
489
- if (isFunctionLike$1(current)) return false;
490
- return checkJsxProp(isNode$1(current) ? current.parent ?? null : null);
400
+ if (isFunctionLike$2(current)) return false;
401
+ return checkJsxProp(isNode$2(current) ? current.parent ?? null : null);
491
402
  };
492
- return checkJsxProp(isNode$1(node) ? node.parent ?? null : null);
403
+ return checkJsxProp(isNode$2(node) ? node.parent ?? null : null);
493
404
  };
494
405
  /**
495
406
  * @param {FunctionLikeNode} node
496
407
  */
497
408
  const isFunctionImmediatelyInvoked = (node) => {
498
- const parent = isNode$1(node) ? node.parent ?? null : null;
409
+ const parent = isNode$2(node) ? node.parent ?? null : null;
499
410
  if (!parent) return false;
500
411
  if (parent.type === "CallExpression" && parent.callee === node) return true;
501
412
  return false;
@@ -504,20 +415,20 @@ const isFunctionImmediatelyInvoked = (node) => {
504
415
  * @param {ESTExpression | null | undefined} root
505
416
  */
506
417
  const expressionContainsJsx$1 = (root) => {
507
- if (!root || !isNode$1(root)) return false;
418
+ if (!root || !isNode$2(root)) return false;
508
419
  const stack = [root];
509
420
  while (stack.length > 0) {
510
421
  const current = stack.pop();
511
- if (!current || !isNode$1(current)) continue;
422
+ if (!current || !isNode$2(current)) continue;
512
423
  if (JSX_NODE_TYPES$1.has(current.type)) return true;
513
- if (FUNCTION_NODE_TYPES$1.has(current.type) && current !== root) continue;
424
+ if (FUNCTION_NODE_TYPES$2.has(current.type) && current !== root) continue;
514
425
  for (const key of Object.keys(current)) {
515
426
  if (key === "parent") continue;
516
427
  const value = current[key];
517
428
  if (!value) continue;
518
429
  if (Array.isArray(value)) {
519
- for (const element of value) if (isNode$1(element)) stack.push(element);
520
- } else if (isNode$1(value)) stack.push(value);
430
+ for (const element of value) if (isNode$2(element)) stack.push(element);
431
+ } else if (isNode$2(value)) stack.push(value);
521
432
  }
522
433
  }
523
434
  return false;
@@ -529,7 +440,7 @@ const expressionContainsJsx$1 = (root) => {
529
440
  const expressionProducesJsx = (root, bindingNames) => {
530
441
  if (!root) return false;
531
442
  if (expressionContainsJsx$1(root)) return true;
532
- if (!isNode$1(root)) return false;
443
+ if (!isNode$2(root)) return false;
533
444
  const type = root.type;
534
445
  if (type === "Identifier") return bindingNames.has(root.name);
535
446
  if (type === "ConditionalExpression") return expressionProducesJsx(root.consequent, bindingNames) || expressionProducesJsx(root.alternate, bindingNames);
@@ -555,7 +466,7 @@ const expressionProducesJsx = (root, bindingNames) => {
555
466
  * @param {string[]} names
556
467
  */
557
468
  const collectBindingNames = (pattern, names) => {
558
- if (!pattern || !isNode$1(pattern)) return;
469
+ if (!pattern || !isNode$2(pattern)) return;
559
470
  const type = pattern.type;
560
471
  if (type === "Identifier") {
561
472
  names.push(pattern.name);
@@ -592,13 +503,13 @@ const getFunctionName = (node) => {
592
503
  if (node.id.type === "Identifier") return node.id.name;
593
504
  }
594
505
  const parent = node.parent;
595
- if (!parent || !isNode$1(parent)) return "";
506
+ if (!parent || !isNode$2(parent)) return "";
596
507
  if (parent.type === "VariableDeclarator") return parent.id && parent.id.type === "Identifier" ? parent.id.name : "";
597
508
  if (parent.type === "AssignmentExpression") return parent.left && parent.left.type === "Identifier" ? parent.left.name : "";
598
509
  if (parent.type === "Property" || parent.type === "MethodDefinition") return parent.key && parent.key.type === "Identifier" ? parent.key.name : "";
599
510
  if (parent.type === "CallExpression") {
600
511
  const callParent = parent.parent;
601
- if (callParent && isNode$1(callParent)) {
512
+ if (callParent && isNode$2(callParent)) {
602
513
  if (callParent.type === "VariableDeclarator") return callParent.id && callParent.id.type === "Identifier" ? callParent.id.name : "";
603
514
  if (callParent.type === "AssignmentExpression") return callParent.left && callParent.left.type === "Identifier" ? callParent.left.name : "";
604
515
  }
@@ -629,7 +540,7 @@ const createNestedFunctionMessage = (childName, parentName) => `JSX-returning ${
629
540
  * @param {string} name
630
541
  */
631
542
  const createIIFEMessage = (name) => `JSX-returning ${describeNested(name)} should not be declared as an immediately invoked function expression (IIFE). Extract it to a named function at module scope.`;
632
- const rule$4 = defineRule({
543
+ const rule$8 = defineRule({
633
544
  meta: {
634
545
  type: "problem",
635
546
  docs: {
@@ -690,7 +601,7 @@ const rule$4 = defineRule({
690
601
  const fnCtx = currentFunction();
691
602
  if (!fnCtx) return;
692
603
  const argument = node.argument;
693
- if (!argument || isFunctionLike$1(argument)) return;
604
+ if (!argument || isFunctionLike$2(argument)) return;
694
605
  if (expressionProducesJsx(argument, fnCtx.jsxBindingNames)) fnCtx.returnsJsx = true;
695
606
  };
696
607
  /** @param {VariableDeclaratorNode} node */
@@ -698,7 +609,7 @@ const rule$4 = defineRule({
698
609
  const fnCtx = currentFunction();
699
610
  if (!fnCtx) return;
700
611
  const init = node.init;
701
- if (!init || isFunctionLike$1(init)) return;
612
+ if (!init || isFunctionLike$2(init)) return;
702
613
  if (!expressionContainsJsx$1(init)) return;
703
614
  const names = [];
704
615
  collectBindingNames(node.id, names);
@@ -714,7 +625,7 @@ const rule$4 = defineRule({
714
625
  const fnCtx = currentFunction();
715
626
  if (!fnCtx) return;
716
627
  const right = node.right;
717
- if (!right || isFunctionLike$1(right)) return;
628
+ if (!right || isFunctionLike$2(right)) return;
718
629
  if (!expressionContainsJsx$1(right)) return;
719
630
  const names = [];
720
631
  if (node.left && node.left.type === "Identifier") {
@@ -751,15 +662,15 @@ const rule$4 = defineRule({
751
662
  };
752
663
  return {
753
664
  FunctionDeclaration(node) {
754
- if (isFunctionLike$1(node)) enterFunction(node);
665
+ if (isFunctionLike$2(node)) enterFunction(node);
755
666
  },
756
667
  "FunctionDeclaration:exit": exitFunction,
757
668
  FunctionExpression(node) {
758
- if (isFunctionLike$1(node)) enterFunction(node);
669
+ if (isFunctionLike$2(node)) enterFunction(node);
759
670
  },
760
671
  "FunctionExpression:exit": exitFunction,
761
672
  ArrowFunctionExpression(node) {
762
- if (isFunctionLike$1(node)) enterFunction(node);
673
+ if (isFunctionLike$2(node)) enterFunction(node);
763
674
  },
764
675
  "ArrowFunctionExpression:exit": exitFunction,
765
676
  ReturnStatement(node) {
@@ -777,7 +688,412 @@ const rule$4 = defineRule({
777
688
  };
778
689
  }
779
690
  });
780
- const noInlineComponentsRule = rule$4;
691
+ const noInlineComponentsRule = rule$8;
692
+
693
+ //#endregion
694
+ //#region src/oxlint-plugins/no-component-pure-functions.js
695
+ /**
696
+ * @typedef {import("oxlint").Context} RuleContext
697
+ * @typedef {import("oxlint").ESTree.Node} ESTNode
698
+ * @typedef {import("oxlint").ESTree.Function | import("oxlint").ESTree.ArrowFunctionExpression} FunctionLikeNode
699
+ * @typedef {import("oxlint").ESTree.VariableDeclarator} VariableDeclaratorNode
700
+ */
701
+ const FUNCTION_NODE_TYPES$1 = new Set([
702
+ "FunctionDeclaration",
703
+ "FunctionExpression",
704
+ "ArrowFunctionExpression"
705
+ ]);
706
+ /**
707
+ * @param {unknown} node
708
+ * @returns {node is ESTNode & { type: string }}
709
+ */
710
+ const isNode$1 = (node) => Boolean(node && typeof node === "object" && "type" in node);
711
+ /**
712
+ * @param {unknown} node
713
+ * @returns {node is FunctionLikeNode}
714
+ */
715
+ const isFunctionLike$1 = (node) => isNode$1(node) && FUNCTION_NODE_TYPES$1.has(node.type);
716
+ /**
717
+ * Checks if a node is a React Hook call (e.g., useState, useEffect, useCallback)
718
+ * @param {ESTNode} node
719
+ * @returns {boolean}
720
+ */
721
+ const isReactHookCall = (node) => {
722
+ if (node.type !== "CallExpression") return false;
723
+ const callee = node.callee;
724
+ if (!callee) return false;
725
+ if (callee.type === "Identifier" && callee.name.startsWith("use")) return true;
726
+ if (callee.type === "MemberExpression" && callee.object && callee.object.type === "Identifier" && callee.object.name === "React" && callee.property && callee.property.type === "Identifier" && callee.property.name.startsWith("use")) return true;
727
+ return false;
728
+ };
729
+ /**
730
+ * Checks if a node contains JSX
731
+ * @param {ESTNode} node
732
+ * @returns {boolean}
733
+ */
734
+ const containsJSX = (node) => {
735
+ if (!node || !isNode$1(node)) return false;
736
+ const stack = [node];
737
+ while (stack.length > 0) {
738
+ const current = stack.pop();
739
+ if (!current || !isNode$1(current)) continue;
740
+ if (current.type === "JSXElement" || current.type === "JSXFragment") return true;
741
+ if (isFunctionLike$1(current) && current !== node) continue;
742
+ for (const key of Object.keys(current)) {
743
+ if (key === "parent") continue;
744
+ const value = current[key];
745
+ if (!value) continue;
746
+ if (Array.isArray(value)) {
747
+ for (const element of value) if (isNode$1(element)) stack.push(element);
748
+ } else if (isNode$1(value)) stack.push(value);
749
+ }
750
+ }
751
+ return false;
752
+ };
753
+ /**
754
+ * Checks if a function accesses variables from its enclosing scope
755
+ * (excluding function parameters and locally declared variables)
756
+ * @param {FunctionLikeNode} node
757
+ * @param {Set<string>} localNames - Names of local variables and parameters
758
+ * @returns {boolean}
759
+ */
760
+ const accessesEnclosingScope = (node, localNames) => {
761
+ if (!node.body) return false;
762
+ const body = node.body;
763
+ if (body.type !== "BlockStatement") return checkExpressionForScopeAccess(body, localNames);
764
+ return checkNodeForScopeAccess(body, localNames);
765
+ };
766
+ /**
767
+ * @param {ESTNode} node
768
+ * @param {Set<string>} localNames
769
+ * @returns {boolean}
770
+ */
771
+ const checkNodeForScopeAccess = (node, localNames) => {
772
+ if (!node || !isNode$1(node)) return false;
773
+ const stack = [node];
774
+ while (stack.length > 0) {
775
+ const current = stack.pop();
776
+ if (!current || !isNode$1(current)) continue;
777
+ if (current.type === "Identifier") {
778
+ const parent = current.parent;
779
+ if (parent && isNode$1(parent) && (parent.type === "VariableDeclarator" || parent.type === "FunctionDeclaration" || parent.type === "Property" && parent.key === current)) continue;
780
+ if (!localNames.has(current.name)) {
781
+ if (!new Set([
782
+ "console",
783
+ "Math",
784
+ "Date",
785
+ "JSON",
786
+ "Object",
787
+ "Array",
788
+ "String",
789
+ "Number",
790
+ "Boolean",
791
+ "parseInt",
792
+ "parseFloat",
793
+ "isNaN",
794
+ "isFinite",
795
+ "undefined",
796
+ "null",
797
+ "true",
798
+ "false",
799
+ "Infinity",
800
+ "NaN",
801
+ "Map",
802
+ "Set",
803
+ "WeakMap",
804
+ "WeakSet",
805
+ "Promise",
806
+ "Symbol",
807
+ "Error",
808
+ "TypeError",
809
+ "ReferenceError",
810
+ "SyntaxError"
811
+ ]).has(current.name)) return true;
812
+ }
813
+ }
814
+ if (isFunctionLike$1(current) && current !== node) continue;
815
+ for (const key of Object.keys(current)) {
816
+ if (key === "parent") continue;
817
+ const value = current[key];
818
+ if (!value) continue;
819
+ if (Array.isArray(value)) {
820
+ for (const element of value) if (isNode$1(element)) stack.push(element);
821
+ } else if (isNode$1(value)) stack.push(value);
822
+ }
823
+ }
824
+ return false;
825
+ };
826
+ /**
827
+ * @param {ESTNode} node
828
+ * @param {Set<string>} localNames
829
+ * @returns {boolean}
830
+ */
831
+ const checkExpressionForScopeAccess = (node, localNames) => {
832
+ return checkNodeForScopeAccess(node, localNames);
833
+ };
834
+ /**
835
+ * Collects all local variable names including parameters
836
+ * @param {FunctionLikeNode} node
837
+ * @returns {Set<string>}
838
+ */
839
+ const collectLocalNames = (node) => {
840
+ const names = /* @__PURE__ */ new Set();
841
+ if (node.params) for (const param of node.params) collectPatternNames(param, names);
842
+ if (node.body && node.body.type === "BlockStatement") {
843
+ for (const statement of node.body.body) if (statement.type === "VariableDeclaration") for (const declarator of statement.declarations) collectPatternNames(declarator.id, names);
844
+ }
845
+ return names;
846
+ };
847
+ /**
848
+ * @param {import("oxlint").ESTree.Pattern} pattern
849
+ * @param {Set<string>} names
850
+ */
851
+ const collectPatternNames = (pattern, names) => {
852
+ if (!pattern || !isNode$1(pattern)) return;
853
+ if (pattern.type === "Identifier") {
854
+ names.add(pattern.name);
855
+ return;
856
+ }
857
+ if (pattern.type === "ArrayPattern") {
858
+ for (const element of pattern.elements) {
859
+ if (!element) continue;
860
+ if (element.type === "RestElement") collectPatternNames(element.argument, names);
861
+ else collectPatternNames(element, names);
862
+ }
863
+ return;
864
+ }
865
+ if (pattern.type === "ObjectPattern") {
866
+ for (const property of pattern.properties) {
867
+ if (!property) continue;
868
+ if (property.type === "Property") collectPatternNames(property.value, names);
869
+ else if (property.type === "RestElement") collectPatternNames(property.argument, names);
870
+ }
871
+ return;
872
+ }
873
+ if (pattern.type === "AssignmentPattern") {
874
+ collectPatternNames(pattern.left, names);
875
+ return;
876
+ }
877
+ if (pattern.type === "RestElement") collectPatternNames(pattern.argument, names);
878
+ };
879
+ /**
880
+ * Checks if a function is likely a React component
881
+ * @param {FunctionLikeNode} node
882
+ * @returns {boolean}
883
+ */
884
+ const isReactComponent = (node) => {
885
+ if (!isComponentName(getFunctionName(node))) return false;
886
+ return containsJSX(node) || containsHooks(node);
887
+ };
888
+ /**
889
+ * Checks if a function contains React Hook calls
890
+ * @param {FunctionLikeNode} node
891
+ * @returns {boolean}
892
+ */
893
+ const containsHooks = (node) => {
894
+ if (!node.body) return false;
895
+ const stack = [node.body];
896
+ while (stack.length > 0) {
897
+ const current = stack.pop();
898
+ if (!current || !isNode$1(current)) continue;
899
+ if (isReactHookCall(current)) return true;
900
+ if (isFunctionLike$1(current) && current !== node.body) continue;
901
+ for (const key of Object.keys(current)) {
902
+ if (key === "parent") continue;
903
+ const value = current[key];
904
+ if (!value) continue;
905
+ if (Array.isArray(value)) {
906
+ for (const element of value) if (isNode$1(element)) stack.push(element);
907
+ } else if (isNode$1(value)) stack.push(value);
908
+ }
909
+ }
910
+ return false;
911
+ };
912
+ /**
913
+ * Checks if a function is wrapped in a React Hook (useCallback, useMemo, etc.)
914
+ * @param {FunctionLikeNode} node
915
+ * @returns {boolean}
916
+ */
917
+ const isWrappedInHook = (node) => {
918
+ const parent = node.parent;
919
+ if (!parent || !isNode$1(parent)) return false;
920
+ if (parent.type === "CallExpression" && isReactHookCall(parent)) return true;
921
+ return false;
922
+ };
923
+ /**
924
+ * Checks if a function is pure (doesn't access component scope, hooks, or JSX)
925
+ * @param {FunctionLikeNode} node
926
+ * @returns {boolean}
927
+ */
928
+ const isPureFunction = (node) => {
929
+ if (containsJSX(node)) return false;
930
+ if (containsHooks(node)) return false;
931
+ if (isWrappedInHook(node)) return false;
932
+ if (accessesEnclosingScope(node, collectLocalNames(node))) return false;
933
+ return true;
934
+ };
935
+ const rule$7 = defineRule({
936
+ meta: {
937
+ type: "suggestion",
938
+ docs: {
939
+ description: "Recommend extracting pure functions from React components to module scope for better performance and readability.",
940
+ recommended: false
941
+ },
942
+ schema: []
943
+ },
944
+ createOnce(context) {
945
+ /**
946
+ * @param {VariableDeclaratorNode} node
947
+ */
948
+ const handleVariableDeclarator = (node) => {
949
+ const init = node.init;
950
+ if (!init || !isFunctionLike$1(init)) return;
951
+ const enclosingFunction = getEnclosingFunction$1(node);
952
+ if (!enclosingFunction) return;
953
+ if (!isReactComponent(enclosingFunction)) return;
954
+ if (!isPureFunction(init)) return;
955
+ const functionName = node.id && node.id.type === "Identifier" ? node.id.name : "this function";
956
+ const componentName = getFunctionName(enclosingFunction);
957
+ context.report({
958
+ node: init,
959
+ message: `Pure function '${functionName}' can be extracted outside of component '${componentName}' to improve performance and readability.`
960
+ });
961
+ };
962
+ return { VariableDeclarator(node) {
963
+ if (node.type === "VariableDeclarator") handleVariableDeclarator(node);
964
+ } };
965
+ }
966
+ });
967
+ const noComponentPureFunctionsRule = rule$7;
968
+
969
+ //#endregion
970
+ //#region src/oxlint-plugins/no-delete.js
971
+ /** @typedef {import("oxlint").ESTree.Node} ESTNode */
972
+ const rule$6 = defineRule({
973
+ meta: {
974
+ type: "problem",
975
+ docs: {
976
+ description: "Disallow the 'delete' operator",
977
+ recommended: true
978
+ },
979
+ schema: []
980
+ },
981
+ createOnce(context) {
982
+ return { UnaryExpression(node) {
983
+ if (node.type !== "UnaryExpression") return;
984
+ if (node.operator === "delete") context.report({
985
+ node,
986
+ message: "Use of 'delete' operator is disallowed. Use object destructuring or set properties to undefined instead."
987
+ });
988
+ } };
989
+ }
990
+ });
991
+ const noDeleteRule = rule$6;
992
+
993
+ //#endregion
994
+ //#region src/oxlint-plugins/no-emoji.js
995
+ /** @typedef {import("oxlint").ESTree.Node} ESTNode */
996
+ /**
997
+ * Regex pattern to match emojis
998
+ * Covers most common emoji ranges in Unicode
999
+ */
1000
+ const EMOJI_REGEX = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F000}-\u{1F02F}\u{1F0A0}-\u{1F0FF}\u{1F100}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{FE00}-\u{FE0F}\u{203C}\u{2049}\u{20E3}\u{2139}\u{2194}-\u{2199}\u{21A9}-\u{21AA}\u{231A}-\u{231B}\u{2328}\u{23CF}\u{23E9}-\u{23F3}\u{23F8}-\u{23FA}\u{24C2}\u{25AA}-\u{25AB}\u{25B6}\u{25C0}\u{25FB}-\u{25FE}\u{2600}-\u{2604}\u{260E}\u{2611}\u{2614}-\u{2615}\u{2618}\u{261D}\u{2620}\u{2622}-\u{2623}\u{2626}\u{262A}\u{262E}-\u{262F}\u{2638}-\u{263A}\u{2640}\u{2642}\u{2648}-\u{2653}\u{265F}-\u{2660}\u{2663}\u{2665}-\u{2666}\u{2668}\u{267B}\u{267E}-\u{267F}\u{2692}-\u{2697}\u{2699}\u{269B}-\u{269C}\u{26A0}-\u{26A1}\u{26A7}\u{26AA}-\u{26AB}\u{26B0}-\u{26B1}\u{26BD}-\u{26BE}\u{26C4}-\u{26C5}\u{26C8}\u{26CE}\u{26CF}\u{26D1}\u{26D3}-\u{26D4}\u{26E9}-\u{26EA}\u{26F0}-\u{26F5}\u{26F7}-\u{26FA}\u{26FD}\u{2702}\u{2705}\u{2708}-\u{270D}\u{270F}\u{2712}\u{2714}\u{2716}\u{271D}\u{2721}\u{2728}\u{2733}-\u{2734}\u{2744}\u{2747}\u{274C}\u{274E}\u{2753}-\u{2755}\u{2757}\u{2763}-\u{2764}\u{2795}-\u{2797}\u{27A1}\u{27B0}\u{27BF}\u{2934}-\u{2935}\u{2B05}-\u{2B07}\u{2B1B}-\u{2B1C}\u{2B50}\u{2B55}\u{3030}\u{303D}\u{3297}\u{3299}]/gu;
1001
+ /**
1002
+ * Find emojis in a string
1003
+ * @param {string} text
1004
+ * @returns {RegExpMatchArray | null}
1005
+ */
1006
+ const findEmojis = (text) => {
1007
+ return text.match(EMOJI_REGEX);
1008
+ };
1009
+ /**
1010
+ * Get a preview of the emoji found
1011
+ * @param {string} text
1012
+ * @returns {string}
1013
+ */
1014
+ const getEmojiPreview = (text) => {
1015
+ const emojis = findEmojis(text);
1016
+ if (!emojis || emojis.length === 0) return "";
1017
+ const uniqueEmojis = [...new Set(emojis)];
1018
+ const preview = uniqueEmojis.slice(0, 3).join(" ");
1019
+ return uniqueEmojis.length > 3 ? `${preview} ...` : preview;
1020
+ };
1021
+ const rule$5 = defineRule({
1022
+ meta: {
1023
+ type: "problem",
1024
+ docs: {
1025
+ description: "Disallow the use of emojis in code. Use icons from a component library instead.",
1026
+ recommended: true
1027
+ },
1028
+ schema: []
1029
+ },
1030
+ createOnce(context) {
1031
+ return {
1032
+ StringLiteral(node) {
1033
+ if (node.type !== "StringLiteral") return;
1034
+ const emojis = findEmojis(node.value);
1035
+ if (emojis && emojis.length > 0) {
1036
+ const preview = getEmojiPreview(node.value);
1037
+ context.report({
1038
+ node,
1039
+ message: `Emojis are not allowed in code. Found: ${preview}. Use icons from a component library instead.`
1040
+ });
1041
+ }
1042
+ },
1043
+ TemplateLiteral(node) {
1044
+ if (node.type !== "TemplateLiteral") return;
1045
+ for (const quasi of node.quasis) {
1046
+ if (quasi.type !== "TemplateElement") continue;
1047
+ const text = quasi.value.raw;
1048
+ const emojis = findEmojis(text);
1049
+ if (emojis && emojis.length > 0) {
1050
+ const preview = getEmojiPreview(text);
1051
+ context.report({
1052
+ node: quasi,
1053
+ message: `Emojis are not allowed in code. Found: ${preview}. Use icons from a component library instead.`
1054
+ });
1055
+ }
1056
+ }
1057
+ },
1058
+ JSXText(node) {
1059
+ if (node.type !== "JSXText") return;
1060
+ const emojis = findEmojis(node.value);
1061
+ if (emojis && emojis.length > 0) {
1062
+ const preview = getEmojiPreview(node.value);
1063
+ context.report({
1064
+ node,
1065
+ message: `Emojis are not allowed in code. Found: ${preview}. Use icons from a component library instead.`
1066
+ });
1067
+ }
1068
+ }
1069
+ };
1070
+ }
1071
+ });
1072
+ const noEmojiRule = rule$5;
1073
+
1074
+ //#endregion
1075
+ //#region src/oxlint-plugins/no-finally.js
1076
+ /** @typedef {import("oxlint").ESTree.Node} ESTNode */
1077
+ const rule$4 = defineRule({
1078
+ meta: {
1079
+ type: "problem",
1080
+ docs: {
1081
+ description: "Disallow 'finally' blocks in try/catch/finally statements",
1082
+ recommended: true
1083
+ },
1084
+ schema: []
1085
+ },
1086
+ createOnce(context) {
1087
+ return { TryStatement(node) {
1088
+ if (node.type !== "TryStatement") return;
1089
+ if (node.finalizer) context.report({
1090
+ node: node.finalizer,
1091
+ message: "Use of 'finally' blocks is disallowed. Handle cleanup explicitly in try/catch blocks instead."
1092
+ });
1093
+ } };
1094
+ }
1095
+ });
1096
+ const noFinallyRule = rule$4;
781
1097
 
782
1098
  //#endregion
783
1099
  //#region src/oxlint-plugins/no-react-namespace.js
@@ -1139,7 +1455,9 @@ const plugin = definePlugin({
1139
1455
  meta: { name: "conorroberts" },
1140
1456
  rules: {
1141
1457
  "jsx-component-pascal-case": jsxComponentPascalCaseRule,
1458
+ "no-array-type": noArrayTypeRule,
1142
1459
  "no-component-date-instantiation": noComponentDateInstantiationRule,
1460
+ "no-component-pure-functions": noComponentPureFunctionsRule,
1143
1461
  "no-delete": noDeleteRule,
1144
1462
  "no-emoji": noEmojiRule,
1145
1463
  "no-finally": noFinallyRule,