@@ -60,24 +60,58 @@ export class HookCollector {
6060 }
6161
6262 private transformHooks ( rawData : RawHookCollection ) : HookCollection {
63- // Deduplicate hooks based on name
63+ // Helper function to merge hooks with the same name and transform them.
64+ const prepareHooks = ( hooks : RawHookData [ 'hooks' ] ) : Hook [ ] => {
65+ const hookMap = new Map < string , RawHookData [ 'hooks' ] [ 0 ] [ ] > ( ) ;
66+
67+ // Group hooks by name
68+ hooks . forEach ( ( hook ) => {
69+ const hookId = this . getHookId ( this . escapeHookName ( hook . name ) ) ;
70+ if ( ! hookMap . has ( hookId ) ) {
71+ hookMap . set ( hookId , [ ] ) ;
72+ }
73+ hookMap . get ( hookId ) ?. push ( hook ) ;
74+ } ) ;
75+
76+ // Merge and transform hooks
77+ return Array . from ( hookMap . entries ( ) ) . map ( ( [ _ , hooks ] ) => {
78+ if ( hooks . length === 1 ) {
79+ return this . transformHook ( hooks [ 0 ] ) ;
80+ }
81+
82+ // Merge multiple hooks with the same name
83+ const mergedHook = { ...hooks [ 0 ] } ;
84+
85+ // Collect sources of all hooks.
86+ mergedHook . files = [ ] ;
87+ hooks . forEach ( ( h ) => {
88+ const file = {
89+ file : h . file || '' ,
90+ line : h . line || 0 ,
91+ } ;
92+ mergedHook . files ?. push ( file ) ;
93+ } ) ;
94+
95+ return this . transformHook ( mergedHook ) ;
96+ } ) ;
97+ } ;
98+
6499 return {
65- actions : rawData . actions . hooks . map ( this . transformHook . bind ( this ) ) ,
66- filters : rawData . filters . hooks . map ( this . transformHook . bind ( this ) ) ,
100+ actions : prepareHooks ( rawData . actions . hooks ) ,
101+ filters : prepareHooks ( rawData . filters . hooks ) ,
67102 } ;
68103 }
69104
70- private transformHook ( hook : RawHookData [ 'hooks' ] [ 0 ] ) : Hook {
71- let hookName = hook . name ;
72-
105+ private escapeHookName ( hookName : string ) : string {
106+ let escapedHookName = hookName ;
73107 /**
74108 * Replace PHP-style interpolations with {$var}_suffix
75109 * Example:
76110 * 'woocommerce_analytics_' . $field . '_' . $context
77111 * becomes
78112 * 'woocommerce_analytics_{$field}_$context'
79113 */
80- hookName = hookName . replace (
114+ escapedHookName = hookName . replace (
81115 / [ ' " ] \s * \. \s * ( \$ (?: [ a - z A - Z _ ] \w * ) (?: - > \w + | \[ [ ^ \] ] + \] | \( \) ) * ( (?: - > \w + | \[ [ ^ \] ] + \] | \( \) ) ) * ) \s * \. \s * [ ' " ] ? _ ? ( [ a - z A - Z 0 - 9 _ ] * ) / g,
82116 ( _ , variable , rest , suffix ) => `{${ variable } ${ rest || '' } }${ suffix ? `_${ suffix } ` : '' } `
83117 ) ;
@@ -89,26 +123,36 @@ export class HookCollector {
89123 * becomes
90124 * 'woocommerce_analytics_{$field}'
91125 */
92- hookName = hookName . replace (
126+ escapedHookName = hookName . replace (
93127 / [ ' " ] \s * \. \s * ( \$ (?: [ a - z A - Z _ ] \w * ) (?: - > \w + | \[ [ ^ \] ] + \] | \( \) ) * ( (?: - > \w + | \[ [ ^ \] ] + \] | \( \) ) ) * ) / g,
94128 ( _ , variable , rest ) => `{${ variable } ${ rest || '' } }`
95129 ) ;
96130
97131 /**
98132 * Remove quotes from hook name
99133 */
100- hookName = hookName . replace ( / [ ' " ] / g, '' ) ;
134+ escapedHookName = hookName . replace ( / [ ' " ] / g, '' ) ;
135+
136+ return escapedHookName ;
137+ }
101138
102- const hookId = hookName
139+ private getHookId ( hookName : string ) : string {
140+ return hookName
103141 . replace ( / [ ^ a - z A - Z 0 - 9 \- _ . ~ ] / g, '' )
104142 . replace ( / ^ _ _ / , '' )
105143 . replace ( / ^ _ / , '' ) ;
144+ }
145+
146+ private transformHook ( hook : RawHookData [ 'hooks' ] [ 0 ] ) : Hook {
147+ const hookName = this . escapeHookName ( hook . name ) ;
148+ const hookId = this . getHookId ( hookName ) ;
106149
107150 return {
108151 id : hookId ,
109152 name : hookName ,
110153 type : hook . type ,
111154 file : hook . file ,
155+ files : hook . files || [ ] ,
112156 line : hook . line || 0 ,
113157 doc : {
114158 description : hook . doc ?. description || '' ,
0 commit comments