eslint-plugin-nextfriday 1.5.3 → 1.7.0

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/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
  var package_default = {
3
3
  name: "eslint-plugin-nextfriday",
4
- version: "1.5.3",
4
+ version: "1.7.0",
5
5
  description: "A comprehensive ESLint plugin providing custom rules and configurations for Next Friday development workflows.",
6
6
  keywords: [
7
7
  "eslint",
@@ -115,12 +115,143 @@ var package_default = {
115
115
  }
116
116
  };
117
117
 
118
- // src/rules/enforce-readonly-component-props.ts
119
- import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
118
+ // src/rules/boolean-naming-prefix.ts
119
+ import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
120
120
  var createRule = ESLintUtils.RuleCreator(
121
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
121
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
122
122
  );
123
- var enforceReadonlyComponentProps = createRule({
123
+ var BOOLEAN_PREFIXES = ["is", "has", "should", "can", "did", "will", "was", "are", "does", "had"];
124
+ var startsWithBooleanPrefix = (name) => {
125
+ const lowerName = name.toLowerCase();
126
+ return BOOLEAN_PREFIXES.some((prefix) => {
127
+ if (!lowerName.startsWith(prefix)) {
128
+ return false;
129
+ }
130
+ if (name.length === prefix.length) {
131
+ return true;
132
+ }
133
+ const nextChar = name.charAt(prefix.length);
134
+ return nextChar === nextChar.toUpperCase();
135
+ });
136
+ };
137
+ var isBooleanLiteral = (node) => node.type === AST_NODE_TYPES.Literal && typeof node.value === "boolean";
138
+ var isBooleanExpression = (node) => {
139
+ if (isBooleanLiteral(node)) {
140
+ return true;
141
+ }
142
+ if (node.type === AST_NODE_TYPES.UnaryExpression && node.operator === "!") {
143
+ return true;
144
+ }
145
+ if (node.type === AST_NODE_TYPES.BinaryExpression) {
146
+ const comparisonOperators = ["===", "!==", "==", "!=", "<", ">", "<=", ">=", "in", "instanceof"];
147
+ return comparisonOperators.includes(node.operator);
148
+ }
149
+ if (node.type === AST_NODE_TYPES.LogicalExpression) {
150
+ return node.operator === "&&" || node.operator === "||";
151
+ }
152
+ return false;
153
+ };
154
+ var hasBooleanTypeAnnotation = (node) => {
155
+ if (node.type === AST_NODE_TYPES.Identifier) {
156
+ const { typeAnnotation } = node;
157
+ if (typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES.TSBooleanKeyword) {
158
+ return true;
159
+ }
160
+ }
161
+ if ("id" in node && node.id.type === AST_NODE_TYPES.Identifier) {
162
+ const { typeAnnotation } = node.id;
163
+ if (typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES.TSBooleanKeyword) {
164
+ return true;
165
+ }
166
+ }
167
+ return false;
168
+ };
169
+ var booleanNamingPrefix = createRule({
170
+ name: "boolean-naming-prefix",
171
+ meta: {
172
+ type: "suggestion",
173
+ docs: {
174
+ description: "Enforce boolean variables and parameters to have a prefix like is, has, should, can, did, will for better readability"
175
+ },
176
+ messages: {
177
+ missingPrefix: "Boolean variable '{{name}}' should have a prefix like is, has, should, can, did, or will. Example: 'is{{suggestedName}}' or 'has{{suggestedName}}'."
178
+ },
179
+ schema: []
180
+ },
181
+ defaultOptions: [],
182
+ create(context) {
183
+ const checkBooleanNaming = (name, node) => {
184
+ if (startsWithBooleanPrefix(name)) {
185
+ return;
186
+ }
187
+ const suggestedName = name.charAt(0).toUpperCase() + name.slice(1);
188
+ context.report({
189
+ node,
190
+ messageId: "missingPrefix",
191
+ data: { name, suggestedName }
192
+ });
193
+ };
194
+ return {
195
+ VariableDeclarator(node) {
196
+ if (node.id.type !== AST_NODE_TYPES.Identifier) {
197
+ return;
198
+ }
199
+ const { name } = node.id;
200
+ if (hasBooleanTypeAnnotation(node)) {
201
+ checkBooleanNaming(name, node.id);
202
+ return;
203
+ }
204
+ if (node.init && isBooleanExpression(node.init)) {
205
+ checkBooleanNaming(name, node.id);
206
+ }
207
+ },
208
+ FunctionDeclaration(node) {
209
+ node.params.forEach((param) => {
210
+ if (param.type === AST_NODE_TYPES.Identifier && hasBooleanTypeAnnotation(param)) {
211
+ checkBooleanNaming(param.name, param);
212
+ }
213
+ if (param.type === AST_NODE_TYPES.AssignmentPattern) {
214
+ if (param.left.type === AST_NODE_TYPES.Identifier && param.right && isBooleanLiteral(param.right)) {
215
+ checkBooleanNaming(param.left.name, param.left);
216
+ }
217
+ }
218
+ });
219
+ },
220
+ FunctionExpression(node) {
221
+ node.params.forEach((param) => {
222
+ if (param.type === AST_NODE_TYPES.Identifier && hasBooleanTypeAnnotation(param)) {
223
+ checkBooleanNaming(param.name, param);
224
+ }
225
+ if (param.type === AST_NODE_TYPES.AssignmentPattern) {
226
+ if (param.left.type === AST_NODE_TYPES.Identifier && param.right && isBooleanLiteral(param.right)) {
227
+ checkBooleanNaming(param.left.name, param.left);
228
+ }
229
+ }
230
+ });
231
+ },
232
+ ArrowFunctionExpression(node) {
233
+ node.params.forEach((param) => {
234
+ if (param.type === AST_NODE_TYPES.Identifier && hasBooleanTypeAnnotation(param)) {
235
+ checkBooleanNaming(param.name, param);
236
+ }
237
+ if (param.type === AST_NODE_TYPES.AssignmentPattern) {
238
+ if (param.left.type === AST_NODE_TYPES.Identifier && param.right && isBooleanLiteral(param.right)) {
239
+ checkBooleanNaming(param.left.name, param.left);
240
+ }
241
+ }
242
+ });
243
+ }
244
+ };
245
+ }
246
+ });
247
+ var boolean_naming_prefix_default = booleanNamingPrefix;
248
+
249
+ // src/rules/enforce-readonly-component-props.ts
250
+ import { AST_NODE_TYPES as AST_NODE_TYPES2, ESLintUtils as ESLintUtils2 } from "@typescript-eslint/utils";
251
+ var createRule2 = ESLintUtils2.RuleCreator(
252
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
253
+ );
254
+ var enforceReadonlyComponentProps = createRule2({
124
255
  name: "enforce-readonly-component-props",
125
256
  meta: {
126
257
  type: "suggestion",
@@ -136,54 +267,54 @@ var enforceReadonlyComponentProps = createRule({
136
267
  defaultOptions: [],
137
268
  create(context) {
138
269
  function hasJSXInConditional(node) {
139
- return node.consequent.type === AST_NODE_TYPES.JSXElement || node.consequent.type === AST_NODE_TYPES.JSXFragment || node.alternate.type === AST_NODE_TYPES.JSXElement || node.alternate.type === AST_NODE_TYPES.JSXFragment;
270
+ return node.consequent.type === AST_NODE_TYPES2.JSXElement || node.consequent.type === AST_NODE_TYPES2.JSXFragment || node.alternate.type === AST_NODE_TYPES2.JSXElement || node.alternate.type === AST_NODE_TYPES2.JSXFragment;
140
271
  }
141
272
  function hasJSXInLogical(node) {
142
- return node.right.type === AST_NODE_TYPES.JSXElement || node.right.type === AST_NODE_TYPES.JSXFragment;
273
+ return node.right.type === AST_NODE_TYPES2.JSXElement || node.right.type === AST_NODE_TYPES2.JSXFragment;
143
274
  }
144
275
  function hasJSXReturn(block) {
145
276
  return block.body.some((stmt) => {
146
- if (stmt.type === AST_NODE_TYPES.ReturnStatement && stmt.argument) {
147
- return stmt.argument.type === AST_NODE_TYPES.JSXElement || stmt.argument.type === AST_NODE_TYPES.JSXFragment || stmt.argument.type === AST_NODE_TYPES.ConditionalExpression && hasJSXInConditional(stmt.argument) || stmt.argument.type === AST_NODE_TYPES.LogicalExpression && hasJSXInLogical(stmt.argument);
277
+ if (stmt.type === AST_NODE_TYPES2.ReturnStatement && stmt.argument) {
278
+ return stmt.argument.type === AST_NODE_TYPES2.JSXElement || stmt.argument.type === AST_NODE_TYPES2.JSXFragment || stmt.argument.type === AST_NODE_TYPES2.ConditionalExpression && hasJSXInConditional(stmt.argument) || stmt.argument.type === AST_NODE_TYPES2.LogicalExpression && hasJSXInLogical(stmt.argument);
148
279
  }
149
280
  return false;
150
281
  });
151
282
  }
152
- function isReactComponent(node) {
153
- if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) {
154
- if (node.body.type === AST_NODE_TYPES.JSXElement || node.body.type === AST_NODE_TYPES.JSXFragment) {
283
+ function isReactComponent2(node) {
284
+ if (node.type === AST_NODE_TYPES2.ArrowFunctionExpression) {
285
+ if (node.body.type === AST_NODE_TYPES2.JSXElement || node.body.type === AST_NODE_TYPES2.JSXFragment) {
155
286
  return true;
156
287
  }
157
- if (node.body.type === AST_NODE_TYPES.BlockStatement) {
288
+ if (node.body.type === AST_NODE_TYPES2.BlockStatement) {
158
289
  return hasJSXReturn(node.body);
159
290
  }
160
- } else if (node.type === AST_NODE_TYPES.FunctionExpression || node.type === AST_NODE_TYPES.FunctionDeclaration) {
161
- if (node.body && node.body.type === AST_NODE_TYPES.BlockStatement) {
291
+ } else if (node.type === AST_NODE_TYPES2.FunctionExpression || node.type === AST_NODE_TYPES2.FunctionDeclaration) {
292
+ if (node.body && node.body.type === AST_NODE_TYPES2.BlockStatement) {
162
293
  return hasJSXReturn(node.body);
163
294
  }
164
295
  }
165
296
  return false;
166
297
  }
167
298
  function isNamedType(node) {
168
- return node.type === AST_NODE_TYPES.TSTypeReference;
299
+ return node.type === AST_NODE_TYPES2.TSTypeReference;
169
300
  }
170
301
  function isAlreadyReadonly(node) {
171
- if (node.type === AST_NODE_TYPES.TSTypeReference && node.typeName) {
172
- if (node.typeName.type === AST_NODE_TYPES.Identifier && node.typeName.name === "Readonly") {
302
+ if (node.type === AST_NODE_TYPES2.TSTypeReference && node.typeName) {
303
+ if (node.typeName.type === AST_NODE_TYPES2.Identifier && node.typeName.name === "Readonly") {
173
304
  return true;
174
305
  }
175
306
  }
176
307
  return false;
177
308
  }
178
309
  function checkFunction(node) {
179
- if (!isReactComponent(node)) {
310
+ if (!isReactComponent2(node)) {
180
311
  return;
181
312
  }
182
313
  if (node.params.length !== 1) {
183
314
  return;
184
315
  }
185
316
  const param = node.params[0];
186
- if (param.type === AST_NODE_TYPES.Identifier && param.typeAnnotation) {
317
+ if (param.type === AST_NODE_TYPES2.Identifier && param.typeAnnotation) {
187
318
  const { typeAnnotation } = param.typeAnnotation;
188
319
  if (isNamedType(typeAnnotation) && !isAlreadyReadonly(typeAnnotation)) {
189
320
  const { sourceCode } = context;
@@ -207,11 +338,132 @@ var enforceReadonlyComponentProps = createRule({
207
338
  });
208
339
  var enforce_readonly_component_props_default = enforceReadonlyComponentProps;
209
340
 
341
+ // src/rules/enforce-sorted-destructuring.ts
342
+ import { AST_NODE_TYPES as AST_NODE_TYPES3, ESLintUtils as ESLintUtils3 } from "@typescript-eslint/utils";
343
+ var createRule3 = ESLintUtils3.RuleCreator(
344
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
345
+ );
346
+ var enforceSortedDestructuring = createRule3({
347
+ name: "enforce-sorted-destructuring",
348
+ meta: {
349
+ type: "suggestion",
350
+ docs: {
351
+ description: "Enforce alphabetical sorting of destructured properties with defaults first"
352
+ },
353
+ schema: [],
354
+ messages: {
355
+ unsortedDestructuring: "Destructured properties should be sorted alphabetically. Properties with defaults should come first, sorted by type (string, number, boolean, object) then alphabetically."
356
+ }
357
+ },
358
+ defaultOptions: [],
359
+ create(context) {
360
+ function getPropertyName(property) {
361
+ if (property.type === AST_NODE_TYPES3.RestElement) {
362
+ return null;
363
+ }
364
+ if (property.key.type === AST_NODE_TYPES3.Identifier) {
365
+ return property.key.name;
366
+ }
367
+ return null;
368
+ }
369
+ function hasDefaultValue(property) {
370
+ return property.value.type === AST_NODE_TYPES3.AssignmentPattern && Boolean(property.value.right);
371
+ }
372
+ function getDefaultValueType(property) {
373
+ if (!hasDefaultValue(property)) {
374
+ return "none";
375
+ }
376
+ const assignmentPattern = property.value;
377
+ const { right } = assignmentPattern;
378
+ if (!right) {
379
+ return "none";
380
+ }
381
+ switch (right.type) {
382
+ case AST_NODE_TYPES3.Literal:
383
+ if (typeof right.value === "string") {
384
+ return "string";
385
+ }
386
+ if (typeof right.value === "number") {
387
+ return "number";
388
+ }
389
+ if (typeof right.value === "boolean") {
390
+ return "boolean";
391
+ }
392
+ return "other";
393
+ case AST_NODE_TYPES3.TemplateLiteral:
394
+ return "string";
395
+ case AST_NODE_TYPES3.ObjectExpression:
396
+ case AST_NODE_TYPES3.ArrayExpression:
397
+ return "object";
398
+ default:
399
+ return "other";
400
+ }
401
+ }
402
+ function getTypeSortOrder(type) {
403
+ const order = {
404
+ string: 0,
405
+ number: 1,
406
+ boolean: 2,
407
+ object: 3,
408
+ other: 4,
409
+ none: 5
410
+ };
411
+ return order[type] ?? 5;
412
+ }
413
+ function checkVariableDeclarator(node) {
414
+ if (node.id.type !== AST_NODE_TYPES3.ObjectPattern) {
415
+ return;
416
+ }
417
+ const { properties } = node.id;
418
+ if (properties.length < 2) {
419
+ return;
420
+ }
421
+ const propertyInfo = properties.map((prop) => {
422
+ if (prop.type === AST_NODE_TYPES3.RestElement) {
423
+ return null;
424
+ }
425
+ return {
426
+ property: prop,
427
+ name: getPropertyName(prop),
428
+ hasDefault: hasDefaultValue(prop),
429
+ defaultType: getDefaultValueType(prop)
430
+ };
431
+ }).filter((info) => info !== null && info.name !== null);
432
+ const sorted = [...propertyInfo].sort((a, b) => {
433
+ if (a.hasDefault && !b.hasDefault) {
434
+ return -1;
435
+ }
436
+ if (!a.hasDefault && b.hasDefault) {
437
+ return 1;
438
+ }
439
+ if (a.hasDefault && b.hasDefault) {
440
+ const typeComparison = getTypeSortOrder(a.defaultType) - getTypeSortOrder(b.defaultType);
441
+ if (typeComparison !== 0) {
442
+ return typeComparison;
443
+ }
444
+ }
445
+ return a.name.localeCompare(b.name);
446
+ });
447
+ const isSorted = propertyInfo.every((info, index) => info.name === sorted[index].name);
448
+ if (!isSorted) {
449
+ context.report({
450
+ node: node.id,
451
+ messageId: "unsortedDestructuring"
452
+ });
453
+ }
454
+ }
455
+ return {
456
+ VariableDeclarator: checkVariableDeclarator
457
+ };
458
+ }
459
+ });
460
+ var enforce_sorted_destructuring_default = enforceSortedDestructuring;
461
+
210
462
  // src/rules/file-kebab-case.ts
211
463
  import path from "path";
212
- import { ESLintUtils as ESLintUtils2 } from "@typescript-eslint/utils";
213
- var createRule2 = ESLintUtils2.RuleCreator(
214
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
464
+ import { ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
465
+ var createRule4 = ESLintUtils4.RuleCreator(
466
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
215
467
  );
216
468
  var isKebabCase = (str) => {
217
469
  if (/\.(config|rc|setup|spec|test)$/.test(str) || /^[a-z0-9]+(?:-[a-z0-9]+)*\.[a-z0-9]+(?:-[a-z0-9]+)*$/.test(str)) {
@@ -219,7 +471,7 @@ var isKebabCase = (str) => {
219
471
  }
220
472
  return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(str);
221
473
  };
222
- var fileKebabCase = createRule2({
474
+ var fileKebabCase = createRule4({
223
475
  name: "file-kebab-case",
224
476
  meta: {
225
477
  type: "problem",
@@ -240,8 +492,8 @@ var fileKebabCase = createRule2({
240
492
  if (ext !== ".ts" && ext !== ".js") {
241
493
  return;
242
494
  }
243
- const basename = path.basename(filename, ext);
244
- if (!isKebabCase(basename)) {
495
+ const basename2 = path.basename(filename, ext);
496
+ if (!isKebabCase(basename2)) {
245
497
  context.report({
246
498
  loc: { line: 1, column: 0 },
247
499
  messageId: "fileKebabCase"
@@ -255,12 +507,12 @@ var file_kebab_case_default = fileKebabCase;
255
507
 
256
508
  // src/rules/jsx-pascal-case.ts
257
509
  import path2 from "path";
258
- import { ESLintUtils as ESLintUtils3 } from "@typescript-eslint/utils";
259
- var createRule3 = ESLintUtils3.RuleCreator(
260
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
510
+ import { ESLintUtils as ESLintUtils5 } from "@typescript-eslint/utils";
511
+ var createRule5 = ESLintUtils5.RuleCreator(
512
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
261
513
  );
262
514
  var isPascalCase = (str) => /^[A-Z][a-zA-Z0-9]*$/.test(str) && !/^[A-Z]+$/.test(str);
263
- var jsxPascalCase = createRule3({
515
+ var jsxPascalCase = createRule5({
264
516
  name: "jsx-pascal-case",
265
517
  meta: {
266
518
  type: "problem",
@@ -281,8 +533,8 @@ var jsxPascalCase = createRule3({
281
533
  if (ext !== ".jsx" && ext !== ".tsx") {
282
534
  return;
283
535
  }
284
- const basename = path2.basename(filename, ext);
285
- if (!isPascalCase(basename)) {
536
+ const basename2 = path2.basename(filename, ext);
537
+ if (!isPascalCase(basename2)) {
286
538
  context.report({
287
539
  loc: { line: 1, column: 0 },
288
540
  messageId: "jsxPascalCase"
@@ -294,13 +546,136 @@ var jsxPascalCase = createRule3({
294
546
  });
295
547
  var jsx_pascal_case_default = jsxPascalCase;
296
548
 
549
+ // src/rules/no-direct-date.ts
550
+ import { ESLintUtils as ESLintUtils6, AST_NODE_TYPES as AST_NODE_TYPES4 } from "@typescript-eslint/utils";
551
+ var createRule6 = ESLintUtils6.RuleCreator(
552
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
553
+ );
554
+ var noDirectDate = createRule6({
555
+ name: "no-direct-date",
556
+ meta: {
557
+ type: "problem",
558
+ docs: {
559
+ description: "Disallow direct usage of Date constructor and methods to enforce centralized date utilities"
560
+ },
561
+ messages: {
562
+ noNewDate: "Avoid using 'new Date()'. Use a centralized date utility like dayjs instead.",
563
+ noDateNow: "Avoid using 'Date.now()'. Use a centralized date utility like dayjs instead.",
564
+ noDateParse: "Avoid using 'Date.parse()'. Use a centralized date utility like dayjs instead."
565
+ },
566
+ schema: []
567
+ },
568
+ defaultOptions: [],
569
+ create(context) {
570
+ return {
571
+ NewExpression(node) {
572
+ if (node.callee.type === AST_NODE_TYPES4.Identifier && node.callee.name === "Date") {
573
+ context.report({
574
+ node,
575
+ messageId: "noNewDate"
576
+ });
577
+ }
578
+ },
579
+ CallExpression(node) {
580
+ if (node.callee.type === AST_NODE_TYPES4.MemberExpression && node.callee.object.type === AST_NODE_TYPES4.Identifier && node.callee.object.name === "Date" && node.callee.property.type === AST_NODE_TYPES4.Identifier) {
581
+ const methodName = node.callee.property.name;
582
+ if (methodName === "now") {
583
+ context.report({
584
+ node,
585
+ messageId: "noDateNow"
586
+ });
587
+ }
588
+ if (methodName === "parse") {
589
+ context.report({
590
+ node,
591
+ messageId: "noDateParse"
592
+ });
593
+ }
594
+ }
595
+ }
596
+ };
597
+ }
598
+ });
599
+ var no_direct_date_default = noDirectDate;
600
+
601
+ // src/rules/jsx-no-variable-in-callback.ts
602
+ import { AST_NODE_TYPES as AST_NODE_TYPES5, ESLintUtils as ESLintUtils7 } from "@typescript-eslint/utils";
603
+ var createRule7 = ESLintUtils7.RuleCreator(
604
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
605
+ );
606
+ var jsxNoVariableInCallback = createRule7({
607
+ name: "jsx-no-variable-in-callback",
608
+ meta: {
609
+ type: "suggestion",
610
+ docs: {
611
+ description: "Disallow variable declarations inside callback functions within JSX"
612
+ },
613
+ schema: [],
614
+ messages: {
615
+ noVariableInCallback: "Variable declarations should not be inside callback functions within JSX. Extract the logic to a separate function outside the JSX."
616
+ }
617
+ },
618
+ defaultOptions: [],
619
+ create(context) {
620
+ function isInsideJSX(node) {
621
+ let current = node.parent;
622
+ while (current) {
623
+ if (current.type === AST_NODE_TYPES5.JSXElement || current.type === AST_NODE_TYPES5.JSXFragment) {
624
+ return true;
625
+ }
626
+ current = current.parent;
627
+ }
628
+ return false;
629
+ }
630
+ function isCallbackInJSX(node) {
631
+ if (!node.parent) {
632
+ return false;
633
+ }
634
+ if (!isInsideJSX(node)) {
635
+ return false;
636
+ }
637
+ if (node.parent.type === AST_NODE_TYPES5.CallExpression || node.parent.type === AST_NODE_TYPES5.JSXExpressionContainer) {
638
+ return true;
639
+ }
640
+ if (node.parent.type === AST_NODE_TYPES5.ArrayExpression && node.parent.parent) {
641
+ if (node.parent.parent.type === AST_NODE_TYPES5.CallExpression || node.parent.parent.type === AST_NODE_TYPES5.JSXExpressionContainer) {
642
+ return true;
643
+ }
644
+ }
645
+ return false;
646
+ }
647
+ function checkFunctionBody(node) {
648
+ if (!isCallbackInJSX(node)) {
649
+ return;
650
+ }
651
+ const { body } = node;
652
+ if (body.type !== AST_NODE_TYPES5.BlockStatement) {
653
+ return;
654
+ }
655
+ body.body.forEach((statement) => {
656
+ if (statement.type === AST_NODE_TYPES5.VariableDeclaration) {
657
+ context.report({
658
+ node: statement,
659
+ messageId: "noVariableInCallback"
660
+ });
661
+ }
662
+ });
663
+ }
664
+ return {
665
+ ArrowFunctionExpression: checkFunctionBody,
666
+ FunctionExpression: checkFunctionBody
667
+ };
668
+ }
669
+ });
670
+ var jsx_no_variable_in_callback_default = jsxNoVariableInCallback;
671
+
297
672
  // src/rules/md-filename-case-restriction.ts
298
673
  import path3 from "path";
299
- import { ESLintUtils as ESLintUtils4 } from "@typescript-eslint/utils";
300
- var createRule4 = ESLintUtils4.RuleCreator(
301
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
674
+ import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
675
+ var createRule8 = ESLintUtils8.RuleCreator(
676
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
302
677
  );
303
- var mdFilenameCaseRestriction = createRule4({
678
+ var mdFilenameCaseRestriction = createRule8({
304
679
  name: "md-filename-case-restriction",
305
680
  meta: {
306
681
  type: "problem",
@@ -320,18 +695,18 @@ var mdFilenameCaseRestriction = createRule4({
320
695
  if (!filename.endsWith(".md")) {
321
696
  return;
322
697
  }
323
- const basename = path3.basename(filename, ".md");
698
+ const basename2 = path3.basename(filename, ".md");
324
699
  function isSnakeCase(text) {
325
700
  return /^[A-Z][A-Z0-9_]*$/.test(text);
326
701
  }
327
702
  function isValidCase(text) {
328
703
  return isSnakeCase(text);
329
704
  }
330
- if (!isValidCase(basename)) {
705
+ if (!isValidCase(basename2)) {
331
706
  context.report({
332
707
  node: context.sourceCode.ast,
333
708
  messageId: "invalidFilenameCase",
334
- data: { filename: basename }
709
+ data: { filename: basename2 }
335
710
  });
336
711
  }
337
712
  }
@@ -341,11 +716,11 @@ var mdFilenameCaseRestriction = createRule4({
341
716
  var md_filename_case_restriction_default = mdFilenameCaseRestriction;
342
717
 
343
718
  // src/rules/no-complex-inline-return.ts
344
- import { ESLintUtils as ESLintUtils5, AST_NODE_TYPES as AST_NODE_TYPES2 } from "@typescript-eslint/utils";
345
- var createRule5 = ESLintUtils5.RuleCreator(
346
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
719
+ import { ESLintUtils as ESLintUtils9, AST_NODE_TYPES as AST_NODE_TYPES6 } from "@typescript-eslint/utils";
720
+ var createRule9 = ESLintUtils9.RuleCreator(
721
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
347
722
  );
348
- var noComplexInlineReturn = createRule5({
723
+ var noComplexInlineReturn = createRule9({
349
724
  name: "no-complex-inline-return",
350
725
  meta: {
351
726
  type: "suggestion",
@@ -361,13 +736,13 @@ var noComplexInlineReturn = createRule5({
361
736
  create(context) {
362
737
  const isComplexExpression = (node) => {
363
738
  if (!node) return false;
364
- if (node.type === AST_NODE_TYPES2.ConditionalExpression) {
739
+ if (node.type === AST_NODE_TYPES6.ConditionalExpression) {
365
740
  return true;
366
741
  }
367
- if (node.type === AST_NODE_TYPES2.LogicalExpression) {
742
+ if (node.type === AST_NODE_TYPES6.LogicalExpression) {
368
743
  return true;
369
744
  }
370
- if (node.type === AST_NODE_TYPES2.NewExpression) {
745
+ if (node.type === AST_NODE_TYPES6.NewExpression) {
371
746
  return true;
372
747
  }
373
748
  return false;
@@ -388,11 +763,11 @@ var no_complex_inline_return_default = noComplexInlineReturn;
388
763
 
389
764
  // src/rules/no-emoji.ts
390
765
  import emojiRegex from "emoji-regex";
391
- import { ESLintUtils as ESLintUtils6 } from "@typescript-eslint/utils";
392
- var createRule6 = ESLintUtils6.RuleCreator(
393
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
766
+ import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
767
+ var createRule10 = ESLintUtils10.RuleCreator(
768
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
394
769
  );
395
- var noEmoji = createRule6({
770
+ var noEmoji = createRule10({
396
771
  name: "no-emoji",
397
772
  meta: {
398
773
  type: "problem",
@@ -426,11 +801,11 @@ var noEmoji = createRule6({
426
801
  var no_emoji_default = noEmoji;
427
802
 
428
803
  // src/rules/no-env-fallback.ts
429
- import { ESLintUtils as ESLintUtils7, AST_NODE_TYPES as AST_NODE_TYPES3 } from "@typescript-eslint/utils";
430
- var createRule7 = ESLintUtils7.RuleCreator(
431
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
804
+ import { ESLintUtils as ESLintUtils11, AST_NODE_TYPES as AST_NODE_TYPES7 } from "@typescript-eslint/utils";
805
+ var createRule11 = ESLintUtils11.RuleCreator(
806
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
432
807
  );
433
- var noEnvFallback = createRule7({
808
+ var noEnvFallback = createRule11({
434
809
  name: "no-env-fallback",
435
810
  meta: {
436
811
  type: "problem",
@@ -445,16 +820,16 @@ var noEnvFallback = createRule7({
445
820
  defaultOptions: [],
446
821
  create(context) {
447
822
  const isProcessEnvAccess = (node) => {
448
- if (node.type !== AST_NODE_TYPES3.MemberExpression) {
823
+ if (node.type !== AST_NODE_TYPES7.MemberExpression) {
449
824
  return false;
450
825
  }
451
826
  const { object } = node;
452
- if (object.type !== AST_NODE_TYPES3.MemberExpression) {
827
+ if (object.type !== AST_NODE_TYPES7.MemberExpression) {
453
828
  return false;
454
829
  }
455
830
  const processNode = object.object;
456
831
  const envNode = object.property;
457
- return processNode.type === AST_NODE_TYPES3.Identifier && processNode.name === "process" && envNode.type === AST_NODE_TYPES3.Identifier && envNode.name === "env";
832
+ return processNode.type === AST_NODE_TYPES7.Identifier && processNode.name === "process" && envNode.type === AST_NODE_TYPES7.Identifier && envNode.name === "env";
458
833
  };
459
834
  return {
460
835
  LogicalExpression(node) {
@@ -478,38 +853,93 @@ var noEnvFallback = createRule7({
478
853
  });
479
854
  var no_env_fallback_default = noEnvFallback;
480
855
 
481
- // src/rules/no-explicit-return-type.ts
482
- import { ESLintUtils as ESLintUtils8 } from "@typescript-eslint/utils";
483
- var createRule8 = ESLintUtils8.RuleCreator(
484
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
856
+ // src/rules/require-explicit-return-type.ts
857
+ import { ESLintUtils as ESLintUtils12, AST_NODE_TYPES as AST_NODE_TYPES8 } from "@typescript-eslint/utils";
858
+ var createRule12 = ESLintUtils12.RuleCreator(
859
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
485
860
  );
486
- var noExplicitReturnType = createRule8({
487
- name: "no-explicit-return-type",
861
+ var isReactComponent = (node) => {
862
+ if (node.type === AST_NODE_TYPES8.ArrowFunctionExpression) {
863
+ const { parent } = node;
864
+ if (parent?.type === AST_NODE_TYPES8.VariableDeclarator) {
865
+ const { id } = parent;
866
+ if (id.type === AST_NODE_TYPES8.Identifier) {
867
+ return /^[A-Z]/.test(id.name);
868
+ }
869
+ }
870
+ }
871
+ if (node.type === AST_NODE_TYPES8.FunctionDeclaration && node.id) {
872
+ return /^[A-Z]/.test(node.id.name);
873
+ }
874
+ return false;
875
+ };
876
+ var isCallbackFunction = (node) => {
877
+ if (node.type === AST_NODE_TYPES8.FunctionDeclaration) {
878
+ return false;
879
+ }
880
+ const { parent } = node;
881
+ if (!parent) {
882
+ return false;
883
+ }
884
+ if (parent.type === AST_NODE_TYPES8.CallExpression && parent.arguments.includes(node)) {
885
+ return true;
886
+ }
887
+ if (parent.type === AST_NODE_TYPES8.Property) {
888
+ return true;
889
+ }
890
+ if (parent.type === AST_NODE_TYPES8.ArrayExpression) {
891
+ return true;
892
+ }
893
+ return false;
894
+ };
895
+ var getFunctionName = (node) => {
896
+ if (node.type === AST_NODE_TYPES8.FunctionDeclaration && node.id) {
897
+ return node.id.name;
898
+ }
899
+ if (node.type === AST_NODE_TYPES8.FunctionExpression && node.id) {
900
+ return node.id.name;
901
+ }
902
+ if ((node.type === AST_NODE_TYPES8.ArrowFunctionExpression || node.type === AST_NODE_TYPES8.FunctionExpression) && node.parent?.type === AST_NODE_TYPES8.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES8.Identifier) {
903
+ return node.parent.id.name;
904
+ }
905
+ return null;
906
+ };
907
+ var requireExplicitReturnType = createRule12({
908
+ name: "require-explicit-return-type",
488
909
  meta: {
489
910
  type: "suggestion",
490
911
  docs: {
491
- description: "Disallow explicit return types on functions"
912
+ description: "Require explicit return types on functions for better code documentation and type safety"
492
913
  },
493
- fixable: "code",
494
- schema: [],
495
914
  messages: {
496
- noExplicitReturnType: "Remove explicit return type '{{returnType}}' - TypeScript can infer it automatically"
497
- }
915
+ missingReturnType: "Function '{{name}}' is missing an explicit return type. Add a return type annotation for better documentation.",
916
+ missingReturnTypeAnonymous: "Function is missing an explicit return type. Add a return type annotation for better documentation."
917
+ },
918
+ schema: []
498
919
  },
499
920
  defaultOptions: [],
500
921
  create(context) {
501
922
  const checkFunction = (node) => {
502
923
  if (node.returnType) {
503
- const returnTypeText = context.sourceCode.getText(node.returnType);
924
+ return;
925
+ }
926
+ if (isCallbackFunction(node)) {
927
+ return;
928
+ }
929
+ if (isReactComponent(node)) {
930
+ return;
931
+ }
932
+ const functionName = getFunctionName(node);
933
+ if (functionName) {
504
934
  context.report({
505
- node: node.returnType,
506
- messageId: "noExplicitReturnType",
507
- data: {
508
- returnType: returnTypeText
509
- },
510
- fix(fixer) {
511
- return fixer.remove(node.returnType);
512
- }
935
+ node,
936
+ messageId: "missingReturnType",
937
+ data: { name: functionName }
938
+ });
939
+ } else {
940
+ context.report({
941
+ node,
942
+ messageId: "missingReturnTypeAnonymous"
513
943
  });
514
944
  }
515
945
  };
@@ -520,14 +950,99 @@ var noExplicitReturnType = createRule8({
520
950
  };
521
951
  }
522
952
  });
523
- var no_explicit_return_type_default = noExplicitReturnType;
953
+ var require_explicit_return_type_default = requireExplicitReturnType;
954
+
955
+ // src/rules/jsx-no-non-component-function.ts
956
+ import { AST_NODE_TYPES as AST_NODE_TYPES10, ESLintUtils as ESLintUtils13 } from "@typescript-eslint/utils";
957
+
958
+ // src/utils.ts
959
+ import { basename, extname } from "path";
960
+ import { AST_NODE_TYPES as AST_NODE_TYPES9 } from "@typescript-eslint/utils";
961
+ var getFileExtension = (filename) => extname(filename).slice(1);
962
+
963
+ // src/rules/jsx-no-non-component-function.ts
964
+ var createRule13 = ESLintUtils13.RuleCreator(
965
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
966
+ );
967
+ var jsxNoNonComponentFunction = createRule13({
968
+ name: "jsx-no-non-component-function",
969
+ meta: {
970
+ type: "problem",
971
+ docs: {
972
+ description: "Disallow non-component functions defined at top level in .tsx and .jsx files"
973
+ },
974
+ schema: [],
975
+ messages: {
976
+ noTopLevelFunction: "Non-component functions should not be defined at top level in .tsx/.jsx files. Either move it inside the component or extract it to a separate file."
977
+ }
978
+ },
979
+ defaultOptions: [],
980
+ create(context) {
981
+ const { filename } = context;
982
+ const extension = getFileExtension(filename);
983
+ if (extension !== "tsx" && extension !== "jsx") {
984
+ return {};
985
+ }
986
+ function isReactComponent2(node) {
987
+ const functionName = node.type === AST_NODE_TYPES10.FunctionDeclaration && node.id ? node.id.name : null;
988
+ if (functionName && /^[A-Z]/.test(functionName)) {
989
+ return true;
990
+ }
991
+ if (node.returnType?.typeAnnotation) {
992
+ const returnTypeNode = node.returnType.typeAnnotation;
993
+ if (returnTypeNode.type === AST_NODE_TYPES10.TSTypeReference && returnTypeNode.typeName.type === AST_NODE_TYPES10.Identifier) {
994
+ const typeName = returnTypeNode.typeName.name;
995
+ if (typeName === "JSX" || typeName === "ReactElement" || typeName === "ReactNode") {
996
+ return true;
997
+ }
998
+ }
999
+ }
1000
+ return false;
1001
+ }
1002
+ function checkTopLevelFunction(node, declaratorNode) {
1003
+ if (isReactComponent2(node)) {
1004
+ return;
1005
+ }
1006
+ const { parent } = node;
1007
+ if (!parent) {
1008
+ return;
1009
+ }
1010
+ if (parent.type === AST_NODE_TYPES10.ExportDefaultDeclaration || parent.type === AST_NODE_TYPES10.ExportNamedDeclaration) {
1011
+ return;
1012
+ }
1013
+ if (declaratorNode?.parent?.parent?.type === AST_NODE_TYPES10.ExportNamedDeclaration) {
1014
+ return;
1015
+ }
1016
+ if (declaratorNode?.id.type === AST_NODE_TYPES10.Identifier) {
1017
+ const varName = declaratorNode.id.name;
1018
+ if (/^[A-Z]/.test(varName)) {
1019
+ return;
1020
+ }
1021
+ }
1022
+ context.report({
1023
+ node: declaratorNode || node,
1024
+ messageId: "noTopLevelFunction"
1025
+ });
1026
+ }
1027
+ return {
1028
+ "Program > VariableDeclaration > VariableDeclarator > ArrowFunctionExpression": function checkArrowFunction(node) {
1029
+ const declarator = node.parent;
1030
+ checkTopLevelFunction(node, declarator);
1031
+ },
1032
+ "Program > FunctionDeclaration": function checkFunctionDeclaration(node) {
1033
+ checkTopLevelFunction(node);
1034
+ }
1035
+ };
1036
+ }
1037
+ });
1038
+ var jsx_no_non_component_function_default = jsxNoNonComponentFunction;
524
1039
 
525
1040
  // src/rules/no-logic-in-params.ts
526
- import { ESLintUtils as ESLintUtils9, AST_NODE_TYPES as AST_NODE_TYPES4 } from "@typescript-eslint/utils";
527
- var createRule9 = ESLintUtils9.RuleCreator(
528
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
1041
+ import { ESLintUtils as ESLintUtils14, AST_NODE_TYPES as AST_NODE_TYPES11 } from "@typescript-eslint/utils";
1042
+ var createRule14 = ESLintUtils14.RuleCreator(
1043
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
529
1044
  );
530
- var noLogicInParams = createRule9({
1045
+ var noLogicInParams = createRule14({
531
1046
  name: "no-logic-in-params",
532
1047
  meta: {
533
1048
  type: "suggestion",
@@ -542,20 +1057,20 @@ var noLogicInParams = createRule9({
542
1057
  defaultOptions: [],
543
1058
  create(context) {
544
1059
  const isComplexExpression = (node) => {
545
- if (node.type === AST_NODE_TYPES4.SpreadElement) {
1060
+ if (node.type === AST_NODE_TYPES11.SpreadElement) {
546
1061
  return false;
547
1062
  }
548
- if (node.type === AST_NODE_TYPES4.ConditionalExpression) {
1063
+ if (node.type === AST_NODE_TYPES11.ConditionalExpression) {
549
1064
  return true;
550
1065
  }
551
- if (node.type === AST_NODE_TYPES4.LogicalExpression) {
1066
+ if (node.type === AST_NODE_TYPES11.LogicalExpression) {
552
1067
  return true;
553
1068
  }
554
- if (node.type === AST_NODE_TYPES4.BinaryExpression) {
1069
+ if (node.type === AST_NODE_TYPES11.BinaryExpression) {
555
1070
  const logicalOperators = ["==", "===", "!=", "!==", "<", ">", "<=", ">=", "in", "instanceof"];
556
1071
  return logicalOperators.includes(node.operator);
557
1072
  }
558
- if (node.type === AST_NODE_TYPES4.UnaryExpression) {
1073
+ if (node.type === AST_NODE_TYPES11.UnaryExpression) {
559
1074
  return node.operator === "!";
560
1075
  }
561
1076
  return false;
@@ -586,12 +1101,127 @@ var noLogicInParams = createRule9({
586
1101
  });
587
1102
  var no_logic_in_params_default = noLogicInParams;
588
1103
 
1104
+ // src/rules/no-single-char-variables.ts
1105
+ import { ESLintUtils as ESLintUtils15, AST_NODE_TYPES as AST_NODE_TYPES12 } from "@typescript-eslint/utils";
1106
+ var createRule15 = ESLintUtils15.RuleCreator(
1107
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
1108
+ );
1109
+ var ALLOWED_IN_FOR_LOOPS = /* @__PURE__ */ new Set(["i", "j", "k", "n"]);
1110
+ var ALLOWED_UNDERSCORE = "_";
1111
+ var isForLoopInit = (node) => {
1112
+ let current = node;
1113
+ while (current) {
1114
+ const parentNode = current.parent;
1115
+ if (!parentNode) {
1116
+ return false;
1117
+ }
1118
+ if (parentNode.type === AST_NODE_TYPES12.ForStatement) {
1119
+ const { init } = parentNode;
1120
+ if (init && init === current) {
1121
+ return true;
1122
+ }
1123
+ }
1124
+ current = parentNode;
1125
+ }
1126
+ return false;
1127
+ };
1128
+ var isAllowedInContext = (name, node) => {
1129
+ if (name === ALLOWED_UNDERSCORE) {
1130
+ return true;
1131
+ }
1132
+ if (ALLOWED_IN_FOR_LOOPS.has(name) && isForLoopInit(node)) {
1133
+ return true;
1134
+ }
1135
+ return false;
1136
+ };
1137
+ var noSingleCharVariables = createRule15({
1138
+ name: "no-single-char-variables",
1139
+ meta: {
1140
+ type: "suggestion",
1141
+ docs: {
1142
+ description: "Disallow single character variable and parameter names for better code readability"
1143
+ },
1144
+ messages: {
1145
+ noSingleChar: "Avoid single character variable name '{{name}}'. Use a descriptive name that clearly indicates the purpose."
1146
+ },
1147
+ schema: []
1148
+ },
1149
+ defaultOptions: [],
1150
+ create(context) {
1151
+ const checkIdentifier = (node, declarationNode) => {
1152
+ const { name } = node;
1153
+ if (name.length !== 1) {
1154
+ return;
1155
+ }
1156
+ if (isAllowedInContext(name, declarationNode)) {
1157
+ return;
1158
+ }
1159
+ context.report({
1160
+ node,
1161
+ messageId: "noSingleChar",
1162
+ data: { name }
1163
+ });
1164
+ };
1165
+ const checkPattern = (pattern, declarationNode) => {
1166
+ if (pattern.type === AST_NODE_TYPES12.Identifier) {
1167
+ checkIdentifier(pattern, declarationNode);
1168
+ } else if (pattern.type === AST_NODE_TYPES12.ObjectPattern) {
1169
+ pattern.properties.forEach((prop) => {
1170
+ if (prop.type === AST_NODE_TYPES12.Property && prop.value.type === AST_NODE_TYPES12.Identifier) {
1171
+ checkIdentifier(prop.value, declarationNode);
1172
+ } else if (prop.type === AST_NODE_TYPES12.RestElement && prop.argument.type === AST_NODE_TYPES12.Identifier) {
1173
+ checkIdentifier(prop.argument, declarationNode);
1174
+ }
1175
+ });
1176
+ } else if (pattern.type === AST_NODE_TYPES12.ArrayPattern) {
1177
+ pattern.elements.forEach((element) => {
1178
+ if (element?.type === AST_NODE_TYPES12.Identifier) {
1179
+ checkIdentifier(element, declarationNode);
1180
+ } else if (element?.type === AST_NODE_TYPES12.RestElement && element.argument.type === AST_NODE_TYPES12.Identifier) {
1181
+ checkIdentifier(element.argument, declarationNode);
1182
+ }
1183
+ });
1184
+ } else if (pattern.type === AST_NODE_TYPES12.AssignmentPattern && pattern.left.type === AST_NODE_TYPES12.Identifier) {
1185
+ checkIdentifier(pattern.left, declarationNode);
1186
+ } else if (pattern.type === AST_NODE_TYPES12.RestElement && pattern.argument.type === AST_NODE_TYPES12.Identifier) {
1187
+ checkIdentifier(pattern.argument, declarationNode);
1188
+ }
1189
+ };
1190
+ return {
1191
+ VariableDeclarator(node) {
1192
+ checkPattern(node.id, node);
1193
+ },
1194
+ FunctionDeclaration(node) {
1195
+ if (node.id) {
1196
+ checkIdentifier(node.id, node);
1197
+ }
1198
+ node.params.forEach((param) => checkPattern(param, node));
1199
+ },
1200
+ FunctionExpression(node) {
1201
+ if (node.id) {
1202
+ checkIdentifier(node.id, node);
1203
+ }
1204
+ node.params.forEach((param) => checkPattern(param, node));
1205
+ },
1206
+ ArrowFunctionExpression(node) {
1207
+ node.params.forEach((param) => checkPattern(param, node));
1208
+ },
1209
+ CatchClause(node) {
1210
+ if (node.param) {
1211
+ checkPattern(node.param, node);
1212
+ }
1213
+ }
1214
+ };
1215
+ }
1216
+ });
1217
+ var no_single_char_variables_default = noSingleCharVariables;
1218
+
589
1219
  // src/rules/prefer-destructuring-params.ts
590
- import { AST_NODE_TYPES as AST_NODE_TYPES5, ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
591
- var createRule10 = ESLintUtils10.RuleCreator(
592
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
1220
+ import { AST_NODE_TYPES as AST_NODE_TYPES13, ESLintUtils as ESLintUtils16 } from "@typescript-eslint/utils";
1221
+ var createRule16 = ESLintUtils16.RuleCreator(
1222
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
593
1223
  );
594
- var preferDestructuringParams = createRule10({
1224
+ var preferDestructuringParams = createRule16({
595
1225
  name: "prefer-destructuring-params",
596
1226
  meta: {
597
1227
  type: "suggestion",
@@ -605,20 +1235,20 @@ var preferDestructuringParams = createRule10({
605
1235
  },
606
1236
  defaultOptions: [],
607
1237
  create(context) {
608
- const isCallbackFunction = (node) => {
1238
+ const isCallbackFunction2 = (node) => {
609
1239
  const { parent } = node;
610
- return parent?.type === AST_NODE_TYPES5.CallExpression;
1240
+ return parent?.type === AST_NODE_TYPES13.CallExpression;
611
1241
  };
612
1242
  const isDeveloperFunction = (node) => {
613
- if (node.type === AST_NODE_TYPES5.FunctionDeclaration) {
1243
+ if (node.type === AST_NODE_TYPES13.FunctionDeclaration) {
614
1244
  return true;
615
1245
  }
616
- if (node.type === AST_NODE_TYPES5.FunctionExpression || node.type === AST_NODE_TYPES5.ArrowFunctionExpression) {
617
- if (isCallbackFunction(node)) {
1246
+ if (node.type === AST_NODE_TYPES13.FunctionExpression || node.type === AST_NODE_TYPES13.ArrowFunctionExpression) {
1247
+ if (isCallbackFunction2(node)) {
618
1248
  return false;
619
1249
  }
620
1250
  const { parent } = node;
621
- return parent?.type === AST_NODE_TYPES5.VariableDeclarator || parent?.type === AST_NODE_TYPES5.AssignmentExpression || parent?.type === AST_NODE_TYPES5.Property || parent?.type === AST_NODE_TYPES5.MethodDefinition;
1251
+ return parent?.type === AST_NODE_TYPES13.VariableDeclarator || parent?.type === AST_NODE_TYPES13.AssignmentExpression || parent?.type === AST_NODE_TYPES13.Property || parent?.type === AST_NODE_TYPES13.MethodDefinition;
622
1252
  }
623
1253
  return false;
624
1254
  };
@@ -630,7 +1260,7 @@ var preferDestructuringParams = createRule10({
630
1260
  if (!isDeveloperFunction(node)) {
631
1261
  return;
632
1262
  }
633
- if (node.type === AST_NODE_TYPES5.FunctionDeclaration && node.id) {
1263
+ if (node.type === AST_NODE_TYPES13.FunctionDeclaration && node.id) {
634
1264
  const functionName = node.id.name;
635
1265
  if (functionName.startsWith("_") || functionName.includes("$") || /^[A-Z][a-zA-Z]*$/.test(functionName)) {
636
1266
  return;
@@ -640,7 +1270,7 @@ var preferDestructuringParams = createRule10({
640
1270
  return;
641
1271
  }
642
1272
  const hasNonDestructuredParams = node.params.some(
643
- (param) => param.type !== AST_NODE_TYPES5.ObjectPattern && param.type !== AST_NODE_TYPES5.RestElement
1273
+ (param) => param.type !== AST_NODE_TYPES13.ObjectPattern && param.type !== AST_NODE_TYPES13.RestElement
644
1274
  );
645
1275
  if (hasNonDestructuredParams) {
646
1276
  context.report({
@@ -658,12 +1288,103 @@ var preferDestructuringParams = createRule10({
658
1288
  });
659
1289
  var prefer_destructuring_params_default = preferDestructuringParams;
660
1290
 
1291
+ // src/rules/prefer-function-declaration.ts
1292
+ import { ESLintUtils as ESLintUtils17, AST_NODE_TYPES as AST_NODE_TYPES14 } from "@typescript-eslint/utils";
1293
+ var createRule17 = ESLintUtils17.RuleCreator(
1294
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
1295
+ );
1296
+ var isTsFile = (filename) => filename.endsWith(".ts") && !filename.endsWith(".d.ts");
1297
+ var isCallbackContext = (node) => {
1298
+ const { parent } = node;
1299
+ if (!parent) {
1300
+ return false;
1301
+ }
1302
+ if (parent.type === AST_NODE_TYPES14.CallExpression && parent.arguments.includes(node)) {
1303
+ return true;
1304
+ }
1305
+ if (parent.type === AST_NODE_TYPES14.NewExpression && parent.arguments.includes(node)) {
1306
+ return true;
1307
+ }
1308
+ if (parent.type === AST_NODE_TYPES14.ReturnStatement) {
1309
+ return true;
1310
+ }
1311
+ if (parent.type === AST_NODE_TYPES14.Property) {
1312
+ return true;
1313
+ }
1314
+ if (parent.type === AST_NODE_TYPES14.ArrayExpression) {
1315
+ return true;
1316
+ }
1317
+ if (parent.type === AST_NODE_TYPES14.ConditionalExpression) {
1318
+ return true;
1319
+ }
1320
+ if (parent.type === AST_NODE_TYPES14.LogicalExpression) {
1321
+ return true;
1322
+ }
1323
+ if (parent.type === AST_NODE_TYPES14.AssignmentExpression && parent.left !== node) {
1324
+ return true;
1325
+ }
1326
+ return false;
1327
+ };
1328
+ var preferFunctionDeclaration = createRule17({
1329
+ name: "prefer-function-declaration",
1330
+ meta: {
1331
+ type: "suggestion",
1332
+ docs: {
1333
+ description: "Enforce function declarations over arrow functions assigned to variables in .ts files for better readability and hoisting"
1334
+ },
1335
+ messages: {
1336
+ preferDeclaration: "Use function declaration instead of arrow function. Replace 'const {{name}} = () => ...' with 'function {{name}}() ...'",
1337
+ preferDeclarationExpr: "Use function declaration instead of function expression. Replace 'const {{name}} = function() ...' with 'function {{name}}() ...'"
1338
+ },
1339
+ schema: []
1340
+ },
1341
+ defaultOptions: [],
1342
+ create(context) {
1343
+ const { filename } = context;
1344
+ if (!isTsFile(filename)) {
1345
+ return {};
1346
+ }
1347
+ return {
1348
+ VariableDeclarator(node) {
1349
+ if (node.id.type !== AST_NODE_TYPES14.Identifier) {
1350
+ return;
1351
+ }
1352
+ const { init } = node;
1353
+ if (!init) {
1354
+ return;
1355
+ }
1356
+ if (init.type === AST_NODE_TYPES14.ArrowFunctionExpression) {
1357
+ if (isCallbackContext(init)) {
1358
+ return;
1359
+ }
1360
+ context.report({
1361
+ node: init,
1362
+ messageId: "preferDeclaration",
1363
+ data: { name: node.id.name }
1364
+ });
1365
+ }
1366
+ if (init.type === AST_NODE_TYPES14.FunctionExpression) {
1367
+ if (isCallbackContext(init)) {
1368
+ return;
1369
+ }
1370
+ context.report({
1371
+ node: init,
1372
+ messageId: "preferDeclarationExpr",
1373
+ data: { name: node.id.name }
1374
+ });
1375
+ }
1376
+ }
1377
+ };
1378
+ }
1379
+ });
1380
+ var prefer_function_declaration_default = preferFunctionDeclaration;
1381
+
661
1382
  // src/rules/prefer-import-type.ts
662
- import { AST_NODE_TYPES as AST_NODE_TYPES6, ESLintUtils as ESLintUtils11 } from "@typescript-eslint/utils";
663
- var createRule11 = ESLintUtils11.RuleCreator(
664
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
1383
+ import { AST_NODE_TYPES as AST_NODE_TYPES15, ESLintUtils as ESLintUtils18 } from "@typescript-eslint/utils";
1384
+ var createRule18 = ESLintUtils18.RuleCreator(
1385
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
665
1386
  );
666
- var preferImportType = createRule11({
1387
+ var preferImportType = createRule18({
667
1388
  name: "prefer-import-type",
668
1389
  meta: {
669
1390
  type: "suggestion",
@@ -678,6 +1399,81 @@ var preferImportType = createRule11({
678
1399
  },
679
1400
  defaultOptions: [],
680
1401
  create(context) {
1402
+ function isInTypeContext(node) {
1403
+ let current = node;
1404
+ while (current) {
1405
+ switch (current.type) {
1406
+ case AST_NODE_TYPES15.TSTypeReference:
1407
+ case AST_NODE_TYPES15.TSTypeAnnotation:
1408
+ case AST_NODE_TYPES15.TSTypeParameterInstantiation:
1409
+ case AST_NODE_TYPES15.TSInterfaceHeritage:
1410
+ case AST_NODE_TYPES15.TSClassImplements:
1411
+ case AST_NODE_TYPES15.TSTypeQuery:
1412
+ case AST_NODE_TYPES15.TSTypeAssertion:
1413
+ case AST_NODE_TYPES15.TSAsExpression:
1414
+ case AST_NODE_TYPES15.TSSatisfiesExpression:
1415
+ case AST_NODE_TYPES15.TSTypeAliasDeclaration:
1416
+ case AST_NODE_TYPES15.TSInterfaceDeclaration:
1417
+ case AST_NODE_TYPES15.TSTypeParameter:
1418
+ case AST_NODE_TYPES15.TSQualifiedName:
1419
+ return true;
1420
+ case AST_NODE_TYPES15.MemberExpression:
1421
+ case AST_NODE_TYPES15.Identifier:
1422
+ current = current.parent;
1423
+ break;
1424
+ default:
1425
+ return false;
1426
+ }
1427
+ }
1428
+ return false;
1429
+ }
1430
+ function isUsedAsValue(localName, scope) {
1431
+ const variable = scope.set.get(localName);
1432
+ if (!variable) {
1433
+ return false;
1434
+ }
1435
+ if (variable.references.length === 0) {
1436
+ return false;
1437
+ }
1438
+ return variable.references.some((ref) => {
1439
+ if (ref.isWrite()) {
1440
+ return false;
1441
+ }
1442
+ const { identifier } = ref;
1443
+ const { parent } = identifier;
1444
+ if (!parent) {
1445
+ return false;
1446
+ }
1447
+ if (isInTypeContext(parent)) {
1448
+ return false;
1449
+ }
1450
+ switch (parent.type) {
1451
+ case AST_NODE_TYPES15.CallExpression:
1452
+ case AST_NODE_TYPES15.NewExpression:
1453
+ case AST_NODE_TYPES15.JSXOpeningElement:
1454
+ case AST_NODE_TYPES15.JSXClosingElement:
1455
+ case AST_NODE_TYPES15.MemberExpression:
1456
+ case AST_NODE_TYPES15.VariableDeclarator:
1457
+ case AST_NODE_TYPES15.TaggedTemplateExpression:
1458
+ case AST_NODE_TYPES15.SpreadElement:
1459
+ case AST_NODE_TYPES15.ExportSpecifier:
1460
+ case AST_NODE_TYPES15.ArrayExpression:
1461
+ case AST_NODE_TYPES15.ObjectExpression:
1462
+ case AST_NODE_TYPES15.BinaryExpression:
1463
+ case AST_NODE_TYPES15.LogicalExpression:
1464
+ case AST_NODE_TYPES15.UnaryExpression:
1465
+ case AST_NODE_TYPES15.ReturnStatement:
1466
+ case AST_NODE_TYPES15.ArrowFunctionExpression:
1467
+ case AST_NODE_TYPES15.ConditionalExpression:
1468
+ case AST_NODE_TYPES15.AwaitExpression:
1469
+ case AST_NODE_TYPES15.YieldExpression:
1470
+ case AST_NODE_TYPES15.Property:
1471
+ return true;
1472
+ default:
1473
+ return false;
1474
+ }
1475
+ });
1476
+ }
681
1477
  function checkImportDeclaration(node) {
682
1478
  if (node.importKind === "type") {
683
1479
  return;
@@ -693,19 +1489,17 @@ var preferImportType = createRule11({
693
1489
  if (isRuntimeImport) {
694
1490
  return;
695
1491
  }
1492
+ const scope = context.sourceCode.getScope(node);
696
1493
  const isTypeOnlyImport = node.specifiers.every((specifier) => {
697
- if (specifier.type === AST_NODE_TYPES6.ImportDefaultSpecifier) {
1494
+ if (specifier.type === AST_NODE_TYPES15.ImportDefaultSpecifier) {
698
1495
  return false;
699
1496
  }
700
- if (specifier.type === AST_NODE_TYPES6.ImportNamespaceSpecifier) {
1497
+ if (specifier.type === AST_NODE_TYPES15.ImportNamespaceSpecifier) {
701
1498
  return false;
702
1499
  }
703
- if (specifier.type === AST_NODE_TYPES6.ImportSpecifier) {
704
- const importedName = specifier.imported.type === AST_NODE_TYPES6.Identifier ? specifier.imported.name : specifier.imported.value;
705
- const isKnownTypeOnly = node.source.value === "@typescript-eslint/utils" && ["TSESTree", "RuleContext"].includes(importedName) || node.source.value === "react" && ["Component", "ComponentProps", "ReactNode", "FC", "JSX", "ReactElement", "PropsWithChildren"].includes(
706
- importedName
707
- ) || importedName.endsWith("Type") || importedName.endsWith("Interface") || importedName.endsWith("Props");
708
- return isKnownTypeOnly;
1500
+ if (specifier.type === AST_NODE_TYPES15.ImportSpecifier) {
1501
+ const localName = specifier.local.name;
1502
+ return !isUsedAsValue(localName, scope);
709
1503
  }
710
1504
  return false;
711
1505
  });
@@ -729,11 +1523,11 @@ var preferImportType = createRule11({
729
1523
  var prefer_import_type_default = preferImportType;
730
1524
 
731
1525
  // src/rules/prefer-interface-over-inline-types.ts
732
- import { AST_NODE_TYPES as AST_NODE_TYPES7, ESLintUtils as ESLintUtils12 } from "@typescript-eslint/utils";
733
- var createRule12 = ESLintUtils12.RuleCreator(
734
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
1526
+ import { AST_NODE_TYPES as AST_NODE_TYPES16, ESLintUtils as ESLintUtils19 } from "@typescript-eslint/utils";
1527
+ var createRule19 = ESLintUtils19.RuleCreator(
1528
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
735
1529
  );
736
- var preferInterfaceOverInlineTypes = createRule12({
1530
+ var preferInterfaceOverInlineTypes = createRule19({
737
1531
  name: "prefer-interface-over-inline-types",
738
1532
  meta: {
739
1533
  type: "suggestion",
@@ -749,67 +1543,67 @@ var preferInterfaceOverInlineTypes = createRule12({
749
1543
  defaultOptions: [],
750
1544
  create(context) {
751
1545
  function hasJSXInConditional(node) {
752
- return node.consequent.type === AST_NODE_TYPES7.JSXElement || node.consequent.type === AST_NODE_TYPES7.JSXFragment || node.alternate.type === AST_NODE_TYPES7.JSXElement || node.alternate.type === AST_NODE_TYPES7.JSXFragment;
1546
+ return node.consequent.type === AST_NODE_TYPES16.JSXElement || node.consequent.type === AST_NODE_TYPES16.JSXFragment || node.alternate.type === AST_NODE_TYPES16.JSXElement || node.alternate.type === AST_NODE_TYPES16.JSXFragment;
753
1547
  }
754
1548
  function hasJSXInLogical(node) {
755
- return node.right.type === AST_NODE_TYPES7.JSXElement || node.right.type === AST_NODE_TYPES7.JSXFragment;
1549
+ return node.right.type === AST_NODE_TYPES16.JSXElement || node.right.type === AST_NODE_TYPES16.JSXFragment;
756
1550
  }
757
1551
  function hasJSXReturn(block) {
758
1552
  return block.body.some((stmt) => {
759
- if (stmt.type === AST_NODE_TYPES7.ReturnStatement && stmt.argument) {
760
- return stmt.argument.type === AST_NODE_TYPES7.JSXElement || stmt.argument.type === AST_NODE_TYPES7.JSXFragment || stmt.argument.type === AST_NODE_TYPES7.ConditionalExpression && hasJSXInConditional(stmt.argument) || stmt.argument.type === AST_NODE_TYPES7.LogicalExpression && hasJSXInLogical(stmt.argument);
1553
+ if (stmt.type === AST_NODE_TYPES16.ReturnStatement && stmt.argument) {
1554
+ return stmt.argument.type === AST_NODE_TYPES16.JSXElement || stmt.argument.type === AST_NODE_TYPES16.JSXFragment || stmt.argument.type === AST_NODE_TYPES16.ConditionalExpression && hasJSXInConditional(stmt.argument) || stmt.argument.type === AST_NODE_TYPES16.LogicalExpression && hasJSXInLogical(stmt.argument);
761
1555
  }
762
1556
  return false;
763
1557
  });
764
1558
  }
765
- function isReactComponent(node) {
766
- if (node.type === AST_NODE_TYPES7.ArrowFunctionExpression) {
767
- if (node.body.type === AST_NODE_TYPES7.JSXElement || node.body.type === AST_NODE_TYPES7.JSXFragment) {
1559
+ function isReactComponent2(node) {
1560
+ if (node.type === AST_NODE_TYPES16.ArrowFunctionExpression) {
1561
+ if (node.body.type === AST_NODE_TYPES16.JSXElement || node.body.type === AST_NODE_TYPES16.JSXFragment) {
768
1562
  return true;
769
1563
  }
770
- if (node.body.type === AST_NODE_TYPES7.BlockStatement) {
1564
+ if (node.body.type === AST_NODE_TYPES16.BlockStatement) {
771
1565
  return hasJSXReturn(node.body);
772
1566
  }
773
- } else if (node.type === AST_NODE_TYPES7.FunctionExpression || node.type === AST_NODE_TYPES7.FunctionDeclaration) {
774
- if (node.body && node.body.type === AST_NODE_TYPES7.BlockStatement) {
1567
+ } else if (node.type === AST_NODE_TYPES16.FunctionExpression || node.type === AST_NODE_TYPES16.FunctionDeclaration) {
1568
+ if (node.body && node.body.type === AST_NODE_TYPES16.BlockStatement) {
775
1569
  return hasJSXReturn(node.body);
776
1570
  }
777
1571
  }
778
1572
  return false;
779
1573
  }
780
1574
  function isInlineTypeAnnotation(node) {
781
- if (node.type === AST_NODE_TYPES7.TSTypeLiteral) {
1575
+ if (node.type === AST_NODE_TYPES16.TSTypeLiteral) {
782
1576
  return true;
783
1577
  }
784
- if (node.type === AST_NODE_TYPES7.TSTypeReference && node.typeArguments) {
785
- return node.typeArguments.params.some((param) => param.type === AST_NODE_TYPES7.TSTypeLiteral);
1578
+ if (node.type === AST_NODE_TYPES16.TSTypeReference && node.typeArguments) {
1579
+ return node.typeArguments.params.some((param) => param.type === AST_NODE_TYPES16.TSTypeLiteral);
786
1580
  }
787
- if (node.type === AST_NODE_TYPES7.TSUnionType) {
1581
+ if (node.type === AST_NODE_TYPES16.TSUnionType) {
788
1582
  return node.types.some((type) => isInlineTypeAnnotation(type));
789
1583
  }
790
1584
  return false;
791
1585
  }
792
1586
  function hasInlineObjectType(node) {
793
- if (node.type === AST_NODE_TYPES7.TSTypeLiteral) {
1587
+ if (node.type === AST_NODE_TYPES16.TSTypeLiteral) {
794
1588
  return true;
795
1589
  }
796
- if (node.type === AST_NODE_TYPES7.TSTypeReference && node.typeArguments) {
797
- return node.typeArguments.params.some((param) => param.type === AST_NODE_TYPES7.TSTypeLiteral);
1590
+ if (node.type === AST_NODE_TYPES16.TSTypeReference && node.typeArguments) {
1591
+ return node.typeArguments.params.some((param) => param.type === AST_NODE_TYPES16.TSTypeLiteral);
798
1592
  }
799
- if (node.type === AST_NODE_TYPES7.TSUnionType) {
1593
+ if (node.type === AST_NODE_TYPES16.TSUnionType) {
800
1594
  return node.types.some((type) => hasInlineObjectType(type));
801
1595
  }
802
1596
  return false;
803
1597
  }
804
1598
  function checkFunction(node) {
805
- if (!isReactComponent(node)) {
1599
+ if (!isReactComponent2(node)) {
806
1600
  return;
807
1601
  }
808
1602
  if (node.params.length !== 1) {
809
1603
  return;
810
1604
  }
811
1605
  const param = node.params[0];
812
- if (param.type === AST_NODE_TYPES7.Identifier && param.typeAnnotation) {
1606
+ if (param.type === AST_NODE_TYPES16.Identifier && param.typeAnnotation) {
813
1607
  const { typeAnnotation } = param.typeAnnotation;
814
1608
  if (isInlineTypeAnnotation(typeAnnotation) && hasInlineObjectType(typeAnnotation)) {
815
1609
  context.report({
@@ -828,12 +1622,100 @@ var preferInterfaceOverInlineTypes = createRule12({
828
1622
  });
829
1623
  var prefer_interface_over_inline_types_default = preferInterfaceOverInlineTypes;
830
1624
 
1625
+ // src/rules/prefer-jsx-template-literals.ts
1626
+ import { AST_NODE_TYPES as AST_NODE_TYPES17, ESLintUtils as ESLintUtils20 } from "@typescript-eslint/utils";
1627
+ var createRule20 = ESLintUtils20.RuleCreator(
1628
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
1629
+ );
1630
+ var preferJSXTemplateLiterals = createRule20({
1631
+ name: "prefer-jsx-template-literals",
1632
+ meta: {
1633
+ type: "suggestion",
1634
+ docs: {
1635
+ description: "Enforce using template literals instead of mixing text and JSX expressions"
1636
+ },
1637
+ fixable: "code",
1638
+ schema: [],
1639
+ messages: {
1640
+ preferTemplate: "Use template literal instead of mixing text with JSX expressions"
1641
+ }
1642
+ },
1643
+ defaultOptions: [],
1644
+ create(context) {
1645
+ function handleTextBeforeExpression(textNode, exprNode) {
1646
+ const textValue = textNode.value;
1647
+ const trimmedText = textValue.trim();
1648
+ if (!trimmedText) {
1649
+ return;
1650
+ }
1651
+ const hasTextContent = trimmedText.length > 0 && textValue !== trimmedText;
1652
+ const hasNoTrailingSpace = trimmedText.length > 0 && /\S$/.test(textValue);
1653
+ if (!hasTextContent && !hasNoTrailingSpace) {
1654
+ return;
1655
+ }
1656
+ context.report({
1657
+ node: textNode,
1658
+ messageId: "preferTemplate",
1659
+ fix(fixer) {
1660
+ const textPart = textValue.trimEnd();
1661
+ const exprText = context.sourceCode.getText(exprNode.expression);
1662
+ const templateLiteral = `{\`${textPart}\${${exprText}}\`}`;
1663
+ return [fixer.replaceText(textNode, templateLiteral), fixer.remove(exprNode)];
1664
+ }
1665
+ });
1666
+ }
1667
+ function handleExpressionBeforeText(exprNode, textNode) {
1668
+ const textValue = textNode.value;
1669
+ const trimmedText = textValue.trim();
1670
+ if (!trimmedText) {
1671
+ return;
1672
+ }
1673
+ const startsWithNonWhitespace = /^\S/.test(trimmedText);
1674
+ if (!startsWithNonWhitespace) {
1675
+ return;
1676
+ }
1677
+ context.report({
1678
+ node: textNode,
1679
+ messageId: "preferTemplate",
1680
+ fix(fixer) {
1681
+ const exprText = context.sourceCode.getText(exprNode.expression);
1682
+ const textPart = textValue.trim();
1683
+ const templateLiteral = `{\`\${${exprText}}${textPart}\`}`;
1684
+ return [fixer.replaceText(exprNode, templateLiteral), fixer.remove(textNode)];
1685
+ }
1686
+ });
1687
+ }
1688
+ function checkJSXElement(node) {
1689
+ const { children } = node;
1690
+ if (children.length < 2) {
1691
+ return;
1692
+ }
1693
+ for (let i = 0; i < children.length - 1; i += 1) {
1694
+ const child = children[i];
1695
+ const nextChild = children[i + 1];
1696
+ if (!child || !nextChild) {
1697
+ return;
1698
+ }
1699
+ if (child.type === AST_NODE_TYPES17.JSXText && nextChild.type === AST_NODE_TYPES17.JSXExpressionContainer) {
1700
+ handleTextBeforeExpression(child, nextChild);
1701
+ } else if (child.type === AST_NODE_TYPES17.JSXExpressionContainer && nextChild.type === AST_NODE_TYPES17.JSXText) {
1702
+ handleExpressionBeforeText(child, nextChild);
1703
+ }
1704
+ }
1705
+ }
1706
+ return {
1707
+ JSXElement: checkJSXElement
1708
+ };
1709
+ }
1710
+ });
1711
+ var prefer_jsx_template_literals_default = preferJSXTemplateLiterals;
1712
+
831
1713
  // src/rules/prefer-named-param-types.ts
832
- import { AST_NODE_TYPES as AST_NODE_TYPES8, ESLintUtils as ESLintUtils13 } from "@typescript-eslint/utils";
833
- var createRule13 = ESLintUtils13.RuleCreator(
834
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
1714
+ import { AST_NODE_TYPES as AST_NODE_TYPES18, ESLintUtils as ESLintUtils21 } from "@typescript-eslint/utils";
1715
+ var createRule21 = ESLintUtils21.RuleCreator(
1716
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
835
1717
  );
836
- var preferNamedParamTypes = createRule13({
1718
+ var preferNamedParamTypes = createRule21({
837
1719
  name: "prefer-named-param-types",
838
1720
  meta: {
839
1721
  type: "suggestion",
@@ -848,16 +1730,16 @@ var preferNamedParamTypes = createRule13({
848
1730
  defaultOptions: [],
849
1731
  create(context) {
850
1732
  function hasInlineObjectType(param) {
851
- if (param.type === AST_NODE_TYPES8.AssignmentPattern) {
1733
+ if (param.type === AST_NODE_TYPES18.AssignmentPattern) {
852
1734
  return hasInlineObjectType(param.left);
853
1735
  }
854
- if (param.type === AST_NODE_TYPES8.ObjectPattern) {
855
- if (param.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES8.TSTypeLiteral) {
1736
+ if (param.type === AST_NODE_TYPES18.ObjectPattern) {
1737
+ if (param.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES18.TSTypeLiteral) {
856
1738
  return true;
857
1739
  }
858
1740
  }
859
- if (param.type === AST_NODE_TYPES8.Identifier) {
860
- if (param.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES8.TSTypeLiteral) {
1741
+ if (param.type === AST_NODE_TYPES18.Identifier) {
1742
+ if (param.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES18.TSTypeLiteral) {
861
1743
  return true;
862
1744
  }
863
1745
  }
@@ -891,11 +1773,11 @@ var preferNamedParamTypes = createRule13({
891
1773
  var prefer_named_param_types_default = preferNamedParamTypes;
892
1774
 
893
1775
  // src/rules/prefer-react-import-types.ts
894
- import { AST_NODE_TYPES as AST_NODE_TYPES9, ESLintUtils as ESLintUtils14 } from "@typescript-eslint/utils";
895
- var createRule14 = ESLintUtils14.RuleCreator(
896
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
1776
+ import { AST_NODE_TYPES as AST_NODE_TYPES19, ESLintUtils as ESLintUtils22 } from "@typescript-eslint/utils";
1777
+ var createRule22 = ESLintUtils22.RuleCreator(
1778
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
897
1779
  );
898
- var preferReactImportTypes = createRule14({
1780
+ var preferReactImportTypes = createRule22({
899
1781
  name: "prefer-react-import-types",
900
1782
  meta: {
901
1783
  type: "suggestion",
@@ -971,7 +1853,7 @@ var preferReactImportTypes = createRule14({
971
1853
  ]);
972
1854
  const allReactExports = /* @__PURE__ */ new Set([...reactTypes, ...reactRuntimeExports]);
973
1855
  function checkMemberExpression(node) {
974
- if (node.object.type === AST_NODE_TYPES9.Identifier && node.object.name === "React" && node.property.type === AST_NODE_TYPES9.Identifier && allReactExports.has(node.property.name)) {
1856
+ if (node.object.type === AST_NODE_TYPES19.Identifier && node.object.name === "React" && node.property.type === AST_NODE_TYPES19.Identifier && allReactExports.has(node.property.name)) {
975
1857
  const typeName = node.property.name;
976
1858
  const isType = reactTypes.has(typeName);
977
1859
  const importStatement = isType ? `import type { ${typeName} } from "react"` : `import { ${typeName} } from "react"`;
@@ -988,7 +1870,7 @@ var preferReactImportTypes = createRule14({
988
1870
  return {
989
1871
  MemberExpression: checkMemberExpression,
990
1872
  "TSTypeReference > TSQualifiedName": (node) => {
991
- if (node.left.type === AST_NODE_TYPES9.Identifier && node.left.name === "React" && node.right.type === AST_NODE_TYPES9.Identifier && allReactExports.has(node.right.name)) {
1873
+ if (node.left.type === AST_NODE_TYPES19.Identifier && node.left.name === "React" && node.right.type === AST_NODE_TYPES19.Identifier && allReactExports.has(node.right.name)) {
992
1874
  const typeName = node.right.name;
993
1875
  const isType = reactTypes.has(typeName);
994
1876
  const importStatement = isType ? `import type { ${typeName} } from "react"` : `import { ${typeName} } from "react"`;
@@ -1008,11 +1890,11 @@ var preferReactImportTypes = createRule14({
1008
1890
  var prefer_react_import_types_default = preferReactImportTypes;
1009
1891
 
1010
1892
  // src/rules/react-props-destructure.ts
1011
- import { AST_NODE_TYPES as AST_NODE_TYPES10, ESLintUtils as ESLintUtils15 } from "@typescript-eslint/utils";
1012
- var createRule15 = ESLintUtils15.RuleCreator(
1013
- (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replace(/-/g, "_").toUpperCase()}.md`
1893
+ import { AST_NODE_TYPES as AST_NODE_TYPES20, ESLintUtils as ESLintUtils23 } from "@typescript-eslint/utils";
1894
+ var createRule23 = ESLintUtils23.RuleCreator(
1895
+ (name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`
1014
1896
  );
1015
- var reactPropsDestructure = createRule15({
1897
+ var reactPropsDestructure = createRule23({
1016
1898
  name: "react-props-destructure",
1017
1899
  meta: {
1018
1900
  type: "suggestion",
@@ -1028,45 +1910,45 @@ var reactPropsDestructure = createRule15({
1028
1910
  defaultOptions: [],
1029
1911
  create(context) {
1030
1912
  function hasJSXInConditional(node) {
1031
- return node.consequent.type === AST_NODE_TYPES10.JSXElement || node.consequent.type === AST_NODE_TYPES10.JSXFragment || node.alternate.type === AST_NODE_TYPES10.JSXElement || node.alternate.type === AST_NODE_TYPES10.JSXFragment;
1913
+ return node.consequent.type === AST_NODE_TYPES20.JSXElement || node.consequent.type === AST_NODE_TYPES20.JSXFragment || node.alternate.type === AST_NODE_TYPES20.JSXElement || node.alternate.type === AST_NODE_TYPES20.JSXFragment;
1032
1914
  }
1033
1915
  function hasJSXInLogical(node) {
1034
- return node.right.type === AST_NODE_TYPES10.JSXElement || node.right.type === AST_NODE_TYPES10.JSXFragment;
1916
+ return node.right.type === AST_NODE_TYPES20.JSXElement || node.right.type === AST_NODE_TYPES20.JSXFragment;
1035
1917
  }
1036
1918
  function hasJSXReturn(block) {
1037
1919
  return block.body.some((stmt) => {
1038
- if (stmt.type === AST_NODE_TYPES10.ReturnStatement && stmt.argument) {
1039
- return stmt.argument.type === AST_NODE_TYPES10.JSXElement || stmt.argument.type === AST_NODE_TYPES10.JSXFragment || stmt.argument.type === AST_NODE_TYPES10.ConditionalExpression && hasJSXInConditional(stmt.argument) || stmt.argument.type === AST_NODE_TYPES10.LogicalExpression && hasJSXInLogical(stmt.argument);
1920
+ if (stmt.type === AST_NODE_TYPES20.ReturnStatement && stmt.argument) {
1921
+ return stmt.argument.type === AST_NODE_TYPES20.JSXElement || stmt.argument.type === AST_NODE_TYPES20.JSXFragment || stmt.argument.type === AST_NODE_TYPES20.ConditionalExpression && hasJSXInConditional(stmt.argument) || stmt.argument.type === AST_NODE_TYPES20.LogicalExpression && hasJSXInLogical(stmt.argument);
1040
1922
  }
1041
1923
  return false;
1042
1924
  });
1043
1925
  }
1044
- function isReactComponent(node) {
1045
- if (node.type === AST_NODE_TYPES10.ArrowFunctionExpression) {
1046
- if (node.body.type === AST_NODE_TYPES10.JSXElement || node.body.type === AST_NODE_TYPES10.JSXFragment) {
1926
+ function isReactComponent2(node) {
1927
+ if (node.type === AST_NODE_TYPES20.ArrowFunctionExpression) {
1928
+ if (node.body.type === AST_NODE_TYPES20.JSXElement || node.body.type === AST_NODE_TYPES20.JSXFragment) {
1047
1929
  return true;
1048
1930
  }
1049
- if (node.body.type === AST_NODE_TYPES10.BlockStatement) {
1931
+ if (node.body.type === AST_NODE_TYPES20.BlockStatement) {
1050
1932
  return hasJSXReturn(node.body);
1051
1933
  }
1052
- } else if (node.type === AST_NODE_TYPES10.FunctionExpression || node.type === AST_NODE_TYPES10.FunctionDeclaration) {
1053
- if (node.body && node.body.type === AST_NODE_TYPES10.BlockStatement) {
1934
+ } else if (node.type === AST_NODE_TYPES20.FunctionExpression || node.type === AST_NODE_TYPES20.FunctionDeclaration) {
1935
+ if (node.body && node.body.type === AST_NODE_TYPES20.BlockStatement) {
1054
1936
  return hasJSXReturn(node.body);
1055
1937
  }
1056
1938
  }
1057
1939
  return false;
1058
1940
  }
1059
1941
  function checkFunction(node) {
1060
- if (!isReactComponent(node)) {
1942
+ if (!isReactComponent2(node)) {
1061
1943
  return;
1062
1944
  }
1063
1945
  if (node.params.length !== 1) {
1064
1946
  return;
1065
1947
  }
1066
1948
  const param = node.params[0];
1067
- if (param.type === AST_NODE_TYPES10.ObjectPattern) {
1068
- const properties = param.properties.filter((prop) => prop.type === AST_NODE_TYPES10.Property).map((prop) => {
1069
- if (prop.key.type === AST_NODE_TYPES10.Identifier) {
1949
+ if (param.type === AST_NODE_TYPES20.ObjectPattern) {
1950
+ const properties = param.properties.filter((prop) => prop.type === AST_NODE_TYPES20.Property).map((prop) => {
1951
+ if (prop.key.type === AST_NODE_TYPES20.Identifier) {
1070
1952
  return prop.key.name;
1071
1953
  }
1072
1954
  return null;
@@ -1098,18 +1980,26 @@ var meta = {
1098
1980
  version: package_default.version
1099
1981
  };
1100
1982
  var rules = {
1983
+ "boolean-naming-prefix": boolean_naming_prefix_default,
1101
1984
  "enforce-readonly-component-props": enforce_readonly_component_props_default,
1985
+ "enforce-sorted-destructuring": enforce_sorted_destructuring_default,
1102
1986
  "file-kebab-case": file_kebab_case_default,
1103
1987
  "jsx-pascal-case": jsx_pascal_case_default,
1988
+ "jsx-no-non-component-function": jsx_no_non_component_function_default,
1989
+ "jsx-no-variable-in-callback": jsx_no_variable_in_callback_default,
1104
1990
  "md-filename-case-restriction": md_filename_case_restriction_default,
1105
1991
  "no-complex-inline-return": no_complex_inline_return_default,
1992
+ "no-direct-date": no_direct_date_default,
1106
1993
  "no-emoji": no_emoji_default,
1107
1994
  "no-env-fallback": no_env_fallback_default,
1108
- "no-explicit-return-type": no_explicit_return_type_default,
1995
+ "require-explicit-return-type": require_explicit_return_type_default,
1109
1996
  "no-logic-in-params": no_logic_in_params_default,
1997
+ "no-single-char-variables": no_single_char_variables_default,
1110
1998
  "prefer-destructuring-params": prefer_destructuring_params_default,
1999
+ "prefer-function-declaration": prefer_function_declaration_default,
1111
2000
  "prefer-import-type": prefer_import_type_default,
1112
2001
  "prefer-interface-over-inline-types": prefer_interface_over_inline_types_default,
2002
+ "prefer-jsx-template-literals": prefer_jsx_template_literals_default,
1113
2003
  "prefer-named-param-types": prefer_named_param_types_default,
1114
2004
  "prefer-react-import-types": prefer_react_import_types_default,
1115
2005
  "react-props-destructure": react_props_destructure_default
@@ -1119,40 +2009,56 @@ var plugin = {
1119
2009
  rules
1120
2010
  };
1121
2011
  var baseRules = {
2012
+ "nextfriday/boolean-naming-prefix": "warn",
1122
2013
  "nextfriday/no-emoji": "warn",
2014
+ "nextfriday/enforce-sorted-destructuring": "warn",
1123
2015
  "nextfriday/file-kebab-case": "warn",
1124
2016
  "nextfriday/md-filename-case-restriction": "warn",
1125
2017
  "nextfriday/prefer-destructuring-params": "warn",
1126
- "nextfriday/no-explicit-return-type": "warn",
2018
+ "nextfriday/prefer-function-declaration": "warn",
2019
+ "nextfriday/require-explicit-return-type": "warn",
1127
2020
  "nextfriday/prefer-import-type": "warn",
1128
2021
  "nextfriday/prefer-named-param-types": "warn",
1129
2022
  "nextfriday/prefer-react-import-types": "warn",
1130
2023
  "nextfriday/no-complex-inline-return": "warn",
2024
+ "nextfriday/no-direct-date": "warn",
1131
2025
  "nextfriday/no-logic-in-params": "warn",
1132
- "nextfriday/no-env-fallback": "warn"
2026
+ "nextfriday/no-env-fallback": "warn",
2027
+ "nextfriday/no-single-char-variables": "warn"
1133
2028
  };
1134
2029
  var baseRecommendedRules = {
2030
+ "nextfriday/boolean-naming-prefix": "error",
1135
2031
  "nextfriday/no-emoji": "error",
2032
+ "nextfriday/enforce-sorted-destructuring": "error",
1136
2033
  "nextfriday/file-kebab-case": "error",
1137
2034
  "nextfriday/md-filename-case-restriction": "error",
1138
2035
  "nextfriday/prefer-destructuring-params": "error",
1139
- "nextfriday/no-explicit-return-type": "error",
2036
+ "nextfriday/prefer-function-declaration": "error",
2037
+ "nextfriday/require-explicit-return-type": "error",
1140
2038
  "nextfriday/prefer-import-type": "error",
1141
2039
  "nextfriday/prefer-named-param-types": "error",
1142
2040
  "nextfriday/prefer-react-import-types": "error",
1143
2041
  "nextfriday/no-complex-inline-return": "error",
2042
+ "nextfriday/no-direct-date": "error",
1144
2043
  "nextfriday/no-logic-in-params": "error",
1145
- "nextfriday/no-env-fallback": "error"
2044
+ "nextfriday/no-env-fallback": "error",
2045
+ "nextfriday/no-single-char-variables": "error"
1146
2046
  };
1147
2047
  var jsxRules = {
1148
2048
  "nextfriday/jsx-pascal-case": "warn",
2049
+ "nextfriday/jsx-no-non-component-function": "warn",
2050
+ "nextfriday/jsx-no-variable-in-callback": "warn",
1149
2051
  "nextfriday/prefer-interface-over-inline-types": "warn",
2052
+ "nextfriday/prefer-jsx-template-literals": "warn",
1150
2053
  "nextfriday/react-props-destructure": "warn",
1151
2054
  "nextfriday/enforce-readonly-component-props": "warn"
1152
2055
  };
1153
2056
  var jsxRecommendedRules = {
1154
2057
  "nextfriday/jsx-pascal-case": "error",
2058
+ "nextfriday/jsx-no-non-component-function": "error",
2059
+ "nextfriday/jsx-no-variable-in-callback": "error",
1155
2060
  "nextfriday/prefer-interface-over-inline-types": "error",
2061
+ "nextfriday/prefer-jsx-template-literals": "error",
1156
2062
  "nextfriday/react-props-destructure": "error",
1157
2063
  "nextfriday/enforce-readonly-component-props": "error"
1158
2064
  };