@@ -693,3 +693,131 @@ def test_non_exported_const_not_exported(self, ts_analyzer):
693693 is_public_exported , name = ts_analyzer .is_function_exported (code , "publicFunc" )
694694 assert is_public_exported is True
695695 assert name == "publicFunc"
696+
697+
698+ class TestWrappedDefaultExports :
699+ """Tests for wrapped default export pattern - Issue #9.
700+
701+ Handles patterns like:
702+ - export default curry(traverseEntity)
703+ - export default compose(fn1, fn2)
704+ - export default wrapper(myFunc)
705+
706+ These must be correctly recognized so the wrapped function is exportable.
707+ """
708+
709+ @pytest .fixture
710+ def ts_analyzer (self ):
711+ """Create a TypeScript analyzer."""
712+ return TreeSitterAnalyzer (TreeSitterLanguage .TYPESCRIPT )
713+
714+ def test_curry_wrapped_export (self , ts_analyzer ):
715+ """Test export default curry(fn) pattern."""
716+ code = """import { curry } from 'lodash/fp';
717+
718+ const traverseEntity = async (visitor, options, entity) => {
719+ return entity;
720+ };
721+
722+ export default curry(traverseEntity);"""
723+
724+ # Check exports parsing
725+ exports = ts_analyzer .find_exports (code )
726+ assert len (exports ) == 1
727+ assert exports [0 ].default_export == "default"
728+ assert exports [0 ].wrapped_default_args == ["traverseEntity" ]
729+
730+ # Check is_function_exported
731+ is_exported , export_name = ts_analyzer .is_function_exported (code , "traverseEntity" )
732+ assert is_exported is True
733+ assert export_name == "default"
734+
735+ def test_compose_wrapped_export (self , ts_analyzer ):
736+ """Test export default compose(fn1, fn2) pattern with multiple args."""
737+ code = """import { compose } from 'lodash/fp';
738+
739+ function validateInput(data) { return data; }
740+ function processData(data) { return data; }
741+
742+ export default compose(validateInput, processData);"""
743+
744+ exports = ts_analyzer .find_exports (code )
745+ assert len (exports ) == 1
746+ assert exports [0 ].wrapped_default_args == ["validateInput" , "processData" ]
747+
748+ # Both functions should be recognized as exported
749+ is_exported1 , _ = ts_analyzer .is_function_exported (code , "validateInput" )
750+ is_exported2 , _ = ts_analyzer .is_function_exported (code , "processData" )
751+ assert is_exported1 is True
752+ assert is_exported2 is True
753+
754+ def test_nested_wrapper_export (self , ts_analyzer ):
755+ """Test nested wrapper: export default compose(curry(fn))."""
756+ code = """export default compose(curry(myFunc));"""
757+
758+ exports = ts_analyzer .find_exports (code )
759+ assert len (exports ) == 1
760+ assert "myFunc" in exports [0 ].wrapped_default_args
761+
762+ is_exported , _ = ts_analyzer .is_function_exported (code , "myFunc" )
763+ assert is_exported is True
764+
765+ def test_generic_wrapper_export (self , ts_analyzer ):
766+ """Test generic wrapper function."""
767+ code = """const myFunction = (x: number) => x * 2;
768+
769+ export default someWrapper(myFunction);"""
770+
771+ is_exported , export_name = ts_analyzer .is_function_exported (code , "myFunction" )
772+ assert is_exported is True
773+ assert export_name == "default"
774+
775+ def test_non_wrapped_function_not_exported (self , ts_analyzer ):
776+ """Test that functions not in the wrapper call are not exported."""
777+ code = """const helper = (x: number) => x + 1;
778+ const main = (x: number) => helper(x) * 2;
779+
780+ export default curry(main);"""
781+
782+ # main is wrapped, so it's exported
783+ is_main_exported , _ = ts_analyzer .is_function_exported (code , "main" )
784+ assert is_main_exported is True
785+
786+ # helper is NOT in the wrapper call, so not exported
787+ is_helper_exported , _ = ts_analyzer .is_function_exported (code , "helper" )
788+ assert is_helper_exported is False
789+
790+ def test_direct_default_export_still_works (self , ts_analyzer ):
791+ """Test that direct default exports still work."""
792+ code = """function myFunc() { return 1; }
793+ export default myFunc;"""
794+
795+ is_exported , export_name = ts_analyzer .is_function_exported (code , "myFunc" )
796+ assert is_exported is True
797+ assert export_name == "default"
798+
799+ def test_strapi_traverse_entity_pattern (self , ts_analyzer ):
800+ """Test the exact strapi pattern that was failing."""
801+ code = """import { curry } from 'lodash/fp';
802+
803+ const traverseEntity = async (visitor: Visitor, options: TraverseOptions, entity: Data) => {
804+ const { path = { raw: null }, schema, getModel } = options;
805+ // ... implementation
806+ return copy;
807+ };
808+
809+ const createVisitorUtils = ({ data }: { data: Data }) => ({
810+ remove(key: string) { delete data[key]; },
811+ set(key: string, value: Data) { data[key] = value; },
812+ });
813+
814+ export default curry(traverseEntity);"""
815+
816+ # traverseEntity should be recognized as exported
817+ is_exported , export_name = ts_analyzer .is_function_exported (code , "traverseEntity" )
818+ assert is_exported is True
819+ assert export_name == "default"
820+
821+ # createVisitorUtils is NOT wrapped, so not exported via default
822+ is_utils_exported , _ = ts_analyzer .is_function_exported (code , "createVisitorUtils" )
823+ assert is_utils_exported is False
0 commit comments