@@ -95,8 +95,9 @@ class Transformer {
9595 state . operator = this . #getOperator( state )
9696
9797 esquery . traverse ( ast , esquery . parse ( query ) , ( ...args ) => {
98- injectionCount ++
99- this . #visit( state , ...args )
98+ if ( this . #visit( state , ...args ) ) {
99+ injectionCount ++
100+ }
100101 } )
101102 }
102103
@@ -137,11 +138,12 @@ class Transformer {
137138 *
138139 * @param {object } state - Merged config + runtime state for this traversal.
139140 * @param {...unknown } args - `(node, parent, ancestry)` from esquery traverse.
141+ * @returns {boolean } True if injection should be counted, false otherwise.
140142 */
141143 #visit ( state , ...args ) {
142144 const transform = this . #customTransforms[ state . operator ] ?? transforms [ state . operator ]
143145 const { index = 0 } = state . functionQuery
144- const [ node ] = args
146+ const [ node , , ancestry ] = args
145147 const type = node . init ?. type || node . type
146148
147149 // Class nodes are visited for traceInstanceMethod (missing method patching),
@@ -151,14 +153,41 @@ class Transformer {
151153 // nested class like `let Server = (() => { class Server {} })()`) is only
152154 // matched because `[id.name="Server"]` is broad. It is not a function node
153155 // and should not be instrumented or counted toward the function index.
154- if ( node . type === 'VariableDeclarator' ) return
156+ if ( node . type === 'VariableDeclarator' ) return false
155157
156158 state . functionIndex = ++ state . functionIndex || 0
157159
158- if ( index !== null && index !== state . functionIndex ) return
160+ if ( index !== null && index !== state . functionIndex ) return false
161+ } else {
162+ // For class nodes, validate that the method exists before instrumenting
163+ const { methodName } = state . functionQuery
164+ if ( methodName ) {
165+ // Handle both ClassDeclaration/ClassExpression and VariableDeclarator with ClassExpression init
166+ const classNode = node . type === 'VariableDeclarator' ? node . init : node
167+ const classBody = classNode . body
168+ const methodExists = classBody . body . some ( ( { key } ) => key ?. name === methodName )
169+
170+ if ( ! methodExists ) {
171+ // Method doesn't exist on the class. Check if any subclass has it.
172+ const className = classNode . id ?. name || node . id ?. name
173+ const program = ancestry [ ancestry . length - 1 ]
174+
175+ if ( className ) {
176+ const hasMethodInSubclass = esquery . query (
177+ program ,
178+ `ClassDeclaration[superClass.name="${ className } "] > ClassBody > MethodDefinition[key.name="${ methodName } "]`
179+ ) . length > 0
180+
181+ if ( ! hasMethodInSubclass ) {
182+ throw new Error ( `Method '${ methodName } ' not found on class '${ className } ' or any of its subclasses` )
183+ }
184+ }
185+ }
186+ }
159187 }
160188
161189 transform ( state , ...args )
190+ return true
162191 }
163192
164193 /**
0 commit comments