@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.
- package/dist/env.mjs +1 -4
- package/dist/env.mjs.map +1 -1
- package/dist/oxlint/config.json +2 -1
- package/dist/oxlint/index.mjs +477 -183
- package/dist/oxlint/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/oxlint/index.mjs
CHANGED
|
@@ -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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
74
|
+
if (!current || !isNode$4(current)) continue;
|
|
75
75
|
if (JSX_NODE_TYPES$2.has(current.type)) return true;
|
|
76
|
-
if (FUNCTION_NODE_TYPES$
|
|
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$
|
|
83
|
-
} else if (isNode$
|
|
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$
|
|
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$
|
|
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$
|
|
134
|
+
if (isFunctionLike$4(node)) enterFunction(node);
|
|
135
135
|
},
|
|
136
136
|
"FunctionDeclaration:exit": exitFunction,
|
|
137
137
|
FunctionExpression(node) {
|
|
138
|
-
if (isFunctionLike$
|
|
138
|
+
if (isFunctionLike$4(node)) enterFunction(node);
|
|
139
139
|
},
|
|
140
140
|
"FunctionExpression:exit": exitFunction,
|
|
141
141
|
ArrowFunctionExpression(node) {
|
|
142
|
-
if (isFunctionLike$
|
|
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$
|
|
151
|
+
const jsxComponentPascalCaseRule = rule$11;
|
|
152
152
|
|
|
153
153
|
//#endregion
|
|
154
154
|
//#region src/oxlint-plugins/no-array-type.js
|
|
155
|
-
const rule$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
306
|
+
if (isFunctionLike$3(node)) enterFunction(node);
|
|
307
307
|
},
|
|
308
308
|
"FunctionDeclaration:exit": exitFunction,
|
|
309
309
|
FunctionExpression(node) {
|
|
310
|
-
if (isFunctionLike$
|
|
310
|
+
if (isFunctionLike$3(node)) enterFunction(node);
|
|
311
311
|
},
|
|
312
312
|
"FunctionExpression:exit": exitFunction,
|
|
313
313
|
ArrowFunctionExpression(node) {
|
|
314
|
-
if (isFunctionLike$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
513
|
-
return checkJsxProp(isNode$
|
|
400
|
+
if (isFunctionLike$2(current)) return false;
|
|
401
|
+
return checkJsxProp(isNode$2(current) ? current.parent ?? null : null);
|
|
514
402
|
};
|
|
515
|
-
return checkJsxProp(isNode$
|
|
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$
|
|
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$
|
|
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$
|
|
422
|
+
if (!current || !isNode$2(current)) continue;
|
|
535
423
|
if (JSX_NODE_TYPES$1.has(current.type)) return true;
|
|
536
|
-
if (FUNCTION_NODE_TYPES$
|
|
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$
|
|
543
|
-
} else if (isNode$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
665
|
+
if (isFunctionLike$2(node)) enterFunction(node);
|
|
778
666
|
},
|
|
779
667
|
"FunctionDeclaration:exit": exitFunction,
|
|
780
668
|
FunctionExpression(node) {
|
|
781
|
-
if (isFunctionLike$
|
|
669
|
+
if (isFunctionLike$2(node)) enterFunction(node);
|
|
782
670
|
},
|
|
783
671
|
"FunctionExpression:exit": exitFunction,
|
|
784
672
|
ArrowFunctionExpression(node) {
|
|
785
|
-
if (isFunctionLike$
|
|
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$
|
|
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,
|