@conorroberts/utils 0.0.67 → 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$10 = defineRule({
88
+ const rule$11 = defineRule({
89
89
  meta: {
90
90
  type: "problem",
91
91
  docs: {
@@ -126,20 +126,20 @@ const rule$10 = 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,11 +148,11 @@ const rule$10 = defineRule({
148
148
  };
149
149
  }
150
150
  });
151
- const jsxComponentPascalCaseRule = rule$10;
151
+ const jsxComponentPascalCaseRule = rule$11;
152
152
 
153
153
  //#endregion
154
154
  //#region src/oxlint-plugins/no-array-type.js
155
- const rule$9 = defineRule({
155
+ const rule$10 = defineRule({
156
156
  meta: {
157
157
  type: "problem",
158
158
  docs: {
@@ -171,7 +171,7 @@ const rule$9 = defineRule({
171
171
  } };
172
172
  }
173
173
  });
174
- const noArrayTypeRule = rule$9;
174
+ const noArrayTypeRule = rule$10;
175
175
 
176
176
  //#endregion
177
177
  //#region src/oxlint-plugins/no-component-date-instantiation.js
@@ -190,7 +190,7 @@ const noArrayTypeRule = rule$9;
190
190
  * @property {boolean} returnsJsx
191
191
  * @property {NewExpressionNode[]} dateInstantiations
192
192
  */
193
- const FUNCTION_NODE_TYPES$2 = new Set([
193
+ const FUNCTION_NODE_TYPES$3 = new Set([
194
194
  "FunctionDeclaration",
195
195
  "FunctionExpression",
196
196
  "ArrowFunctionExpression"
@@ -199,18 +199,18 @@ const FUNCTION_NODE_TYPES$2 = new Set([
199
199
  * @param {unknown} node
200
200
  * @returns {node is ESTNode & { type: string }}
201
201
  */
202
- 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);
203
203
  /**
204
204
  * @param {unknown} node
205
205
  * @returns {node is FunctionLikeNode}
206
206
  */
207
- 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);
208
208
  /**
209
209
  * Check if a function name follows React component naming convention (PascalCase)
210
210
  * @param {unknown} name
211
211
  * @returns {name is string}
212
212
  */
213
- const isComponentName = (name) => typeof name === "string" && /^[A-Z]/.test(name);
213
+ const isComponentName$1 = (name) => typeof name === "string" && /^[A-Z]/.test(name);
214
214
  /**
215
215
  * Get the name of a function node
216
216
  * @param {FunctionLikeNode} node
@@ -222,7 +222,7 @@ const getFunctionName$1 = (node) => {
222
222
  if (node.id.type === "Identifier") return node.id.name;
223
223
  }
224
224
  const parent = node.parent;
225
- if (!parent || !isNode$2(parent)) return "";
225
+ if (!parent || !isNode$3(parent)) return "";
226
226
  if (parent.type === "VariableDeclarator") return parent.id && parent.id.type === "Identifier" ? parent.id.name : "";
227
227
  if (parent.type === "AssignmentExpression") return parent.left && parent.left.type === "Identifier" ? parent.left.name : "";
228
228
  if (parent.type === "Property" || parent.type === "MethodDefinition") return parent.key && parent.key.type === "Identifier" ? parent.key.name : "";
@@ -234,7 +234,7 @@ const getFunctionName$1 = (node) => {
234
234
  * @returns {boolean}
235
235
  */
236
236
  const isJSXNode = (node) => {
237
- if (!node || !isNode$2(node)) return false;
237
+ if (!node || !isNode$3(node)) return false;
238
238
  return node.type === "JSXElement" || node.type === "JSXFragment";
239
239
  };
240
240
  /**
@@ -246,7 +246,7 @@ const isDateInstantiation = (node) => {
246
246
  if (node.callee.type === "Identifier" && node.callee.name === "Date") return true;
247
247
  return false;
248
248
  };
249
- const rule$8 = defineRule({
249
+ const rule$9 = defineRule({
250
250
  meta: {
251
251
  type: "problem",
252
252
  docs: {
@@ -280,7 +280,7 @@ const rule$8 = defineRule({
280
280
  const fnCtx = functionStack.pop();
281
281
  if (!fnCtx) return;
282
282
  if (!fnCtx.returnsJsx) return;
283
- if (!isComponentName(fnCtx.name)) return;
283
+ if (!isComponentName$1(fnCtx.name)) return;
284
284
  for (const dateNode of fnCtx.dateInstantiations) context.report({
285
285
  node: dateNode,
286
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.`
@@ -303,15 +303,15 @@ const rule$8 = defineRule({
303
303
  };
304
304
  return {
305
305
  FunctionDeclaration(node) {
306
- if (isFunctionLike$2(node)) enterFunction(node);
306
+ if (isFunctionLike$3(node)) enterFunction(node);
307
307
  },
308
308
  "FunctionDeclaration:exit": exitFunction,
309
309
  FunctionExpression(node) {
310
- if (isFunctionLike$2(node)) enterFunction(node);
310
+ if (isFunctionLike$3(node)) enterFunction(node);
311
311
  },
312
312
  "FunctionExpression:exit": exitFunction,
313
313
  ArrowFunctionExpression(node) {
314
- if (isFunctionLike$2(node)) enterFunction(node);
314
+ if (isFunctionLike$3(node)) enterFunction(node);
315
315
  },
316
316
  "ArrowFunctionExpression:exit": exitFunction,
317
317
  ReturnStatement(node) {
@@ -323,136 +323,7 @@ const rule$8 = defineRule({
323
323
  };
324
324
  }
325
325
  });
326
- const noComponentDateInstantiationRule = rule$8;
327
-
328
- //#endregion
329
- //#region src/oxlint-plugins/no-delete.js
330
- /** @typedef {import("oxlint").ESTree.Node} ESTNode */
331
- const rule$7 = defineRule({
332
- meta: {
333
- type: "problem",
334
- docs: {
335
- description: "Disallow the 'delete' operator",
336
- recommended: true
337
- },
338
- schema: []
339
- },
340
- createOnce(context) {
341
- return { UnaryExpression(node) {
342
- if (node.type !== "UnaryExpression") return;
343
- if (node.operator === "delete") context.report({
344
- node,
345
- message: "Use of 'delete' operator is disallowed. Use object destructuring or set properties to undefined instead."
346
- });
347
- } };
348
- }
349
- });
350
- const noDeleteRule = rule$7;
351
-
352
- //#endregion
353
- //#region src/oxlint-plugins/no-emoji.js
354
- /** @typedef {import("oxlint").ESTree.Node} ESTNode */
355
- /**
356
- * Regex pattern to match emojis
357
- * Covers most common emoji ranges in Unicode
358
- */
359
- 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;
360
- /**
361
- * Find emojis in a string
362
- * @param {string} text
363
- * @returns {RegExpMatchArray | null}
364
- */
365
- const findEmojis = (text) => {
366
- return text.match(EMOJI_REGEX);
367
- };
368
- /**
369
- * Get a preview of the emoji found
370
- * @param {string} text
371
- * @returns {string}
372
- */
373
- const getEmojiPreview = (text) => {
374
- const emojis = findEmojis(text);
375
- if (!emojis || emojis.length === 0) return "";
376
- const uniqueEmojis = [...new Set(emojis)];
377
- const preview = uniqueEmojis.slice(0, 3).join(" ");
378
- return uniqueEmojis.length > 3 ? `${preview} ...` : preview;
379
- };
380
- const rule$6 = defineRule({
381
- meta: {
382
- type: "problem",
383
- docs: {
384
- description: "Disallow the use of emojis in code. Use icons from a component library instead.",
385
- recommended: true
386
- },
387
- schema: []
388
- },
389
- createOnce(context) {
390
- return {
391
- StringLiteral(node) {
392
- if (node.type !== "StringLiteral") return;
393
- const emojis = findEmojis(node.value);
394
- if (emojis && emojis.length > 0) {
395
- const preview = getEmojiPreview(node.value);
396
- context.report({
397
- node,
398
- message: `Emojis are not allowed in code. Found: ${preview}. Use icons from a component library instead.`
399
- });
400
- }
401
- },
402
- TemplateLiteral(node) {
403
- if (node.type !== "TemplateLiteral") return;
404
- for (const quasi of node.quasis) {
405
- if (quasi.type !== "TemplateElement") continue;
406
- const text = quasi.value.raw;
407
- const emojis = findEmojis(text);
408
- if (emojis && emojis.length > 0) {
409
- const preview = getEmojiPreview(text);
410
- context.report({
411
- node: quasi,
412
- message: `Emojis are not allowed in code. Found: ${preview}. Use icons from a component library instead.`
413
- });
414
- }
415
- }
416
- },
417
- JSXText(node) {
418
- if (node.type !== "JSXText") return;
419
- const emojis = findEmojis(node.value);
420
- if (emojis && emojis.length > 0) {
421
- const preview = getEmojiPreview(node.value);
422
- context.report({
423
- node,
424
- message: `Emojis are not allowed in code. Found: ${preview}. Use icons from a component library instead.`
425
- });
426
- }
427
- }
428
- };
429
- }
430
- });
431
- const noEmojiRule = rule$6;
432
-
433
- //#endregion
434
- //#region src/oxlint-plugins/no-finally.js
435
- /** @typedef {import("oxlint").ESTree.Node} ESTNode */
436
- const rule$5 = defineRule({
437
- meta: {
438
- type: "problem",
439
- docs: {
440
- description: "Disallow 'finally' blocks in try/catch/finally statements",
441
- recommended: true
442
- },
443
- schema: []
444
- },
445
- createOnce(context) {
446
- return { TryStatement(node) {
447
- if (node.type !== "TryStatement") return;
448
- if (node.finalizer) context.report({
449
- node: node.finalizer,
450
- message: "Use of 'finally' blocks is disallowed. Handle cleanup explicitly in try/catch blocks instead."
451
- });
452
- } };
453
- }
454
- });
455
- const noFinallyRule = rule$5;
326
+ const noComponentDateInstantiationRule = rule$9;
456
327
 
457
328
  //#endregion
458
329
  //#region src/oxlint-plugins/no-inline-components.js
@@ -487,21 +358,38 @@ const noFinallyRule = rule$5;
487
358
  * @property {NestedFunctionRecord[]} nestedJsxChildren
488
359
  */
489
360
  const JSX_NODE_TYPES$1 = new Set(["JSXElement", "JSXFragment"]);
490
- const FUNCTION_NODE_TYPES$1 = new Set([
361
+ const FUNCTION_NODE_TYPES$2 = new Set([
491
362
  "FunctionDeclaration",
492
363
  "FunctionExpression",
493
364
  "ArrowFunctionExpression"
494
365
  ]);
495
366
  /**
367
+ * @param {unknown} name
368
+ * @returns {name is string}
369
+ */
370
+ const isComponentName = (name) => typeof name === "string" && /^[A-Z]/.test(name);
371
+ /**
496
372
  * @param {unknown} node
497
373
  * @returns {node is ESTNode & { type: string }}
498
374
  */
499
- 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);
500
376
  /**
501
377
  * @param {unknown} node
502
378
  * @returns {node is FunctionLikeNode}
503
379
  */
504
- 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
+ };
505
393
  /**
506
394
  * @param {FunctionLikeNode} node
507
395
  */
@@ -509,16 +397,16 @@ const isFunctionUsedAsJsxProp = (node) => {
509
397
  const checkJsxProp = (current) => {
510
398
  if (!current) return false;
511
399
  if (current.type === "JSXAttribute") return true;
512
- if (isFunctionLike$1(current)) return false;
513
- 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);
514
402
  };
515
- return checkJsxProp(isNode$1(node) ? node.parent ?? null : null);
403
+ return checkJsxProp(isNode$2(node) ? node.parent ?? null : null);
516
404
  };
517
405
  /**
518
406
  * @param {FunctionLikeNode} node
519
407
  */
520
408
  const isFunctionImmediatelyInvoked = (node) => {
521
- const parent = isNode$1(node) ? node.parent ?? null : null;
409
+ const parent = isNode$2(node) ? node.parent ?? null : null;
522
410
  if (!parent) return false;
523
411
  if (parent.type === "CallExpression" && parent.callee === node) return true;
524
412
  return false;
@@ -527,20 +415,20 @@ const isFunctionImmediatelyInvoked = (node) => {
527
415
  * @param {ESTExpression | null | undefined} root
528
416
  */
529
417
  const expressionContainsJsx$1 = (root) => {
530
- if (!root || !isNode$1(root)) return false;
418
+ if (!root || !isNode$2(root)) return false;
531
419
  const stack = [root];
532
420
  while (stack.length > 0) {
533
421
  const current = stack.pop();
534
- if (!current || !isNode$1(current)) continue;
422
+ if (!current || !isNode$2(current)) continue;
535
423
  if (JSX_NODE_TYPES$1.has(current.type)) return true;
536
- if (FUNCTION_NODE_TYPES$1.has(current.type) && current !== root) continue;
424
+ if (FUNCTION_NODE_TYPES$2.has(current.type) && current !== root) continue;
537
425
  for (const key of Object.keys(current)) {
538
426
  if (key === "parent") continue;
539
427
  const value = current[key];
540
428
  if (!value) continue;
541
429
  if (Array.isArray(value)) {
542
- for (const element of value) if (isNode$1(element)) stack.push(element);
543
- } 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);
544
432
  }
545
433
  }
546
434
  return false;
@@ -552,7 +440,7 @@ const expressionContainsJsx$1 = (root) => {
552
440
  const expressionProducesJsx = (root, bindingNames) => {
553
441
  if (!root) return false;
554
442
  if (expressionContainsJsx$1(root)) return true;
555
- if (!isNode$1(root)) return false;
443
+ if (!isNode$2(root)) return false;
556
444
  const type = root.type;
557
445
  if (type === "Identifier") return bindingNames.has(root.name);
558
446
  if (type === "ConditionalExpression") return expressionProducesJsx(root.consequent, bindingNames) || expressionProducesJsx(root.alternate, bindingNames);
@@ -578,7 +466,7 @@ const expressionProducesJsx = (root, bindingNames) => {
578
466
  * @param {string[]} names
579
467
  */
580
468
  const collectBindingNames = (pattern, names) => {
581
- if (!pattern || !isNode$1(pattern)) return;
469
+ if (!pattern || !isNode$2(pattern)) return;
582
470
  const type = pattern.type;
583
471
  if (type === "Identifier") {
584
472
  names.push(pattern.name);
@@ -615,13 +503,13 @@ const getFunctionName = (node) => {
615
503
  if (node.id.type === "Identifier") return node.id.name;
616
504
  }
617
505
  const parent = node.parent;
618
- if (!parent || !isNode$1(parent)) return "";
506
+ if (!parent || !isNode$2(parent)) return "";
619
507
  if (parent.type === "VariableDeclarator") return parent.id && parent.id.type === "Identifier" ? parent.id.name : "";
620
508
  if (parent.type === "AssignmentExpression") return parent.left && parent.left.type === "Identifier" ? parent.left.name : "";
621
509
  if (parent.type === "Property" || parent.type === "MethodDefinition") return parent.key && parent.key.type === "Identifier" ? parent.key.name : "";
622
510
  if (parent.type === "CallExpression") {
623
511
  const callParent = parent.parent;
624
- if (callParent && isNode$1(callParent)) {
512
+ if (callParent && isNode$2(callParent)) {
625
513
  if (callParent.type === "VariableDeclarator") return callParent.id && callParent.id.type === "Identifier" ? callParent.id.name : "";
626
514
  if (callParent.type === "AssignmentExpression") return callParent.left && callParent.left.type === "Identifier" ? callParent.left.name : "";
627
515
  }
@@ -652,7 +540,7 @@ const createNestedFunctionMessage = (childName, parentName) => `JSX-returning ${
652
540
  * @param {string} name
653
541
  */
654
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.`;
655
- const rule$4 = defineRule({
543
+ const rule$8 = defineRule({
656
544
  meta: {
657
545
  type: "problem",
658
546
  docs: {
@@ -713,7 +601,7 @@ const rule$4 = defineRule({
713
601
  const fnCtx = currentFunction();
714
602
  if (!fnCtx) return;
715
603
  const argument = node.argument;
716
- if (!argument || isFunctionLike$1(argument)) return;
604
+ if (!argument || isFunctionLike$2(argument)) return;
717
605
  if (expressionProducesJsx(argument, fnCtx.jsxBindingNames)) fnCtx.returnsJsx = true;
718
606
  };
719
607
  /** @param {VariableDeclaratorNode} node */
@@ -721,7 +609,7 @@ const rule$4 = defineRule({
721
609
  const fnCtx = currentFunction();
722
610
  if (!fnCtx) return;
723
611
  const init = node.init;
724
- if (!init || isFunctionLike$1(init)) return;
612
+ if (!init || isFunctionLike$2(init)) return;
725
613
  if (!expressionContainsJsx$1(init)) return;
726
614
  const names = [];
727
615
  collectBindingNames(node.id, names);
@@ -737,7 +625,7 @@ const rule$4 = defineRule({
737
625
  const fnCtx = currentFunction();
738
626
  if (!fnCtx) return;
739
627
  const right = node.right;
740
- if (!right || isFunctionLike$1(right)) return;
628
+ if (!right || isFunctionLike$2(right)) return;
741
629
  if (!expressionContainsJsx$1(right)) return;
742
630
  const names = [];
743
631
  if (node.left && node.left.type === "Identifier") {
@@ -774,15 +662,15 @@ const rule$4 = defineRule({
774
662
  };
775
663
  return {
776
664
  FunctionDeclaration(node) {
777
- if (isFunctionLike$1(node)) enterFunction(node);
665
+ if (isFunctionLike$2(node)) enterFunction(node);
778
666
  },
779
667
  "FunctionDeclaration:exit": exitFunction,
780
668
  FunctionExpression(node) {
781
- if (isFunctionLike$1(node)) enterFunction(node);
669
+ if (isFunctionLike$2(node)) enterFunction(node);
782
670
  },
783
671
  "FunctionExpression:exit": exitFunction,
784
672
  ArrowFunctionExpression(node) {
785
- if (isFunctionLike$1(node)) enterFunction(node);
673
+ if (isFunctionLike$2(node)) enterFunction(node);
786
674
  },
787
675
  "ArrowFunctionExpression:exit": exitFunction,
788
676
  ReturnStatement(node) {
@@ -800,7 +688,412 @@ const rule$4 = defineRule({
800
688
  };
801
689
  }
802
690
  });
803
- 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;
804
1097
 
805
1098
  //#endregion
806
1099
  //#region src/oxlint-plugins/no-react-namespace.js
@@ -1164,6 +1457,7 @@ const plugin = definePlugin({
1164
1457
  "jsx-component-pascal-case": jsxComponentPascalCaseRule,
1165
1458
  "no-array-type": noArrayTypeRule,
1166
1459
  "no-component-date-instantiation": noComponentDateInstantiationRule,
1460
+ "no-component-pure-functions": noComponentPureFunctionsRule,
1167
1461
  "no-delete": noDeleteRule,
1168
1462
  "no-emoji": noEmojiRule,
1169
1463
  "no-finally": noFinallyRule,