@@ -483,6 +483,167 @@ describe('when allowNamedFunctions is true', () => {
483483 } ) ;
484484} ) ;
485485
486+ describe ( 'when allowNamedFunctions is "only-expressions"' , ( ) => {
487+ describe ( 'it allows named function expressions but transforms named declarations and anonymous expressions' , ( ) => {
488+ ruleTester . run ( 'prefer-arrow-functions' , rule , {
489+ valid : [
490+ // Named function expressions should be preserved
491+ { code : 'useThisFunction(function doSomething() { return "bar"; })' } ,
492+ { code : 'useThisFunction(function namedFunc() { console.log("test"); })' } ,
493+ { code : 'callback(function myCallback() { return 42; })' } ,
494+ { code : 'React.forwardRef(function MyComponent(props, ref) { return props.children; })' } ,
495+ { code : 'addEventListener("click", function handleClick(event) { console.log(event); })' } ,
496+ { code : 'arr.map(function mapFn(item) { return item * 2; })' } ,
497+ { code : 'setTimeout(function delayedFn() { console.log("delayed"); }, 1000)' } ,
498+
499+ // Named function expressions with async should be preserved
500+ { code : 'useAsyncFunction(async function asyncNamed() { return await getData(); })' } ,
501+
502+ // Named function expressions with generators should be preserved
503+ { code : 'useGenerator(function* generatorNamed() { yield 1; })' } ,
504+
505+ // Named function expressions that preserve this behavior
506+ { code : 'obj.method(function namedWithThis() { return this.value; })' } ,
507+ { code : 'context.bind(function boundNamed() { this.prop = "value"; })' } ,
508+
509+ // Named function expressions with complex parameters
510+ { code : 'processor(function processData(data, { options = {} } = {}) { return data.process(options); })' } ,
511+
512+ // Nested named function expressions
513+ { code : 'outer(function outerNamed() { return inner(function innerNamed() { return "nested"; }); })' } ,
514+
515+ // Arrow functions should remain valid (no change from existing behavior)
516+ { code : 'const arrow = () => "arrow"' } ,
517+ { code : 'useArrowFunction(() => "test")' } ,
518+ ] . map ( withOptions ( { allowNamedFunctions : 'only-expressions' } ) ) ,
519+ invalid : [
520+ // Named function declarations should be transformed
521+ {
522+ code : 'function doSomething() { return "bar"; }' ,
523+ output : 'const doSomething = () => "bar";' ,
524+ } ,
525+ {
526+ code : 'function namedDeclaration() { console.log("test"); }' ,
527+ output : 'const namedDeclaration = () => { console.log("test"); };' ,
528+ } ,
529+ {
530+ code : 'async function asyncDeclaration() { return await getData(); }' ,
531+ output : 'const asyncDeclaration = async () => await getData();' ,
532+ } ,
533+
534+ // Anonymous function expressions should be transformed
535+ {
536+ code : 'useThisFunction(function() { return "bar"; })' ,
537+ output : 'useThisFunction(() => "bar")' ,
538+ } ,
539+ {
540+ code : 'callback(function() { console.log("test"); })' ,
541+ output : 'callback(() => { console.log("test"); })' ,
542+ } ,
543+ {
544+ code : 'addEventListener("click", function(event) { console.log(event); })' ,
545+ output : 'addEventListener("click", (event) => { console.log(event); })' ,
546+ } ,
547+ {
548+ code : 'arr.map(function(item) { return item * 2; })' ,
549+ output : 'arr.map((item) => item * 2)' ,
550+ } ,
551+ {
552+ code : 'setTimeout(function() { console.log("delayed"); }, 1000)' ,
553+ output : 'setTimeout(() => { console.log("delayed"); }, 1000)' ,
554+ } ,
555+
556+ // Anonymous async function expressions should be transformed
557+ {
558+ code : 'useAsyncFunction(async function() { return await getData(); })' ,
559+ output : 'useAsyncFunction(async () => await getData())' ,
560+ } ,
561+
562+ // Variable assignments with anonymous functions should be transformed
563+ {
564+ code : 'var foo = function() { return "bar"; };' ,
565+ output : 'var foo = () => "bar";' ,
566+ } ,
567+ {
568+ code : 'const handler = function() { console.log("click"); };' ,
569+ output : 'const handler = () => { console.log("click"); };' ,
570+ } ,
571+
572+ // Object methods with anonymous functions should be transformed
573+ {
574+ code : 'var obj = { method: function() { return "test"; } }' ,
575+ output : 'var obj = { method: () => "test" }' ,
576+ } ,
577+
578+ // Nested cases: outer anonymous should transform, inner named expression should not
579+ {
580+ code : 'var outer = function() { return inner(function namedInner() { return "nested"; }); };' ,
581+ output : 'var outer = () => inner(function namedInner() { return "nested"; });' ,
582+ } ,
583+
584+ // Export default declarations should be transformed
585+ {
586+ code : 'export default function() { return "exported"; }' ,
587+ output : 'export default () => "exported";' ,
588+ } ,
589+ ]
590+ . map ( withOptions ( { allowNamedFunctions : 'only-expressions' } ) )
591+ . map ( withErrors ( [ 'USE_ARROW_WHEN_FUNCTION' ] ) )
592+ . concat ( [
593+ // Multiple function scenarios - both should be transformed (handled separately)
594+ {
595+ code : 'function declaration() { return "decl"; } var expr = function() { return "expr"; };' ,
596+ output : 'const declaration = () => "decl"; var expr = () => "expr";' ,
597+ options : [ { allowNamedFunctions : 'only-expressions' } ] ,
598+ errors : [ { messageId : 'USE_ARROW_WHEN_FUNCTION' } , { messageId : 'USE_ARROW_WHEN_FUNCTION' } ] ,
599+ } ,
600+ ] ) ,
601+ } ) ;
602+ } ) ;
603+
604+ describe ( 'it preserves this behavior with only-expressions option' , ( ) => {
605+ ruleTester . run ( 'prefer-arrow-functions' , rule , {
606+ valid : [
607+ // Named function expressions that use this should be preserved
608+ { code : 'obj.addEventListener("click", function handleClick() { this.style.color = "red"; })' } ,
609+ { code : 'elements.forEach(function processElement() { this.process(); })' } ,
610+ { code : 'jQuery(selector).each(function namedEach() { $(this).addClass("active"); })' } ,
611+
612+ // Functions that don't use this but are named expressions should be preserved
613+ { code : 'callback(function namedCallback() { return "no this used"; })' } ,
614+
615+ // Anonymous functions that use this should be preserved (existing behavior)
616+ { code : 'obj.addEventListener("click", function() { this.style.color = "red"; })' } ,
617+ { code : 'elements.forEach(function() { this.process(); })' } ,
618+
619+ // Arrow functions should remain valid (no change)
620+ { code : 'obj.addEventListener("click", () => { console.log("arrow with no this"); })' } ,
621+ ] . map ( withOptions ( { allowNamedFunctions : 'only-expressions' } ) ) ,
622+ invalid : [
623+ // Named function declarations that don't use this should still be transformed
624+ {
625+ code : 'function noThisUsed() { return "safe to transform"; }' ,
626+ output : 'const noThisUsed = () => "safe to transform";' ,
627+ } ,
628+
629+ // Anonymous function expressions that don't use this should be transformed
630+ {
631+ code : 'callback(function() { return "no this, should transform"; })' ,
632+ output : 'callback(() => "no this, should transform")' ,
633+ } ,
634+
635+ // Variable assignments with anonymous functions that don't use this
636+ {
637+ code : 'var handler = function() { console.log("no this"); };' ,
638+ output : 'var handler = () => { console.log("no this"); };' ,
639+ } ,
640+ ]
641+ . map ( withOptions ( { allowNamedFunctions : 'only-expressions' } ) )
642+ . map ( withErrors ( [ 'USE_ARROW_WHEN_FUNCTION' ] ) ) ,
643+ } ) ;
644+ } ) ;
645+ } ) ;
646+
486647describe ( 'when file is TSX' , ( ) => {
487648 describe ( 'it properly fixes generic type arguments' , ( ) => {
488649 ruleTester . run ( 'prefer-arrow-functions' , rule , {
0 commit comments