From 0cc8e436fc1100f3fe6f3dc9234a5f01dcf30f54 Mon Sep 17 00:00:00 2001 From: Nick Olszanski Date: Fri, 15 Sep 2023 21:34:27 +0200 Subject: [PATCH] fix: impore how we replace `this` in third-party code` (#453) * fix: imporove how we replace `this` in third-party code * stash --- src/lib/web-worker/worker-exec.ts | 47 +++++++++++++++++---- tests/unit/worker-exec.spec.ts | 68 ++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/lib/web-worker/worker-exec.ts b/src/lib/web-worker/worker-exec.ts index ab93d29d..685ab84d 100644 --- a/src/lib/web-worker/worker-exec.ts +++ b/src/lib/web-worker/worker-exec.ts @@ -116,17 +116,50 @@ export const runScriptContent = ( return errorMsg; }; +/** + * Replace some `this` symbols with a new value. + * Still not perfect, but might be better than a more naive regex. + * Check out the tests for examples: tests/unit/worker-exec.spec.ts + */ +export const replaceThisInSource = (scriptContent: string, newThis: string): string => { + // Best for now but not perfect + // Use a more complex regex to match potential preceding character and adjust replacement accordingly + const r0 = /(? { + // console.log('\n'); + // console.log('input: ' + scriptContent); + // console.log('match: ', match); + // console.log('p1: ', p1); + // console.log('p2: ', p2); + // console.log('\n'); + if (p1 != null) { + return (p1 || '') + (p2 || '') + 'this'; + } + // If there was a preceding character, include it unchanged + // console.log('===', scriptContent, '----', p1, p2); + return (p1 || '') + (p2 || '') + newThis; + }); + + // 3.5 + // const r = /(^|[^a-zA-Z0-9_$.\'\"`])\.\.\.?this(?![a-zA-Z0-9_$:])/g; + // return scriptContent.replace(r, '$1' + newThis); + // const r = /(? { env.$runWindowLoadEvent$ = 1; + // First we want to replace all `this` symbols + let sourceWithReplacedThis = replaceThisInSource(scriptContent, '(thi$(this)?window:this)'); + scriptContent = - `with(this){${scriptContent - .replace(/\bthis\b/g, (match, offset, originalStr) => - offset > 0 && originalStr[offset - 1] !== '$' ? '(thi$(this)?window:this)' : match - ) - .replace(/\/\/# so/g, '//Xso')}\n;function thi$(t){return t===this}};${( - webWorkerCtx.$config$.globalFns || [] - ) + `with(this){${sourceWithReplacedThis.replace( + /\/\/# so/g, + '//Xso' + )}\n;function thi$(t){return t===this}};${(webWorkerCtx.$config$.globalFns || []) .filter((globalFnName) => /[a-zA-Z_$][0-9a-zA-Z_$]*/.test(globalFnName)) .map((g) => `(typeof ${g}=='function'&&(this.${g}=${g}))`) .join(';')};` + (scriptUrl ? '\n//# sourceURL=' + scriptUrl : ''); diff --git a/tests/unit/worker-exec.spec.ts b/tests/unit/worker-exec.spec.ts index 677af09c..f508f9f6 100644 --- a/tests/unit/worker-exec.spec.ts +++ b/tests/unit/worker-exec.spec.ts @@ -1,7 +1,6 @@ import * as assert from 'uvu/assert'; -import { run } from '../../src/lib/web-worker/worker-exec'; +import { run, replaceThisInSource } from '../../src/lib/web-worker/worker-exec'; import { suite } from './utils'; - const test = suite(); test('add window id to postMessage() when cross-origin', ({ env, win, winId }) => { @@ -93,4 +92,69 @@ test('window is window', ({ env, win }) => { assert.is(win.result, true); }); +test('We should replace `this` keyword more or less sane', ({ env, win }) => { + const replaceSymbol = '__this'; + const r = function (code: string) { + return replaceThisInSource(code, replaceSymbol); + }; + + // Should replace: + // assert.is(r('`sadly we fail at this simple string`'), '`sadly we fail at this simple string`'); + assert.is(r('{this:123}'), '{this:123}'); + assert.is(r('a.this'), 'a.this'); + assert.is(r('[`kathis`]'), '[`kathis`]'); + assert.is(r('{ ...this.opts };'), '{ ...__this.opts };'); + assert.is(r('{ ...this.opts };this.lol;'), '{ ...__this.opts };__this.lol;'); + + const input = ` + // Should replace: + { ...this.opts };this.lol; + this + this.test + log(this.variable) + \`hello \${CONST} is \${this.CONST2}\` + + // Should not replace: + ['this', "this", \`this\`] + {this:123} + { this: 123 } + 'sadly we fail at this simple string' + "same as this" + \`and this is \${false} too\`; + a.b.this + let _this, This, $this + `; + // const rez = replaceThisInSource(input, `__this`); + + const out = ` + // Should replace: + { ...__this.opts };__this.lol; + __this + __this.test + log(__this.variable) + \`hello \${CONST} is \${__this.CONST2}\` + + // Should not replace: + ['this', "this", \`this\`] + {this:123} + { this: 123 } + 'sadly we fail at __this simple string' + "same as __this" + \`and __this is \${false} too\`; + a.b.this + let _this, This, $this + `; + + // assert.is(rez, out); +}); + +// test('properly replaces this is js window context', ({ env, win }) => { +// const s = ` +// let a = { this: 123 }; +// window.result = a.this; +// `; +// run(env, s); +// assert.is(win.result, 123); +// }); + test.run();