{"record":{"id":"c8e80ebac83b1098","repo":"BabylonJS/Babylon.js","slug":"property-propertykey-tostring-of-object","errorCode":null,"errorMessage":"Property \"${propertyKey.toString()}\" of object \"${target}\" is not a function.","messagePattern":"Property \"(.+?)\" of object \"(.+?)\" is not a function\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/dev/inspector-v2/src/instrumentation/functionInstrumentation.ts","lineNumber":39,"sourceCode":" * @returns A disposable that removes the hooks when disposed and returns the object to its original state.\r\n */\r\n// This overload only matches when K is a specific literal key (not a union like keyof T)\r\nexport function InterceptFunction<T extends object, K extends keyof T>(\r\n    target: T,\r\n    propertyKey: string extends K ? never : number extends K ? never : symbol extends K ? never : K,\r\n    hooks: NonNullable<T[K]> extends (...args: infer Args) => unknown ? FunctionHooks<Args> : FunctionHooks\r\n): IDisposable;\r\n// Fallback overload for generic/dynamic cases where the function type cannot be inferred\r\nexport function InterceptFunction<T extends object>(target: T, propertyKey: keyof T, hooks: FunctionHooks): IDisposable;\r\n/** @internal */\r\nexport function InterceptFunction<T extends object>(target: T, propertyKey: keyof T, hooks: FunctionHooks): IDisposable {\r\n    if (!hooks.afterCall) {\r\n        throw new Error(\"At least one hook must be provided.\");\r\n    }\r\n\r\n    const originalFunction = Reflect.get(target, propertyKey, target) as (...args: any) => any;\r\n    if (typeof originalFunction !== \"function\") {\r\n        throw new Error(`Property \"${propertyKey.toString()}\" of object \"${target}\" is not a function.`);\r\n    }\r\n\r\n    // Make sure the property is configurable and writable, otherwise it is immutable and cannot be intercepted.\r\n    const propertyDescriptor = Reflect.getOwnPropertyDescriptor(target, propertyKey);\r\n    if (propertyDescriptor) {\r\n        if (!propertyDescriptor.configurable) {\r\n            throw new Error(`Property \"${propertyKey.toString()}\" of object \"${target}\" is not configurable.`);\r\n        }\r\n\r\n        if (propertyDescriptor.writable === false || (propertyDescriptor.writable === undefined && !propertyDescriptor.set)) {\r\n            throw new Error(`Property \"${propertyKey.toString()}\" of object \"${target}\" is readonly.`);\r\n        }\r\n    }\r\n\r\n    // Get or create the hooks map for the target object.\r\n    let hooksMap = InterceptorHooksMaps.get(target);\r\n    if (!hooksMap) {\r\n        InterceptorHooksMaps.set(target, (hooksMap = new Map()));\r","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/inspector-v2/src/instrumentation/functionInstrumentation.ts#L21-L57","documentation":"InterceptFunction replaces a property on a target object with an intercepting proxy function, so it requires the property to already be a function. Before instrumenting, it reads the property via Reflect.get and throws this error if the resolved value is not callable. This guards against instrumenting data properties, undefined keys, or typos in the property name.","triggerScenarios":"Calling InterceptFunction(target, propertyKey, hooks) (directly or via useInterceptObservable/hookMainSoundTrack/playHook/stopHook/animateHook/setSelectedItem) when target[propertyKey] is undefined, a data value, a getter returning a non-function, or the property name is misspelled.","commonSituations":"Typo in the method name; instrumenting an object whose API changed between library versions so the method no longer exists; trying to intercept a plain data field or a property that is only assigned later (asynchronously) before it is set; targeting a module namespace object or a frozen/bound copy where the method lives on the prototype under a different key.","solutions":["Verify the exact property name exists on the target at interception time (log Object.keys(target) / console.log(target)).","Fix typos and confirm the property is a function (typeof target[key] === 'function') before calling InterceptFunction.","If the property is assigned asynchronously, defer interception until after assignment (e.g. await initialization, or intercept on the prototype where the method is defined).","If the value is data, use a property/value interception mechanism instead of function instrumentation."],"exampleFix":"// before\nInterceptFunction(player, 'paly' /* typo */, hooks);\n// after\nif (typeof player.play === 'function') {\n  InterceptFunction(player, 'play', hooks);\n}","handlingStrategy":"validation","validationCode":"function canInterceptFunction(target, propertyKey) {\n  const value = Reflect.get(target, propertyKey, target);\n  return typeof value === 'function';\n}\nif (!canInterceptFunction(target, 'play')) throw new TypeError('target.play is not a function');","typeGuard":"function isInterceptableFunction(target: object, key: PropertyKey): boolean {\n  return typeof (target as any)[key] === 'function';\n}","tryCatchPattern":"try {\n  InterceptFunction(target, propertyKey, hooks);\n} catch (e) {\n  if (String((e as Error)?.message).includes('is not a function')) {\n    console.warn(`Cannot intercept ${String(propertyKey)}: not a function on`, target);\n    return null; // skip instrumentation gracefully\n  }\n  throw e;\n}","preventionTips":["Always confirm typeof target[key] === 'function' before InterceptFunction.","Check for typos against the object's actual API surface (console.dir / Object.keys).","Defer interception until asynchronous initialization has assigned the method.","Use TypeScript types for the target so nonexistent members are caught at compile time."],"tags":["instrumentation","runtime","function-interception","api-misuse"],"backgroundTag":"property-is-not-a-function","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}