@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.
- package/dist/env.mjs +1 -4
- package/dist/env.mjs.map +1 -1
- package/dist/oxlint/config.json +4 -2
- package/dist/oxlint/index.mjs +499 -181
- 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$9 = 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,7 +148,30 @@ const rule$9 = defineRule({
|
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
});
|
|
151
|
-
const jsxComponentPascalCaseRule = rule$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
306
|
+
if (isFunctionLike$3(node)) enterFunction(node);
|
|
284
307
|
},
|
|
285
308
|
"FunctionDeclaration:exit": exitFunction,
|
|
286
309
|
FunctionExpression(node) {
|
|
287
|
-
if (isFunctionLike$
|
|
310
|
+
if (isFunctionLike$3(node)) enterFunction(node);
|
|
288
311
|
},
|
|
289
312
|
"FunctionExpression:exit": exitFunction,
|
|
290
313
|
ArrowFunctionExpression(node) {
|
|
291
|
-
if (isFunctionLike$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
490
|
-
return checkJsxProp(isNode$
|
|
400
|
+
if (isFunctionLike$2(current)) return false;
|
|
401
|
+
return checkJsxProp(isNode$2(current) ? current.parent ?? null : null);
|
|
491
402
|
};
|
|
492
|
-
return checkJsxProp(isNode$
|
|
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$
|
|
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$
|
|
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$
|
|
422
|
+
if (!current || !isNode$2(current)) continue;
|
|
512
423
|
if (JSX_NODE_TYPES$1.has(current.type)) return true;
|
|
513
|
-
if (FUNCTION_NODE_TYPES$
|
|
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$
|
|
520
|
-
} 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);
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
665
|
+
if (isFunctionLike$2(node)) enterFunction(node);
|
|
755
666
|
},
|
|
756
667
|
"FunctionDeclaration:exit": exitFunction,
|
|
757
668
|
FunctionExpression(node) {
|
|
758
|
-
if (isFunctionLike$
|
|
669
|
+
if (isFunctionLike$2(node)) enterFunction(node);
|
|
759
670
|
},
|
|
760
671
|
"FunctionExpression:exit": exitFunction,
|
|
761
672
|
ArrowFunctionExpression(node) {
|
|
762
|
-
if (isFunctionLike$
|
|
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$
|
|
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,
|