From b91644b7438fbe16b4d89e97192c14ea0a37ac65 Mon Sep 17 00:00:00 2001 From: Popov Aleksey Date: Fri, 3 Feb 2023 07:22:08 +0400 Subject: [PATCH] feat: added methods handling in optional chaining (#2849) * test: added test in optional chaining for methods * feat: added handling method in optional chaining * fix: fixed condition * refactor: reused function isFunction * test: fixed test * test: fixed test --- .../sandbox/code-instrumentation/index.ts | 3 +- .../code-instrumentation/location/wrapper.ts | 5 +-- .../sandbox/code-instrumentation/methods.ts | 28 ++++++++++------ .../event/drag-and-drop/data-transfer-item.ts | 3 +- src/client/sandbox/event/focus-blur.ts | 7 ++-- src/client/sandbox/event/listeners.ts | 3 +- src/client/sandbox/event/message.ts | 3 +- src/client/sandbox/event/unload.ts | 3 +- src/client/sandbox/fetch.ts | 3 +- src/client/sandbox/ie-debug.ts | 3 +- src/client/sandbox/native-methods.ts | 3 +- src/client/sandbox/node/window.ts | 8 ++--- src/client/sandbox/style.ts | 3 +- src/client/utils/dom.ts | 4 +-- src/client/utils/event.ts | 5 +-- src/client/utils/position.ts | 3 +- src/client/utils/types.ts | 4 +++ src/processing/script/node-builder.ts | 10 ++++-- .../script/transformers/method-call.ts | 8 +++-- .../process-script-test.js | 23 ++++++++++--- test/server/script-processor-test.js | 32 ++++++++++++++++++- 21 files changed, 121 insertions(+), 43 deletions(-) diff --git a/src/client/sandbox/code-instrumentation/index.ts b/src/client/sandbox/code-instrumentation/index.ts index 93c35c173..3912c15ef 100644 --- a/src/client/sandbox/code-instrumentation/index.ts +++ b/src/client/sandbox/code-instrumentation/index.ts @@ -10,6 +10,7 @@ import { getProxyUrl, stringifyResourceType } from '../../utils/url'; import urlResolver from '../../utils/url-resolver'; import EventSandbox from '../event'; import MessageSandbox from '../event/message'; +import { isFunction } from 'lodash'; export default class CodeInstrumentation extends SandboxBase { static readonly WRAPPED_EVAL_FN = 'hammerhead|code-instrumentation|wrapped-eval-fn'; @@ -121,7 +122,7 @@ export default class CodeInstrumentation extends SandboxBase { return target; const shouldConvertToArray = !nativeMethods.isArray.call(nativeMethods.Array, target) && - typeof target[Symbol.iterator] === 'function'; + isFunction(target[Symbol.iterator]); return shouldConvertToArray ? nativeMethods.arrayFrom.call(nativeMethods.Array, target) : target; }, diff --git a/src/client/sandbox/code-instrumentation/location/wrapper.ts b/src/client/sandbox/code-instrumentation/location/wrapper.ts index 684ae9e8f..631bdb420 100644 --- a/src/client/sandbox/code-instrumentation/location/wrapper.ts +++ b/src/client/sandbox/code-instrumentation/location/wrapper.ts @@ -27,6 +27,7 @@ import IntegerIdGenerator from '../../../utils/integer-id-generator'; import { createOverriddenDescriptor, overrideStringRepresentation } from '../../../utils/overriding'; import MessageSandbox from '../../event/message'; import { isIE11 } from '../../../utils/browser'; +import { isFunction } from '../../../utils/types'; const GET_ORIGIN_CMD = 'hammerhead|command|get-origin'; const ORIGIN_RECEIVED_CMD = 'hammerhead|command|origin-received'; @@ -263,7 +264,7 @@ export default class LocationWrapper extends LocationInheritor { const protoKeys = nativeMethods.objectKeys(Location.prototype); const locWrapper = this; const rewriteDescriptorFn = (descriptor, key: string) => { - if (typeof descriptor[key] !== 'function') + if (!isFunction(descriptor[key])) return; const nativeMethod = descriptor[key]; @@ -293,7 +294,7 @@ export default class LocationWrapper extends LocationInheritor { } // NOTE: window.Location in IE11 is object -if (typeof Location !== 'function') +if (!isFunction(Location)) LocationWrapper.toString = () => Location.toString(); else overrideStringRepresentation(LocationWrapper, Location); diff --git a/src/client/sandbox/code-instrumentation/methods.ts b/src/client/sandbox/code-instrumentation/methods.ts index 5c43bd8fe..46959733c 100644 --- a/src/client/sandbox/code-instrumentation/methods.ts +++ b/src/client/sandbox/code-instrumentation/methods.ts @@ -3,7 +3,11 @@ import INSTRUCTION from '../../../processing/script/instruction'; import { shouldInstrumentMethod } from '../../../processing/script/instrumented'; import { isWindow, isLocation } from '../../utils/dom'; import fastApply from '../../utils/fast-apply'; -import * as typeUtils from '../../utils/types'; +import { + isNullOrUndefined, + inaccessibleTypeToStr, + isFunction, +} from '../../utils/types'; import { getProxyUrl, stringifyResourceType } from '../../utils/url'; import nativeMethods from '../native-methods'; import MessageSandbox from '../event/message'; @@ -59,7 +63,7 @@ export default class MethodCallInstrumentation extends SandboxBase { if (win.postMessage === win.postMessage) return win.postMessage === fn; - return fn && typeof fn.toString === 'function' && fn.toString() === win.postMessage.toString(); + return fn && isFunction(fn.toString) && fn.toString() === win.postMessage.toString(); } attach (window: Window & typeof globalThis) { @@ -68,20 +72,26 @@ export default class MethodCallInstrumentation extends SandboxBase { // NOTE: In Google Chrome, iframes whose src contains html code raise the 'load' event twice. // So, we need to define code instrumentation functions as 'configurable' so that they can be redefined. nativeMethods.objectDefineProperty(window, INSTRUCTION.callMethod, { - value: (owner: any, methName: any, args: any[]) => { - if (typeUtils.isNullOrUndefined(owner)) - MethodCallInstrumentation._error(`Cannot call method '${methName}' of ${typeUtils.inaccessibleTypeToStr(owner)}`); + value: (owner: any, methName: any, args: any[], optional = false) => { + if (isNullOrUndefined(owner) && !optional) + MethodCallInstrumentation._error(`Cannot call method '${methName}' of ${inaccessibleTypeToStr(owner)}`); - if (typeof owner[methName] !== 'function') + if (!isFunction(owner[methName]) && !optional) MethodCallInstrumentation._error(`'${methName}' is not a function`); // OPTIMIZATION: previously we've performed the // `this.methodWrappers.hasOwnProperty(methName)` // check which is quite slow. Now we use the // fast RegExp check instead. - if (typeof methName === 'string' && shouldInstrumentMethod(methName) && - this.methodWrappers[methName].condition(owner)) - return this.methodWrappers[methName].method(owner, args); + if (typeof methName === 'string' && shouldInstrumentMethod(methName)) { + if (optional && !isFunction(owner[methName])) + return void 0; + else if (this.methodWrappers[methName].condition(owner)) + return this.methodWrappers[methName].method(owner, args); + } + + if (optional && !isFunction(owner[methName])) + return void 0; return fastApply(owner, methName, args); }, diff --git a/src/client/sandbox/event/drag-and-drop/data-transfer-item.ts b/src/client/sandbox/event/drag-and-drop/data-transfer-item.ts index d16cdd2f6..03e0b1a0f 100644 --- a/src/client/sandbox/event/drag-and-drop/data-transfer-item.ts +++ b/src/client/sandbox/event/drag-and-drop/data-transfer-item.ts @@ -1,3 +1,4 @@ +import { isFunction } from '../../../utils/types'; import nativeMethods from '../../native-methods'; import DATA_TRANSFER_ITEM_KIND from './data-transfer-item-kind'; @@ -23,7 +24,7 @@ export default class DataTransferItem { if (!arguments.length) throw new Error("Failed to execute 'getAsString' on 'DataTransferItem': 1 argument required, but only 0 present."); - if (typeof callback !== 'function') + if (!isFunction(callback)) return; if (kind !== DATA_TRANSFER_ITEM_KIND.string) diff --git a/src/client/sandbox/event/focus-blur.ts b/src/client/sandbox/event/focus-blur.ts index 9d22ed402..3797f32df 100644 --- a/src/client/sandbox/event/focus-blur.ts +++ b/src/client/sandbox/event/focus-blur.ts @@ -13,6 +13,7 @@ import TimersSandbox from '../timers'; import ElementEditingWatcher from './element-editing-watcher'; import { ScrollState } from '../../../typings/client'; import nextTick from '../../utils/next-tick'; +import { isFunction } from '../../utils/types'; const INTERNAL_FOCUS_BLUR_FLAG_PREFIX = 'hammerhead|event|internal-'; @@ -279,7 +280,7 @@ export default class FocusBlurSandbox extends SandboxBase { if (browserUtils.isMSEdge && el && domUtils.isTextEditableElement(el)) this._eventSimulator.selectionchange(el); - if (typeof callback === 'function') + if (isFunction(callback)) callback(); } @@ -403,7 +404,7 @@ export default class FocusBlurSandbox extends SandboxBase { this.blur(domUtils.getIframeByElement(activeElement), raiseFocusEvent, true, isNativeFocus); else if (!focusOnChange) raiseFocusEvent(); - else if (typeof callback === 'function') + else if (isFunction(callback)) callback(); }, silent, isNativeFocus, el); } @@ -450,7 +451,7 @@ export default class FocusBlurSandbox extends SandboxBase { }; this._raiseEvent(el, 'blur', () => { - if (typeof callback === 'function') + if (isFunction(callback)) callback(focusedOnChange); }, raiseEventParameters); } diff --git a/src/client/sandbox/event/listeners.ts b/src/client/sandbox/event/listeners.ts index 6e93cb827..2272b1a3e 100644 --- a/src/client/sandbox/event/listeners.ts +++ b/src/client/sandbox/event/listeners.ts @@ -19,6 +19,7 @@ import { overrideFunction, overrideStringRepresentation, } from '../../utils/overriding'; +import { isFunction } from '../../utils/types'; const LISTENED_EVENTS = [ 'click', 'mousedown', 'mouseup', 'dblclick', 'contextmenu', 'mousemove', 'mouseover', 'mouseout', @@ -85,7 +86,7 @@ export default class Listeners extends EventEmitter { if (Listeners._isIEServiceHandler(listener) || eventCtx.cancelOuterHandlers) return null; - if (typeof eventCtx.outerHandlersWrapper === 'function') + if (isFunction(eventCtx.outerHandlersWrapper)) return eventCtx.outerHandlersWrapper.call(this, e, listener); return callEventListener(this, listener, e); diff --git a/src/client/sandbox/event/message.ts b/src/client/sandbox/event/message.ts index eaaa4b890..e70c843ea 100644 --- a/src/client/sandbox/event/message.ts +++ b/src/client/sandbox/event/message.ts @@ -18,6 +18,7 @@ import { parse as parseJSON, stringify as stringifyJSON } from '../../../utils/j import Listeners from './listeners'; import UnloadSandbox from './unload'; import settings from '../../settings'; +import { isFunction } from '../../utils/types'; enum MessageType { // eslint-disable-line no-shadow Service = 'hammerhead|service-msg', @@ -202,7 +203,7 @@ export default class MessageSandbox extends SandboxBase { overrideDescriptor(eventPropsOwner, 'onmessage', { getter: () => this.storedOnMessageHandler, setter: handler => { - this.storedOnMessageHandler = typeof handler === 'function' ? handler : null; + this.storedOnMessageHandler = isFunction(handler) ? handler : null; nativeMethods.winOnMessageSetter.call(window, this.storedOnMessageHandler ? e => this._onWindowMessage(e, handler) diff --git a/src/client/sandbox/event/unload.ts b/src/client/sandbox/event/unload.ts index 28224c43b..93fbc526f 100644 --- a/src/client/sandbox/event/unload.ts +++ b/src/client/sandbox/event/unload.ts @@ -4,6 +4,7 @@ import createPropertyDesc from '../../utils/create-property-desc.js'; import { isFirefox, isIOS } from '../../utils/browser'; import { overrideDescriptor } from '../../utils/overriding'; import Listeners from './listeners'; +import { isFunction } from '../../utils/types'; interface EventProperties { storedReturnValue: string; @@ -163,7 +164,7 @@ export default class UnloadSandbox extends SandboxBase { } setOnEvent (eventProperties: EventProperties, window: Window, handler) { - if (typeof handler === 'function') { + if (isFunction(handler)) { eventProperties.storedHandler = handler; eventProperties.eventPropSetter.call(window, e => this._createEventHandler(eventProperties)(e, handler)); diff --git a/src/client/sandbox/fetch.ts b/src/client/sandbox/fetch.ts index 2da769cad..7cb922ce7 100644 --- a/src/client/sandbox/fetch.ts +++ b/src/client/sandbox/fetch.ts @@ -20,6 +20,7 @@ import { isAuthorizationHeader, removeAuthenticatePrefix, removeAuthorizationPrefix, } from '../../utils/headers'; +import { isFunction } from '../utils/types'; function getCredentialsMode (credentialsOpt: any) { credentialsOpt = String(credentialsOpt).toLowerCase(); @@ -184,7 +185,7 @@ export default class FetchSandbox extends SandboxBaseWithDelayedSettings { overrideFunction(window.Headers.prototype, 'forEach', function (this: Headers, ...args: Parameters) { const callback = args[0]; - if (typeof callback === 'function') { + if (isFunction(callback)) { args[0] = function (value, name, headers) { value = FetchSandbox._removeAuthHeadersPrefix(name, value); diff --git a/src/client/sandbox/ie-debug.ts b/src/client/sandbox/ie-debug.ts index f4a9149dc..4b15e10f3 100644 --- a/src/client/sandbox/ie-debug.ts +++ b/src/client/sandbox/ie-debug.ts @@ -1,5 +1,6 @@ import SandboxBase from './base'; import { isIE } from '../utils/browser'; +import { isFunction } from '../utils/types'; const BROWSERTOOLS_CONSOLE_SAFEFUNC = '__BROWSERTOOLS_CONSOLE_SAFEFUNC'; @@ -13,7 +14,7 @@ export default class IEDebugSandbox extends SandboxBase { } _createFuncWrapper (func) { - if (typeof func === 'function') { + if (isFunction(func)) { return (fn, safeAssert) => { const ieDebugSandbox = this; diff --git a/src/client/sandbox/native-methods.ts b/src/client/sandbox/native-methods.ts index 67ab343a0..2c922a496 100644 --- a/src/client/sandbox/native-methods.ts +++ b/src/client/sandbox/native-methods.ts @@ -1,6 +1,7 @@ /*global Document, Window */ import globalContextInfo from '../utils/global-context-info'; import { isNativeFunction } from '../utils/overriding'; +import { isFunction } from '../utils/types'; const NATIVE_CODE_RE = /\[native code]/; @@ -1210,7 +1211,7 @@ class NativeMethods { this.FileList = win.FileList; // NOTE: non-IE11 case. window.File in IE11 is not constructable. - if (win.File && typeof win.File === 'function') + if (win.File && isFunction(win.File)) this.File = win.File; } diff --git a/src/client/sandbox/node/window.ts b/src/client/sandbox/node/window.ts index ec3c40e12..20e5955d6 100644 --- a/src/client/sandbox/node/window.ts +++ b/src/client/sandbox/node/window.ts @@ -46,7 +46,7 @@ import { isIframeWindow, } from '../../utils/dom'; -import { isPrimitiveType } from '../../utils/types'; +import { isFunction, isPrimitiveType } from '../../utils/types'; import INTERNAL_ATTRS from '../../../processing/dom/internal-attributes'; import INTERNAL_PROPS from '../../../processing/dom/internal-properties'; import constructorIsCalledWithoutNewKeyword from '../../utils/constructor-is-called-without-new-keyword'; @@ -385,7 +385,7 @@ export default class WindowSandbox extends SandboxBase { } private static _patchFunctionPrototype (fn: Function, ctx: any): void { - if (!ctx || typeof ctx === 'function') + if (!ctx || isFunction(ctx)) return; const inheritorProto = nativeMethods.objectGetPrototypeOf(ctx); @@ -844,8 +844,8 @@ export default class WindowSandbox extends SandboxBase { return nativeMethods.functionToString.call(this); }); - if (typeof window.history.pushState === 'function' - && typeof window.history.replaceState === 'function' + if (isFunction(window.history.pushState) + && isFunction(window.history.replaceState) && !this.proxyless) { const createWrapperForHistoryStateManipulationFn = function (nativeFn) { return function (this: History, ...args) { diff --git a/src/client/sandbox/style.ts b/src/client/sandbox/style.ts index 7c6738438..bfda48594 100644 --- a/src/client/sandbox/style.ts +++ b/src/client/sandbox/style.ts @@ -8,6 +8,7 @@ import { import styleProcessor from './../../processing/style'; import { getProxyUrl, parseProxyUrl } from './../utils/url'; +import { isFunction } from '../utils/types'; const CSS_STYLE_IS_PROCESSED = 'hammerhead|style|is-processed'; const CSS_STYLE_PROXY_OBJECT = 'hammerhead|style|proxy-object'; @@ -187,7 +188,7 @@ export default class StyleSandbox extends SandboxBase { const nativeFn = this.nativeMethods.objectGetOwnPropertyDescriptor.call(window.Object, styleDeclarationProto, prop).value;// eslint-disable-line no-restricted-properties if (this.nativeMethods.objectHasOwnProperty.call(styleDeclarationProto, prop) && - typeof nativeFn === 'function') { + isFunction(nativeFn)) { (styleDeclarationProto[prop] as unknown as Function) = function (this: Window) { return nativeFn.apply(this[CSS_STYLE_PROXY_TARGET] || this, arguments); }; diff --git a/src/client/utils/dom.ts b/src/client/utils/dom.ts index 9fbd6e423..d5ec95999 100644 --- a/src/client/utils/dom.ts +++ b/src/client/utils/dom.ts @@ -17,7 +17,7 @@ import { import { getNativeQuerySelectorAll } from './query-selector'; import { instanceAndPrototypeToStringAreEqual } from './feature-detection'; -import { isNumber } from './types'; +import { isFunction, isNumber } from './types'; let scrollbarSize = 0; @@ -862,7 +862,7 @@ export function findParent (node, includeSelf = false, predicate) { node = getParent(node); while (node) { - if (typeof predicate !== 'function' || predicate(node)) + if (!isFunction(predicate) || predicate(node)) return node; node = getParent(node); diff --git a/src/client/utils/event.ts b/src/client/utils/event.ts index 8e8906c6e..ef7ce4481 100644 --- a/src/client/utils/event.ts +++ b/src/client/utils/event.ts @@ -1,4 +1,5 @@ import nativeMethods from '../sandbox/native-methods'; +import { isFunction } from './types'; const COMPOSED_EVENTS = [ 'blur', @@ -77,11 +78,11 @@ export function stopPropagation (ev) { } export function isObjectEventListener (listener) { - return typeof listener === 'object' && listener && typeof listener.handleEvent === 'function'; + return typeof listener === 'object' && listener && isFunction(listener.handleEvent); } export function isValidEventListener (listener) { - return typeof listener === 'function' || isObjectEventListener(listener); + return isFunction(listener) || isObjectEventListener(listener); } export function callEventListener (ctx, listener, e) { diff --git a/src/client/utils/position.ts b/src/client/utils/position.ts index b50f04464..9c3951094 100644 --- a/src/client/utils/position.ts +++ b/src/client/utils/position.ts @@ -2,6 +2,7 @@ import nativeMethods from '../sandbox/native-methods'; import * as domUtils from './dom'; import * as styleUtils from './style'; import { isFirefox, isIE } from './browser'; +import { isFunction } from './types'; function getAreaElementRectangle (el, mapContainer) { const shape = nativeMethods.getAttribute.call(el, 'shape'); @@ -318,7 +319,7 @@ export function getOffsetPosition (el, roundFn = Math.round) { let { left, top } = calcOffsetPositionFn(el, borders, offsetPosition, doc, currentIframe); - if (typeof roundFn === 'function') { + if (isFunction(roundFn)) { left = roundFn(left); top = roundFn(top); } diff --git a/src/client/utils/types.ts b/src/client/utils/types.ts index c4bcaab96..28ae1448c 100644 --- a/src/client/utils/types.ts +++ b/src/client/utils/types.ts @@ -25,3 +25,7 @@ export function isNull (obj) { export function isNumber (val) { return typeof val === 'number'; } + +export function isFunction (val) { + return typeof val === 'function'; +} diff --git a/src/processing/script/node-builder.ts b/src/processing/script/node-builder.ts index cf20293be..d8e6595ba 100644 --- a/src/processing/script/node-builder.ts +++ b/src/processing/script/node-builder.ts @@ -163,11 +163,15 @@ export function createPropertySetWrapper (propertyName: string, obj: Expression, return createSimpleCallExpression(setPropertyIdentifier, [obj, createSimpleLiteral(propertyName), value]); } -export function createMethodCallWrapper (owner: Expression, method: Literal, args: (Expression | SpreadElement)[]): CallExpression { +export function createMethodCallWrapper (owner: Expression, method: Literal, methodArgs: (Expression | SpreadElement)[], optional = false): CallExpression { const callMethodIdentifier = createIdentifier(INSTRUCTION.callMethod); - const methodArgsArray = createArrayExpression(args); + const methodArgsArray = createArrayExpression(methodArgs); + const args = [owner, method, methodArgsArray]; - return createSimpleCallExpression(callMethodIdentifier, [owner, method, methodArgsArray]); + if (optional) + args.push(createSimpleLiteral(optional)); + + return createSimpleCallExpression(callMethodIdentifier, args); } export function createPropertyGetWrapper (propertyName: string, owner: Expression, optional = false): CallExpression { diff --git a/src/processing/script/transformers/method-call.ts b/src/processing/script/transformers/method-call.ts index 8e9d9b991..66bb719ba 100644 --- a/src/processing/script/transformers/method-call.ts +++ b/src/processing/script/transformers/method-call.ts @@ -9,6 +9,7 @@ import { Expression, Literal, Identifier, + SimpleCallExpression, } from 'estree'; import { Transformer } from './index'; @@ -45,12 +46,13 @@ const transformer: Transformer = { }, run: node => { - const callee = node.callee as MemberExpression; - const method = callee.computed + const callee = node.callee as MemberExpression; + const method = callee.computed ? callee.property as Literal : createSimpleLiteral((callee.property as Identifier).name); // eslint-disable-line no-extra-parens + const optional = (node as SimpleCallExpression).optional; - return createMethodCallWrapper(callee.object as Expression, method, node.arguments); + return createMethodCallWrapper(callee.object as Expression, method, node.arguments, optional); }, }; diff --git a/test/client/fixtures/sandbox/code-instrumentation/process-script-test.js b/test/client/fixtures/sandbox/code-instrumentation/process-script-test.js index 9dd8a8836..876a33d1e 100644 --- a/test/client/fixtures/sandbox/code-instrumentation/process-script-test.js +++ b/test/client/fixtures/sandbox/code-instrumentation/process-script-test.js @@ -245,12 +245,27 @@ if (!browserUtils.isIE11) { src: 'var obj = null; var counter = 0; window.optionChainingResult = obj?.[counter -1];', expected: void 0, }, - { - src: 'var obj = { href: "123" }; window.optionChainingResult = obj?.["href"];', - expected: '123', - }, ]; + var additionalCases = []; + + // NOTE: Safari until iOS 13.4 don't have full support optional chaining + if (!browserUtils.isIOS || browserUtils.compareVersions([browserUtils.webkitVersion, '608.2.11']) === 1) { + var additionalCases = [ + { + src: 'var obj = { href: "123" }; window.optionChainingResult = obj?.["href"];', + expected: '123', + }, + { + src: 'var obj = {}; window.optionChainingResult = obj["href"]?.();', + expected: void 0, + }, + ]; + + for (let i = 0; i < additionalCases.length; i++) + testCases.push(additionalCases[i]); + } + for (var i = 0; i < testCases.length; i++) { var testCase = testCases[i]; var script = processScript(testCase.src); diff --git a/test/server/script-processor-test.js b/test/server/script-processor-test.js index d99c38725..90df070cd 100644 --- a/test/server/script-processor-test.js +++ b/test/server/script-processor-test.js @@ -2,7 +2,10 @@ const { expect } = require('chai'); const multiline = require('multiline'); const { processScript, isScriptProcessed } = require('../../lib/processing/script'); const { HEADER, SCRIPT_PROCESSING_START_COMMENT } = require('../../lib/processing/script/header'); -const { PROPERTIES: INSTRUMENTED_PROPERTIES } = require('../../lib/processing/script/instrumented'); +const { + PROPERTIES: INSTRUMENTED_PROPERTIES, + METHODS: INSTRUMENTED_METHODS, +} = require('../../lib/processing/script/instrumented'); const ACORN_UNICODE_PATCH_WARNING = multiline(function () {/* ATTENTION! If this test fails, this may happen because you have updated acorn. @@ -117,6 +120,22 @@ function testPropertyProcessing (templates) { }); } +function testMethodProcessing (templates) { + INSTRUMENTED_METHODS.forEach(propName => { + if (propName.indexOf('-') !== -1) + return; + + const testCases = templates.map(template => { + return { + src: template.src.replace(/\{0}/g, propName), + expected: template.expected.replace(/\{0}/g, propName), + }; + }); + + testProcessing(testCases); + }); +} + function assertHasHeader (expected, testCases) { testCases.forEach(src => { const processed = processScript(src, true); @@ -842,6 +861,17 @@ describe('Script processor', () => { expected: '__get$(obj,"{0}",true)?.method(args)', }, ]); + + testMethodProcessing([ + { + src: 'obj.{0}?.()', + expected: '__call$(obj,"{0}",[],true)', + }, + { + src: 'obj.[0]?.()', + expected: 'obj.[0]?.()', + }, + ]); }); describe('Destructuring', () => {